diff --git a/.ameba.yml b/.ameba.yml new file mode 100644 index 00000000000..0569b1c3886 --- /dev/null +++ b/.ameba.yml @@ -0,0 +1,63 @@ +# This configuration file was generated by `ameba --gen-config` +# on 2019-10-15 23:45:35 UTC using Ameba version 0.10.1. +# The point is for the user to remove these configuration records +# one by one as the reported problems are removed from the code base. + +# Run `ameba --only Metrics/CyclomaticComplexity` for details +Metrics/CyclomaticComplexity: + Description: Disallows methods with a cyclomatic complexity higher than `MaxComplexity` + MaxComplexity: 12 + Enabled: true + Severity: Convention + +# Run `ameba --only Lint/ShadowingOuterLocalVar` for details +Lint/ShadowingOuterLocalVar: + Description: Disallows the usage of the same name as outer local variables for block + or proc arguments. + Enabled: true + Severity: Warning + +# Run `ameba --only Style/ConstantNames` for details +Style/ConstantNames: + Description: Enforces constant names to be in screaming case + Enabled: False + Severity: Convention + +# Run `ameba --only Style/UnlessElse` for details +Style/UnlessElse: + Description: Disallows the use of an `else` block with the `unless` + Enabled: true + Severity: Convention + +# Run `ameba --only Lint/UnusedArgument` for details +Lint/UnusedArgument: + Description: Disallows unused arguments + IgnoreDefs: true + IgnoreBlocks: false + IgnoreProcs: false + Enabled: true + Severity: Warning + +# Run `ameba --only Lint/UselessAssign` for details +Lint/UselessAssign: + Description: Disallows useless variable assignments + Enabled: true + Severity: Warning + Excluded: + - repositories/private_drivers/lib/redis/spec/redis_spec.cr + - repositories/private_drivers/lib/pool/test/pool_test.cr + - repositories/private_drivers/lib/pool/test/connection_pool_test.cr + +# Run `ameba --only Lint/UnreachableCode` for details +Lint/UnreachableCode: + Description: Reports unreachable code + Enabled: true + Severity: Warning + Excluded: + - repositories/private_drivers/lib/driver/src/driver/protocol/management.cr + +# Run `ameba --only Style/VariableNames` for details +Style/VariableNames: + Description: Enforces variable names to be in underscored case + Enabled: true + Severity: Convention diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..abad59c9f43 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: 'Bug: A concise description of the behaviour' +labels: bug +assignees: '' + +--- + +**Describe the bug** + +A clear and concise description of what the bug is. + +**To Reproduce** + +Steps to reproduce the behaviour or a minimal code snippet that demonstrates the behaviour. + +**Expected behaviour** + +A clear and concise description of what you expected to happen. + +**Screenshots or a paste of terminal output** + +If applicable, add screenshots to help explain your problem. + +**Versions (please complete the following information):** + +- Output of `$ crystal version` +- Driver version [e.g. 3.x] + +**Additional context** + +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/driver_migration.md b/.github/ISSUE_TEMPLATE/driver_migration.md new file mode 100644 index 00000000000..bc50ed19207 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/driver_migration.md @@ -0,0 +1,20 @@ +--- +name: Driver Migration +about: Migrate existing Ruby Engine Driver to Crystal +title: 'Driver Migration: Migrate existing Ruby driver' +labels: driver +assignees: '' + +--- + +**Driver to be Migrated** + +Information about the driver to be migrated. + +**Link to Existing Driver** + +Link to existing Driver on Ruby Drivers Repo. + +**Additional context** + +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/driver_request.md b/.github/ISSUE_TEMPLATE/driver_request.md new file mode 100644 index 00000000000..b68b3c805a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/driver_request.md @@ -0,0 +1,32 @@ +--- +name: Driver Request +about: Request a new driver to be created +title: 'Driver Request: Information required to create a new driver' +labels: driver +assignees: '' + +--- + +**Driver Type** + +Logic/Device/SSH/Websocket + +**Manufacturer** + +Manufacturer of device, software or service + +**Model/Service** + +Model or Service + +**Link to or Attach Device API or Protocol** + +If applicable, add screenshots to help explain your problem. + +**Describe any desired functionality** + +- Control all aspects of device + +**Additional context** + +Add any other context about the driver request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..01f460a18d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: 'RFC: Concise description of desired feature' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** + +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** + +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** + +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** + +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/crystal.yml b/.github/workflows/crystal.yml new file mode 100644 index 00000000000..597fcda80ae --- /dev/null +++ b/.github/workflows/crystal.yml @@ -0,0 +1,21 @@ +name: Crystal CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + style: + runs-on: ubuntu-latest + container: + image: crystallang/crystal + steps: + - uses: actions/checkout@v2 + - name: Format + run: crystal tool format + - name: Lint + uses: crystal-ameba/github-action@v0.2.6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 0792935e4a3..cb138427422 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ lib .shards app *.dwarf +repositories/* +bin +.DS_Store +*.rdb diff --git a/.travis.yml b/.travis.yml index ffc7b6ac56d..fc1d546a2dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,9 @@ +dist: xenial +sudo: required + language: crystal +install: + - docker-compose up -d + - sleep 10 +script: + - docker exec -it drivers crystal spec diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000000..c1424e57eb7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "gdb", + "request": "launch", + "target": "./bin/test-harness", + "cwd": "${workspaceRoot}", + "preLaunchTask": "Compile", + "setupCommands": [ + { "text": "-gdb-set follow-fork-mode child" } + ] + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000000..ce3aa5cfd9f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,10 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile", + "command": "shards build --debug drivers", + "type": "shell" + } + ] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..5cecc6d349c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM crystallang/crystal:0.36.1-alpine +COPY . /src +WORKDIR /src + +# Install the latest version of LibSSH2 and the GDB debugger +RUN apk update +RUN apk add --no-cache libssh2 libssh2-dev libssh2-static iputils gdb + +# Add trusted CAs for communicating with external services +RUN apk update && apk add --no-cache ca-certificates tzdata && update-ca-certificates + +# Build App +RUN rm -rf lib bin +RUN mkdir -p /src/bin/drivers +RUN shards build --error-trace --production + +# Run the app binding on port 8080 +EXPOSE 8080 +ENTRYPOINT ["/src/bin/test-harness"] +CMD ["/src/bin/test-harness", "-b", "0.0.0.0", "-p", "8080"] diff --git a/README.md b/README.md index 1bd8efef9e8..4d47a69e423 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,15 @@ -# Spider-Gazelle Application Template +# PlaceOS Drivers -[![Build Status](https://travis-ci.org/spider-gazelle/spider-gazelle.svg?branch=master)](https://travis-ci.org/spider-gazelle/spider-gazelle) +[![Build Status](https://travis-ci.org/placeos/drivers.svg?branch=master)](https://travis-ci.org/placeos/drivers) -Clone this repository to start building your own spider-gazelle based application +Manage and test [PlaceOS](https://place.technology) drivers. -## Documentation +## Development -* [Action Controller](https://github.com/spider-gazelle/action-controller) base class for building [Controllers](http://guides.rubyonrails.org/action_controller_overview.html) -* [Active Model](https://github.com/spider-gazelle/active-model) base class for building [ORMs](https://en.wikipedia.org/wiki/Object-relational_mapping) -* [Habitat](https://github.com/luckyframework/habitat) configuration and settings for Crystal projects -* [router.cr](https://github.com/tbrand/router.cr) base request handling -* [Radix](https://github.com/luislavena/radix) Radix Tree implementation for request routing -* [HTTP::Server](https://crystal-lang.org/api/latest/HTTP/Server.html) built-in Crystal Lang HTTP server - * Request - * Response - * Cookies - * Headers - * Params etc +To spin up the test harness, clone the repository and run... +```bash +$ docker-compose up -d +``` -Spider-Gazelle builds on the amazing performance of **router.cr** [here](https://github.com/tbrand/which_is_the_fastest).:rocket: - - -## Testing - -`crystal spec` - -* to run in development mode `crystal ./src/app.cr` - -## Compiling - -`crystal build ./src/app.cr` - -### Deploying - -Once compiled you are left with a binary `./app` - -* for help `./app --help` -* viewing routes `./app --routes` -* run on a different port or host `./app -h 0.0.0.0 -p 80` +Point a browser to [localhost:8085](http://localhost:8085), and you're good to go. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000000..726c92ece9c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +version: "3.7" +services: + redis: + image: eqalpha/keydb + restart: always + container_name: redis + hostname: redis + environment: + - TZ=$TZ + + drivers: + build: . + image: placeos/drivers + restart: always + container_name: drivers + hostname: drivers + environment: + - CRYSTAL_PATH=lib:/lib/local-shards + depends_on: + - redis + ports: + - 127.0.0.1:8085:8080 + - 127.0.0.1:4444:4444 + volumes: + - ./drivers/:/src/drivers/ + - ./repositories/:/src/repositories/ + - ./lib/:/lib/local-shards/ + environment: + - REDIS_URL=redis://redis:6379 + - TZ=$TZ diff --git a/docs/directory_structure.md b/docs/directory_structure.md new file mode 100644 index 00000000000..88355f1f49b --- /dev/null +++ b/docs/directory_structure.md @@ -0,0 +1,20 @@ +# Directory Structures + +PlaceOS core / drivers makes the assumption that the working directory one level +up from the scratch directory. An example deployment structure: + +* Working dir: `/home/placeos/core` +* Executable: `/home/placeos/core/bin/core` +* Driver repositories: `/home/placeos/repositories` + * PlaceOS Drivers: `/home/placeos/repositories/drivers` +* Driver executables: `/home/placeos/core/bin/drivers` + * Samsung driver: `/home/placeos/core/bin/drivers/353b53_samsung_display_md_series_cr` + +However when developing the structure will look more like: + +* Working dir: `/home/steve/drivers` +* Driver repository: `/home/steve/drivers` +* Driver executables: `/home/steve/drivers/bin/drivers` + * Samsung driver: `/home/placeos/core/bin/drivers/353b53_samsung_display_md_series_cr` + +The primary difference between production and development is PlaceOS core, in production, will be cloning repositories and installing shards as required. diff --git a/docs/gdb-entitlement.xml b/docs/gdb-entitlement.xml new file mode 100644 index 00000000000..9d9251f55d9 --- /dev/null +++ b/docs/gdb-entitlement.xml @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.debugger + + + + + diff --git a/docs/http-api.md b/docs/http-api.md new file mode 100644 index 00000000000..9e908404b81 --- /dev/null +++ b/docs/http-api.md @@ -0,0 +1,154 @@ +# HTTP API + +Primarily for development. + + +## GET /build + +Returns the list of available drivers + +* `repository=folder_name` (optional) if you wish to specify a third party repository +* `compiled=true` (optional) if you only want the list of compiled drivers + +```json + +["drivers/place/spec_helper.cr", "..."] +``` + + +### GET /build/repositories + +Returns the list of 3rd party repositories + +```json + +["private_drivers", "..."] +``` + + +### GET /build/repository_commits + +Returns the list of available commits at the repository level + +* `repository=folder_name` (optional) if you wish to specify a third party repository +* `count=50` (optional) if you want more or less commits + +```json + +{ + "commit": "01519d6", + "date": "2019-06-02T23:59:22+10:00", + "author": "Stephen von Takach", + "subject": "implement websocket spec runner" +} +``` + + +### GET /build/{{escaped driver path}} + +Returns the list of compiled versions of the specified file are available + +```json + +["private_drivers_cr_01519d6", "..."] +``` + + +### GET /build/{{escaped driver path}}/commits + +Returns the list of available commits for the current driver + +* `repository=folder_name` (optional) if you wish to specify a third party repository +* `count=50` (optional) if you want more or less commits + +```json + +{ + "commit": "01519d6", + "date": "2019-06-02T23:59:22+10:00", + "author": "Stephen von Takach", + "subject": "implement websocket spec runner" +} +``` + + +### POST /build + +compiles a driver + +* `driver=drivers/path.cr` (required) the path to the driver +* `commit=01519d6` (optional) defaults to head + + +### DELETE /build/{{escaped driver path}} + +deletes compiled versions of a driver + +* `repository=folder_name` (optional) if you wish to specify a third party repository +* `commit=01519d6` (optional) deletes all versions of a driver if not specified + + +## GET /test + +Lists the available specs + +```json + +["drivers/place/spec_helper_spec.cr", "..."] +``` + + +### GET /test/{{escaped spec path}}/commits + +Returns the list of available commits for the specified spec + +* `repository=folder_name` (optional) if you wish to specify a third party repository +* `count=50` (optional) if you want more or less commits + +```json + +{ + "commit": "01519d6", + "date": "2019-06-02T23:59:22+10:00", + "author": "Stephen von Takach", + "subject": "implement websocket spec runner" +} +``` + + +### POST /test + +Compiles and runs a spec and returns the output + +* `repository=folder_name` (optional) if you wish to specify a third party repository +* `driver=drivers/path/to/file.cr` (required) the driver you want to test +* `spec=drivers/path/to/file_spec.cr` (required) the spec you want to run on the driver +* `commit=01519d6` (optional) the commit you would like the driver to be running at +* `spec_commit=01519d6` (optional) the commit you would like the spec to be running at +* `force=true` (optional) forces a re-compilation of the driver and spec +* `debug=true` (optional) compiles the files with debugging symbols + +```text +Launching spec runner +Launching driver: /Users/steve/Documents/projects/placeos/drivers/bin/drivers/drivers_place_private_helper_cr_4f6e0cd +... starting driver IO services +... starting module +... waiting for module +... module connected +... enabling debug output +... starting spec +... spec complete +... terminating driver gracefully +Driver terminated with: 0 + + +Finished in 15.65 milliseconds +0 examples, 0 failures, 0 errors, 0 pending + +spec runner exited with 0 +``` + + +### WebSocket /test/run_spec + +Same requirements as `POST /test` above however it streams the response diff --git a/docs/runtime-debugging.md b/docs/runtime-debugging.md new file mode 100644 index 00000000000..9fe51783978 --- /dev/null +++ b/docs/runtime-debugging.md @@ -0,0 +1,195 @@ +# Runtime Debugging + +This is supported via VS Code on OSX or Linux platforms. +It might be possible to do remote debugging on Windows in conjunction with the Linux Layer. + +* Requires [VS Code](https://code.visualstudio.com/) + * install [Crystal Lang](https://marketplace.visualstudio.com/items?itemName=faustinoaq.crystal-lang) extension + * install [Native Debug](https://marketplace.visualstudio.com/items?itemName=webfreak.debug) extension +* Requires [GDB](https://www.gnu.org/software/gdb/) + * On OSX install using [Homebrew](https://brew.sh/) + * Then code sign the executable: https://sourceware.org/gdb/wiki/PermissionsDarwin + * The `gdb-entitlement.xml` file is in this folder + * When creating the signing certificate follow [this guide](https://apple.stackexchange.com/questions/309017/unknown-error-2-147-414-007-on-creating-certificate-with-certificate-assist) + +This should also work with [LLDB](https://lldb.llvm.org/) on OSX however [has issues](https://github.com/crystal-lang/crystal/issues/4457). + + +## Debug on VSCode + +By convention the project directory name is the same as your application name, if you have changed it, please update `${workspaceFolderBasename}` with the name configured inside `shards.yml` + +### 1. `tasks.json` configuration to compile a crystal project + +```javascript +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Compile", + "command": "shards build --debug ${workspaceFolderBasename}", + "type": "shell" + } + ] +} +``` + +### 2. `launch.json` configuration to debug a binary + +#### Using GDB + +```javascript +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "gdb", + "request": "launch", + "target": "./bin/${workspaceFolderBasename}", + "cwd": "${workspaceRoot}", + "preLaunchTask": "Compile" + } + ] +} +``` + +#### Using LLDB + +```javascript +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "lldb-mi", + "request": "launch", + "target": "./bin/${workspaceFolderBasename}", + "cwd": "${workspaceRoot}", + "preLaunchTask": "Compile" + } + ] +} +``` + +### 3. Then hit the DEBUG green play button + +![debugging](https://i.imgur.com/GsGT1h0.png) + +## Tips and Tricks for debugging Crystal applications + +### 1. Use debugger keyword + +Instead of putting breakpoints using commands inside GDB or LLDB you can try to set a breakpoint using `debugger` keyword. + +```ruby +i = 0 +while i < 3 + i += 1 + debugger # => breakpoint +end +``` + +### 2. Avoid breakpoints inside blocks + +Currently, Crystal lacks support for debugging inside of blocks. If you put a breakpoint inside a block, it will be ignored. + +As a workaround, use `pp` to pretty print objects inside of blocks. + +```ruby +3.times do |i| + pp i +end +# i => 1 +# i => 2 +# i => 3 +``` + +### 3. Try `@[NoInline]` to debug arguments data + +Sometimes crystal will optimize argument data, so the debugger will show `` instead of the arguments. To avoid this behavior use the `@[NoInline]` attribute before your function implementation. + +```ruby +@[NoInline] +def foo(bar) + debugger +end +``` + +### 4. Printing strings objects \(GDB\) + +To print string objects in the debugger: + +First, setup the debugger with the `debugger` statement: + +```ruby +foo = "Hello World!" +debugger +``` + +Then use `print` in the debugging console. + +```bash +(gdb) print &foo.c +$1 = (UInt8 *) 0x10008e6c4 "Hello World!" +``` + +Or add `&foo.c` using a new variable entry on watch section in VSCode debugger + +![Using VSCode GUI](https://i.imgur.com/EpQinL7.png) + +### 5. Printing array variables + +To print array items in the debugger: + +First, setup the debugger with the `debugger` statement: + +```ruby +foo = ["item 0", "item 1", "item 2"] +debugger +``` + +Then use `print` in the debugging console: + +```bash +(gdb) print &foo.buffer[0].c +$19 = (UInt8 *) 0x10008e7f4 "item 0" +``` + +Change the buffer index for each item you want to print. + +### 6. Printing instance variables + +For printing `@foo` var in this code: + +```ruby +class Bar + @foo = 0 + def baz + debugger + end +end + +Bar.new +``` + +You can use `self.foo` in the debugger terminal or VSCode GUI. + +### 7. Print hidden objects + +Some objects do not show at all. You can unhide them using the `.to_s` method and a temporary debugging variable, like this: + +```ruby +def bar(hello) + "#{hello} World!" +end + +def foo(hello) + bar_hello_to_s = bar(hello).to_s + debugger +end + +foo("Hello") +``` + +This trick allows showing the `bar_hello_to_s` variable inside the debugger tool. diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 00000000000..7c6cd358d15 --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,37 @@ +# Setup + +This allows you to build and test drivers without installing or running the complete PlaceOS service. + +1. clone the drivers repository: `git clone https://github.com/placeos/drivers drivers` +2. clone private repositories here: `mkdir ./drivers/repositories` + + +## OSX + +Install [Homebrew](https://brew.sh/) to install dependencies + +* Install [Crystal Lang](https://crystal-lang.org/reference/installation/): `brew install crystal` +* Install libssh2: `brew install libssh2` +* Install redis: `brew install redis` + +Ensure the following lines are in your `.bashrc` file + +```shell +export PATH="/usr/local/opt/llvm/bin:$PATH" +export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/opt/openssl/lib/pkgconfig +``` + + +## Running Specs + +1. Ensure redis is running: `redis-server` +2. Install dependencies: `cd drivers; shards update` +3. Launch application: `crystal run ./src/app.cr` +4. Browse to: http://localhost:3000/ + +Now you can build drivers and run specs: + +* Build a drvier or spec: `curl -X POST "http://localhost:3000/build?driver=drivers/helvar/net.cr"` +* Run a spec: `curl -X POST "http://localhost:3000/test?driver=drivers/lutron/lighting.cr&spec=drivers/lutron/lighting_spec.cr"` + +To build or test against drivers in private repositories include the repository param: `repository=private_drivers` diff --git a/docs/writing-a-driver.md b/docs/writing-a-driver.md new file mode 100644 index 00000000000..f40440c903b --- /dev/null +++ b/docs/writing-a-driver.md @@ -0,0 +1,455 @@ +# How to write a driver + +There are three kind of drivers + +* Streaming IO (TCP, SSH, UDP, Multicast ect) +* HTTP Client +* Logic + +From a driver structure standpoint there is no difference between these types. + +* The same driver can be used over a TCP, UDP or SSH transport. +* All drivers support HTTP methods if a URI endpoint is defined. +* If a driver is associated with a System then it has access to logic helpers + +However typically a driver will only implement one of these interfaces. + + +## Concepts + +Backing a driver is few different pieces that make it function. + +* Queue +* Transport +* Subscriptions +* Scheduler +* Settings +* Logger +* Metadata +* Security +* Interfaces + + +### Queue + +The queue is a list of potentially asynchronous tasks that should be performed in a sequence. + +* Each task has a priority (defaults to `50`) - higher priority tasks run first +* Tasks can be named. If a new task is added with the same name it replaces the existing task. +* Tasks have a timeout (defaults to `5.seconds`) +* Tasks can be retried (defaults to `3` before failing) + +Tasks have a callback that is used to run the task + +```crystal + +# => you can set queue defaults globally + +# set a delay between the current task completing and the next task +queue.delay = 1.second +queue.retries = 5 + +queue(priority: 20, timeout: 1.second) do |task| + # perform action here + + # signal result + task.success("optional success value") + task.abort("optional failure message") + task.retry + + # Give me more time to complete the task + task.reset_timers +end + +``` + +In most cases you won't need to use the queue explicitly however it is good to understand that it is there and how it functions. + + +### Transport + +The transport loaded is defined by settings in the database. + +#### Streaming IO + +You should always tokenise your streams. +This can be handled automatically by the [built in tokeniser](https://github.com/spider-gazelle/tokenizer) + +```crystal + +def on_load + transport.tokenizer = Tokenizer.new("\r\n") +end + +``` + +There are a few ways to use streaming IO methods: + +1. send and receive + +```crystal + +def perform_action + # You call send with some data. + # you can also optionally pass some queue options to the function + send("message data", priority: 30, name: "generic-message") +end + +# A common received function for handling responses +def received(data, task) + # data is always `Bytes` + # task is always `PlaceOS::Driver::Task?` (i.e. could be nil if no active task) + + # convert data into the appropriate format + data = String.new(data) + + # decide if the request was a success or not + # you can pass any value that is JSON serialisable to success + # (if it can't be serialised then nil is sent) + task.try &.success(data) +end + +``` + +2. send and callback + +```crystal + +def perform_action + request = "build request" + + send(request, priority: 30, name: "generic-message") do |data, task| + data = String.new(data) + + # process response here (might need to know the request context) + + task.try &.success(data) + end +end + +``` + +3. send immediately (no queuing) + +```crystal + +def perform_action_now! + transport.send("no queue") +end + +``` + + +#### HTTP Client + +All drivers have built in methods for performing HTTP requests. + +* For streaming IO devices this defaults to `http://device.ip.address` or `https` if the transport is using TLS / SSH. +* All devices can provide a custom HTTP base URI. + +There are methods for all the typical HTTP verbs: get, post, put, patch, delete + +```crystal + +def perform_action + basic_auth = "Basic #{Base64.strict_encode("#{@username}:#{@password}")}" + + response = post("/v1/message/path", body: { + messages: numbers, + }.to_json, headers: { + "Authorization" => basic_auth, + "Content-Type" => "application/json", + "Accept" => "application/json", + }, params: { + "key" => "value" + }) + + raise "request failed with #{response.status_code}" unless (200...300).include?(data.status_code) +end + +``` + + +#### Special SSH methods + +SSH connections will attempt to open a shell to the remote device however sometimes you may be able to execute operations independently. + +```crystal + +def perform_action + # if the application launched supports input you can use the bidirectional IO + # to communicate with the app + io = exec("command") +end + +``` + + +#### Logic drivers + +The main difference between logic drivers and other transports is that a logic module is directly associated with a System and cannot be shared. (all other drivers can appear in multiple systems) + +* You can access remote modules in the system via the `system` helper + +```crystal + +# Get a system proxy +sys = system +sys.name #=> "Name of system" +sys.email #=> "resource@email.address" +sys.capacity #=> 12 +sys.bookable #=> true +sys.id #=> "sys-tem~id" +sys.modules #=> ["Array", "Of", "Unique", "Module", "Names", "In", "System"] +sys.count("Module") #=> 3 +sys.implementing(PlaceOS::Driver::Interface::Powerable) #=> ["Camera", "Display"] + +# Look at status on a remote module +system[:Display][:power] #=> true + +# Access a different module index +system[:Display_2][:power] +system.get(:Display, 2)[:power] + +# Access all modules of a type +system.all(:Display) + +# Check if a module exists +system.exists?(:Display) #=> true +system.exists?(:Display_2) #=> false + +``` + +you can bind to state in remote modules + +```crystal + +bind Display_1, :power, :power_changed + +private def power_changed(subscription, new_value) + logger.debug new_value +end + + +# you can also bind to internal state (available in all drivers) +bind :power, :power_changed + +``` + +It's also possible to create shortcuts to other modules. +This is powerful as these shortcuts are exposed as metadata - allowing backoffice to perform system verification. + +For example, consider the following video conference system: + +```crystal + +# It requires at least one camera that can move and be turned on and off +accessor camera : Array(Camera), implementing: [Powerable, Moveable] + +# Optional room blinds that can be opened and closed +accessor blinds : Array(Blind)?, implementing: [Switchable] + +# A single display is required with an optional screen (maybe it's a projector) +accessor main_display : Display_1, implementing: Powerable +accessor screen : Screen? + +``` + + +### Subscriptions + +You can dynamically bind to state of interest in remote modules + +```crystal + +# subscription is returned and provided with every status update in the callback +subscription = system.subscribe(:Display_1, :power) do |subscription, new_value| + # values are always raw JSON strings + JSON.parse(new_value) +end + +# Local subscriptions +subscription = subscribe(:state) do |subscription, new_value| + # values are always raw JSON strings + JSON.parse(new_value) +end + +# Clearing all subscriptions +subscriptions.clear + +``` + +Similarly to subscriptions, there are channels that can be setup for broadcasting +arbitrary data that might not need be exposed as state. + +```crystal + +subscription = monitor(:channel_name) do |subscription, new_value| + # values are always raw JSON strings + JSON.parse(new_value) +end + +# Publish something on the channel to all listeners +publish(:channel_name, "some event") + +``` + + +### Scheduler + +There is a built in scheduler: https://github.com/spider-gazelle/tasker + +```crystal + +def connected + schedule.every(40.seconds) { poll_device } + schedule.in(200.milliseconds) { send_hello } +end + +def disconnected + schedule.clear +end + +``` + + +### Settings + +Settings are stored as JSON and then extracted as required, serialising to the specified type +There are two types: + +* Required settings - raise an error if the setting is unavailable +* Optional settings - return `nil` if the setting is unavailable + +NOTE:: All settings will raise an error if they exist but fail to serialise (as they are not formatted correctly etc) + +```crystal + +# Required settings +def on_update + @display_id = setting(Int32, :display_id) + + # Can extract deeply nested values + # i.e. {input: {list: ["HDMI", "VGA"] }} + @primary_input = setting(InputEnum, :input, :list, 0) +end + +# Optional settings (you can optionally provide a default) +def on_update + @display_id = setting?(Int32, :display_id) || 1 + @primary_input = setting?(InputEnum, :input, :list, 0) || InputEnum::HDMI +end + +``` + + +### Logger + +There is a logger available: https://crystal-lang.org/api/latest/Logger.html + +* Warning and above are written to disk. +* debug and info are only available when there is an open debugging session. + +```crystal + +logger.warn "error unknown response" + +# You should typically use the block form for debug and info messages +# this only performs string interpolations if a debugging session is attached +logger.debug { "function called with #{value}" } + +``` + +The logging format has been pre-configured so all logging from Engine is uniform and simple to parse + + +### Metadata + +Metadata is used by various components to simplify configuration. + +* `generic_name` => the name that should be used in a system to access the module +* `descriptive_name` => the manufacturers name for the device +* `description` => notes or any other descriptive information you wish to add +* `tcp_port` => TCP port the TCP transport should connect to +* `udp_port` => UDP port the UDP transport should connect to +* `uri_base` => The HTTP base for any HTTP requests +* `default_settings` => Defaults or example settings that should be used to configure a module + + +```crystal + +class MyDevice < PlaceOS::Driver + generic_name :Driver + descriptive_name "Driver model Test" + description "This is the driver used for testing" + tcp_port 22 + default_settings({ + name: "Room 123", + username: "steve", + password: "$encrypt", + complex: { + crazy_deep: 1223, + }, + }) + + # ... + +end + +``` + + +### Security + +By default all public functions are exposed for execution. +However you can limit who is able to execute sensitive functions. + +```crystal + +@[Security(Level::Administrator)] +def perform_task(name : String | Int32) + queue &.success("hello #{name}") +end + +``` + +Use the `Security` annotation to define the access level of the function. +The options are: + +* Administrator `Level::Administrator` +* Support `Level::Support` + + +### Interfaces + +Drivers can expose any methods that make sense for the device, service or logic they encapsulate. +Across these there are often core sets of similar functionality. +Interfaces provide a standard way of implementing and interacting with this. + +Thier usage is optional, but highly encouraged as it both improves modularity and reduces complexity in driver implementations. + +A full list of interfaces is [available in the driver framework](https://github.com/PlaceOS/driver/tree/master/src/placeos-driver/interface). This will expand over time to cover common, repeated patterns as they emerge. + +#### Implementing an Interface + +Each interface is a module containing abstract methods, types and functionality built from these. + +First include the module within the driver body. +```crystal +include Interface::Powerable +``` +You will then need to provide implementations of the abstract methods. +The compiler will guide you in this. + +Some interfaces will also provide default implementation for other methods. +These may be overridden if the device or service provides a more efficient way to directly execute the desired behaviour. +To keep compatibility, overridden methods must maintain feature and functional parity with the original implementation. + +#### Using an Interface + +Drivers that provide an Interface can be discovered using the `system.implementing` method from any logic module. +This will return a list of all drivers in the system which implement the Interface. + +Similarly, the `accessor` macro provides a way to declare a dependency on a sibling driver that provides specific functionality. + +For more infomaration on these and for usage examples, see [logic drivers](#logic-drivers). diff --git a/docs/writing-a-spec.md b/docs/writing-a-spec.md new file mode 100644 index 00000000000..d470973feb7 --- /dev/null +++ b/docs/writing-a-spec.md @@ -0,0 +1,231 @@ +# How to write a spec + +There are three kind of drivers + +* Streaming IO (TCP, SSH, UDP, Multicast, ect) +* HTTP Client +* Logic + +From a driver code structure standpoint there is no difference between these types. + +* The same driver can be used over a TCP, UDP or SSH transport. +* All drivers support HTTP methods if a URI endpoint is defined. +* If a driver is associated with a System then it has access to logic helpers + +During a test, the loaded module is loaded with a TCP transport, HTTP enabled and logic module capabilities. +This allows for testing the full capabilities of any driver. + +The driver is launched as it would be in production. + + +## Expectations + +Specs have access to Crystal lang spec expectations. This allows you to confirm expectations. +https://crystal-lang.org/api/latest/Spec/Expectations.html + +```crystal + +variable = 34 +variable.should eq(34) + +``` + +There is a good overview on how to use expectations here: https://crystal-lang.org/reference/guides/testing.html + + +### Status + +Expectations are primarily there to test the state of the module. + +* You can access state via the status helper: `status[:state_name]` +* Then you can check it an expected value: `status[:state_name].should eq(14)` + + +## Testing Streaming IO + +The following functions are available for testing streaming IO: + +* `transmit(data)` -> transmits the object to the module over the streaming IO interface +* `responds(data)` -> alias for `transmit` +* `should_send(data, timeout = 500.milliseconds)` -> expects the module to respond with the data provided +* `expect_send(timeout = 500.milliseconds)` -> returns the next `Bytes` sent by the module (useful if the data sent is not deterministic, i.e. has a time stamp) + +A common test case is to ensure that module state updates as expected after transmitting some data to it: + +```crystal + +# transmit some data +transmit(">V:2,C:11,G:2001,B:1,S:1,F:100#") + +# check that the state updated as expected +status[:area2001].should eq(1) + +``` + + +## Testing HTTP requests + +The test suite emulates a HTTP server so you can inspect HTTP requests and send canned responses to the module. + +```crystal + +expect_http_request do |request, response| + io = request.body + if io + data = io.gets_to_end + request = JSON.parse(data) + if request["message"] == "hello steve" + response.status_code = 202 + else + response.status_code = 401 + end + else + raise "expected request to include dialing details #{request.inspect}" + end +end + +# check that the state updated as expected +status[:area2001].should eq(1) + +``` + +Use `expect_http_request` to access an expected request coming from the module. + +* when the block completes, the response is sent to the module +* you can see `request` object details here: https://crystal-lang.org/api/latest/HTTP/Request.html +* you can see `response` object details here: https://crystal-lang.org/api/latest/HTTP/Server/Response.html + + +## Executing functions + +This allows you to request actions be performed in the module via the standard public interface. + +* `exec(:function_name, argument_name: argument_value)` -> `response` a response future (async return value) +* You should send and `responds(data)` before inspecting the `response.get` + +```crystal + +# Execute a command +response = exec(:scene?, area: 1) + +# Check that the command causes the module to send some data +should_send("?AREA,1,6\r\n") +# Respond to that command +responds("~AREA,1,6,2\r\n") + +# Check if the functions return value is expected +response.get.should eq(2) +# Check if the module state is correct +status[:area1].should eq(2) + +``` + + +## Testing Logic + +Logic modules typically expect a system to contain some drivers which the logic modules interacts with. + +```crystal + +# define mock versions of the drivers it will interact with + +class Display < DriverSpecs::MockDriver + include Interface::Powerable + include Interface::Muteable + + enum Inputs + HDMI + HDMI2 + VGA + VGA2 + Miracast + DVI + DisplayPort + HDBaseT + Composite + end + + include PlaceOS::Driver::Interface::InputSelection(Inputs) + + # Configure initial state in on_load + def on_load + self[:power] = false + self[:input] = Inputs::HDMI + end + + # implement the abstract methods required by the interfaces + def power(state : Bool) + self[:power] = state + end + + def switch_to(input : Inputs) + mute(false) + self[:input] = input + end + + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + self[:mute] = state + self[:mute0] = state + end +end + +``` + +Then you can define the system configuration, +you can also change the system configuration throughout your spec to test different configurations. + +```crystal + +DriverSpecs.mock_driver "Place::LogicExample" do + + # Where `{Display, Display}` is referencing the `MockDriver` class defined above + # and `Display:` is the friendly name + # so this system would have `Display_1`, `Display_2`, `Switcher_1` + system({ + Display: {Display, Display}, + Switcher: {Switcher}, + }) + + # ... +end + +``` + +Along with the physical system configuration you can test different setting configurations. +Settings can also be changed throughout the life cycle of your spec. + +```crystal + +DriverSpecs.mock_driver "Place::LogicExample" do + + settings({ + name: "Meeting Room 1", + map_id: "1.03" + }) + +end + +``` + +An action you perform on your driver might be expected to update state in the mock devices. +You can access this state via the `system` helper + +```crystal + +DriverSpecs.mock_driver "Place::LogicExample" do + + # execute a function in your logic module + exec(:power, true) + + # Check that the expected state has updated in you mock device + system(:Display_1)[:power].should eq(true) + +end + +``` + +All status queried in this manner is returned as a `JSON::Any` object diff --git a/drivers/bose/control_space_serial.cr b/drivers/bose/control_space_serial.cr new file mode 100644 index 00000000000..a2721f7b0b4 --- /dev/null +++ b/drivers/bose/control_space_serial.cr @@ -0,0 +1,58 @@ +module Bose; end + +# Documentation: https://aca.im/driver_docs/Bose/Bose-ControlSpace-SerialProtocol-v5.pdf + +class Bose::ControlSpaceSerial < PlaceOS::Driver + # Discovery Information + tcp_port 10055 + descriptive_name "Bose ControlSpace Serial Protocol" + generic_name :Mixer + + def on_load + # 0x0D ( carriage return \r) + transport.tokenizer = Tokenizer.new(Bytes[0x0D]) + on_update + end + + def on_update + end + + def connected + schedule.every(60.seconds) do + logger.debug { "-- maintaining connection" } + do_send "GS", priority: 99 + end + end + + def disconnected + schedule.clear + end + + private def do_send(data, **options) + logger.debug { "requesting: #{data}" } + send "#{data}\x0D", **options + end + + def set_parameter_group(id : UInt8) + do_send("SS #{id.to_s(16).upcase}", wait: false, name: "set_pgroup").get + self[:parameter_group] = id + end + + def get_parameter_group + do_send "GS" + end + + def received(data, task) + # Ignore the framing bytes + data = String.new(data).rchop + logger.debug { "ControlSpace sent: #{data}" } + + parts = data.split(" ") + case parts[0] + when "S" + self[:parameter_group] = parts[1].to_i(16) + end + + task.try &.success + end +end diff --git a/drivers/bose/control_space_serial_spec.cr b/drivers/bose/control_space_serial_spec.cr new file mode 100644 index 00000000000..f5685cafbd0 --- /dev/null +++ b/drivers/bose/control_space_serial_spec.cr @@ -0,0 +1,10 @@ +DriverSpecs.mock_driver "Bose::ControlSpaceSerial" do + exec(:set_parameter_group, 12) + should_send("SS C\r") + status[:parameter_group].should eq(12) + + exec(:get_parameter_group) + should_send("GS\r") + responds("S FF\r") + status[:parameter_group].should eq(255) +end diff --git a/drivers/cisco/dna_spaces.cr b/drivers/cisco/dna_spaces.cr new file mode 100644 index 00000000000..c5d221038d1 --- /dev/null +++ b/drivers/cisco/dna_spaces.cr @@ -0,0 +1,631 @@ +module Cisco; end + +require "set" +require "jwt" +require "s2_cells" +require "simple_retry" +require "placeos-driver/interface/locatable" + +class Cisco::DNASpaces < PlaceOS::Driver + include Interface::Locatable + + # Discovery Information + descriptive_name "Cisco DNA Spaces" + generic_name :DNA_Spaces + uri_base "https://partners.dnaspaces.io" + + default_settings({ + dna_spaces_activation_key: "provide this and the API / tenant ids will be generated automatically", + dna_spaces_api_key: "X-API-KEY", + tenant_id: "sfdsfsdgg", + + # Time before a user location is considered probably too old (in minutes) + max_location_age: 10, + + floorplan_mappings: { + location_a4cb0: { + "level_name" => "optional name", + "building" => "zone-GAsXV0nc", + "level" => "zone-GAsmleH", + "offset_x" => 12.4, + "offset_y" => 5.2, + "map_width" => 50.3, + "map_height" => 100.9, + }, + }, + + debug_stream: false, + }) + + @streaming = false + @last_received = 0_i64 + @stream_active = false + + def on_load + on_update + if !@api_key.empty? + @streaming = true + spawn(same_thread: true) { start_streaming_events } + end + end + + def on_unload + @terminated = true + @channel.close + @stream_active = false + update_monitoring_status(running: false) + end + + @activation_token : String = "" + @api_key : String = "" + @tenant_id : String = "" + @terminated : Bool = false + @channel : Channel(String) = Channel(String).new + @max_location_age : Time::Span = 10.minutes + @s2_level : Int32 = 21 + @floorplan_mappings : Hash(String, Hash(String, String | Float64)) = Hash(String, Hash(String, String | Float64)).new + @debug_stream : Bool = false + @events_received : UInt64 = 0_u64 + + def on_update + @max_location_age = (setting?(UInt32, :max_location_age) || 10).minutes + @s2_level = setting?(Int32, :s2_level) || 21 + @floorplan_mappings = setting?(Hash(String, Hash(String, String | Float64)), :floorplan_mappings) || @floorplan_mappings + @debug_stream = setting?(Bool, :debug_stream) || false + + schedule.clear + schedule.every(30.minutes) { cleanup_caches } + schedule.every(5.minutes) { update_monitoring_status } + schedule.in(5.seconds) { update_monitoring_status } + + @activation_token = setting?(String, :dna_spaces_activation_key) || "" + if @activation_token.empty? + @api_key = setting(String, :dna_spaces_api_key) + @tenant_id = setting(String, :tenant_id) + else + @api_key = setting?(String, :dna_spaces_api_key) || "" + @tenant_id = setting?(String, :tenant_id) || "" + + # Activate the API key using the activation_token + schedule.in(5.seconds) { activate } if @api_key.empty? + end + + if !@streaming && !@api_key.empty? + @streaming = true + spawn(same_thread: true) { start_streaming_events } + end + end + + @[Security(Level::Support)] + def activate + return if @activation_token.empty? + + response = get("/client/v1/partner/partnerPublicKey/") + raise "failed to obtain partner public key, code #{response.status_code}" unless response.success? + + logger.debug { "public key requested: #{response.body}" } + + payload = NamedTuple( + status: Bool, + message: String, + data: Array(ActivactionPublicKey)).from_json(response.body.not_nil!) + + raise "unexpected failure obtaining partner public key: #{payload[:message]}" unless payload[:status] + + public_key = payload[:data][0].public_key + payload, header = JWT.decode(@activation_token, public_key, JWT::Algorithm::RS256) + app_id = payload["appId"].as_s + ref_id = payload["activationRefId"].as_s + tenant_id = payload["tenantId"].as_i64.to_s + + response = post("/client/v1/partner/activateOnPremiseApp", headers: { + "Content-Type" => "application/json", + "Authorization" => "Bearer #{@activation_token}", + }, body: { + appId: app_id, + activationRefId: ref_id, + }.to_json) + raise "failed to obtain API key, code #{response.status_code}\n#{response.body}" unless response.success? + + logger.debug { "application activated: #{response.body}" } + + payload = NamedTuple( + status: Bool, + message: String, + data: NamedTuple(apiKey: String)).from_json(response.body.not_nil!) + + raise "unexpected failure obtaining API key: #{payload[:message]}" unless payload[:status] + + api_key = payload[:data][:apiKey] + logger.debug { "saving API key: #{tenant_id}, #{api_key}" } + + define_setting(:tenant_id, tenant_id) + define_setting(:dna_spaces_api_key, api_key) + define_setting(:dna_spaces_activation_key, "") + + logger.debug { "settings saved! Starting stream" } + @api_key = api_key + @tenant_id = tenant_id + if !@streaming + @streaming = true + spawn(same_thread: true) { start_streaming_events } + end + end + + class LocationInfo + include JSON::Serializable + + getter location : Location + + @[JSON::Field(key: "locationDetails")] + getter details : LocationDetails + end + + def get_location_info(location_id : String) + response = get("/api/partners/v1/locations/#{location_id}?partnerTenantId=#{@tenant_id}", headers: { + "X-API-KEY" => @api_key, + }) + + raise "failed to obtain location id #{location_id}, code #{response.status_code}" unless response.success? + LocationInfo.from_json(response.body.not_nil!) + end + + @description_lock : Mutex = Mutex.new + @location_descriptions : Hash(String, String) = {} of String => String + + def seen_locations + @description_lock.synchronize { @location_descriptions.dup } + end + + # MAC Address => Location (including user) + @locations : Hash(String, DeviceLocationUpdate) = {} of String => DeviceLocationUpdate + @loc_lock : Mutex = Mutex.new + + def locations + @loc_lock.synchronize { yield @locations } + end + + @user_lookup : Hash(String, Set(String)) = {} of String => Set(String) + @user_loc : Mutex = Mutex.new + + def user_lookup + @user_loc.synchronize { yield @user_lookup } + end + + def user_lookup(user_id : String) + formatted_user = format_username(user_id) + user_lookup { |lookup| lookup[formatted_user]? } + end + + def locate_mac(address : String) + formatted_address = format_mac(address) + locations { |locs| locs[formatted_address]? } + end + + @[Security(PlaceOS::Driver::Level::Support)] + def inspect_state + logger.debug { + "MAC Locations: #{locations &.keys}" + } + {tracking: locations &.size, events_received: @events_received} + end + + @map_details : Hash(String, Dimension) = {} of String => Dimension + @map_lock : Mutex = Mutex.new + + def get_map_details(map_id : String) + map = @map_lock.synchronize { @map_details[map_id]? } + if !map + response = get("/api/partners/v1/maps/#{map_id}?partnerTenantId=#{@tenant_id}", headers: { + "X-API-KEY" => @api_key, + }) + if !response.success? + message = "failed to obtain map id #{map_id}, code #{response.status_code}" + logger.warn { message } + return nil + end + map = MapInfo.from_json(response.body.not_nil!).dimension + @map_lock.synchronize { @map_details[map_id] = map } + end + map + end + + @[Security(PlaceOS::Driver::Level::Support)] + def cleanup_caches : Nil + logger.debug { "removing location data that is over 30 minutes old" } + + old = 30.minutes.ago.to_unix + remove_keys = [] of String + locations do |locs| + locs.each { |mac, location| remove_keys << mac if location.last_seen < old } + remove_keys.each { |mac| locs.delete(mac) } + end + + logger.debug { "removed #{remove_keys.size} MACs" } + nil + end + + # we want to stream events until driver is terminated + protected def start_streaming_events + @streaming = true + SimpleRetry.try_to( + base_interval: 10.milliseconds, + max_interval: 5.seconds + ) { stream_events unless @terminated } + ensure + @streaming = false + end + + # as sometimes the map id is missing, but in the same location + # location id => map id + @location_id_maps = {} of String => String + + # Processes events as they come in, forces a disconnect if no events are sent + # for a period of time as the remote should be sending them periodically + protected def process_events(client) + loop do + select + when data = @channel.receive + logger.debug { "received push #{data}" } if @debug_stream + @events_received = @events_received &+ 1_u64 + begin + event = Cisco::DNASpaces::Events.from_json(data) + payload = event.payload + case payload + when DeviceExit + device_mac = format_mac(payload.device.mac_address) + locations &.delete(device_mac) + when DeviceEntry + # This is used entirely for + @description_lock.synchronize { payload.location.descriptions(@location_descriptions) } + when DeviceLocationUpdate + # Keep track of device location + device_mac = format_mac(payload.device.mac_address) + existing = nil + + # ignore locations where we don't have enough details to put the device on a map + if payload.map_id.presence + @location_id_maps[payload.location.location_id] = payload.map_id + else + found = false + payload.location_mappings.values.each do |loc_id| + if map_id = @location_id_maps[loc_id]? + payload.map_id = map_id + found = true + break + end + end + + if !found + logger.debug { "ignoring device #{device_mac} location as map_id is empty, location id #{payload.location.location_id}, visit #{payload.visit_id}" } + next + end + end + + payload.last_seen = payload.last_seen // 1000 + + locations do |loc| + existing = loc[device_mac]? + loc[device_mac] = payload + end + + # Maintain user lookup + if payload.raw_user_id.presence + user_id = format_username(payload.raw_user_id) + + if existing && payload.raw_user_id != existing.raw_user_id + old_user_id = format_username(existing.raw_user_id) + + user_lookup do |lookup| + lookup[old_user_id]?.try &.delete(device_mac) + devices = lookup[old_user_id]? || Set(String).new + devices.delete(device_mac) + lookup.delete(old_user_id) if devices.empty? + + devices = lookup[user_id]? || Set(String).new + devices << device_mac + lookup[user_id] = devices + end + else + user_lookup do |lookup| + devices = lookup[user_id]? || Set(String).new + devices << device_mac + lookup[user_id] = devices + end + end + end + + # payload.location_mappings => { "ZONE" => loc_id, "FLOOR" => loc_id, "BUILDING" => loc_id, "CAMPUS" => loc_id } + else + logger.debug { "ignoring event: #{payload ? payload.class : event.class}" } + end + rescue error + logger.error(exception: error) { "parsing DNA Spaces event: #{data}" } + end + when timeout(20.seconds) + logger.debug { "no events received for 20 seconds, expected heartbeat at 15 seconds" } + @channel.close + break + end + end + ensure + client.close + end + + protected def stream_events + client = HTTP::Client.new URI.parse(config.uri.not_nil!) + client.get("/api/partners/v1/firehose/events", HTTP::Headers{ + "X-API-KEY" => @api_key, + }) do |response| + if !response.success? + @stream_active = false + logger.warn { "failed to connect to firehose api #{response.status_code}" } + raise "failed to connect to firehose api #{response.status_code}" + end + + @stream_active = true + + # We use a channel for event processing so we can make use of timeouts + @channel = Channel(String).new + spawn(same_thread: true) { process_events(client) } + + begin + loop do + if response.body_io.closed? + @channel.close + break + end + + if data = response.body_io.gets + @last_received = Time.utc.to_unix_ms + @channel.send data + else + @channel.close + break + end + end + rescue IO::Error + @channel.close + end + end + + # Trigger the retry behaviour + @stream_active = false + raise "stream closed" + end + + # ============================= + # Locatable interface + # ============================= + def locate_user(email : String? = nil, username : String? = nil) + if macs = user_lookup(username.presence || email.presence.not_nil!) + location_max_age = @max_location_age.ago.to_unix + + macs.compact_map { |mac| + if location = locate_mac(mac) + if location.last_seen > location_max_age + # we update the mac_address to a formatted version + location.device.mac_address = mac + location + end + end + }.sort { |a, b| + b.last_seen <=> a.last_seen + }.map { |location| + lat = location.latitude + lon = location.longitude + + loc = { + "location" => "wireless", + "coordinates_from" => "top-left", + "x" => location.x_pos, + "y" => location.y_pos, + "lon" => lon, + "lat" => lat, + "s2_cell_id" => S2Cells::LatLon.new(lat, lon).to_token(@s2_level), + "mac" => location.device.mac_address, + "variance" => location.unc, + "last_seen" => location.last_seen, + "dna_floor_id" => location.map_id, + "ssid" => location.ssid, + "manufacturer" => location.device.manufacturer, + "os" => location.device.os, + } + + map_width = 0.0 + map_height = 0.0 + offset_x = 0.0 + offset_y = 0.0 + + # Add our zone IDs to the response + location.location_mappings.each do |tag, location_id| + if level_data = @floorplan_mappings[location_id]? + level_data.each do |key, value| + case key + when "offset_x" + offset_x = value.as(Float64) + loc["x"] = location.x_pos - offset_x + when "offset_y" + offset_y = value.as(Float64) + loc["y"] = location.y_pos - offset_y + when "map_width" + map_width = value.as(Float64) + when "map_height" + map_height = value.as(Float64) + else + loc[key] = value + end + end + break + end + end + + # Add map information to the response + if map_width > 0.0 && map_height > 0.0 + loc["map_width"] = map_width + loc["map_height"] = map_height + elsif map_size = get_map_details(location.map_id) + loc["map_width"] = map_width > 0.0 ? map_width : (map_size.length - offset_x) + loc["map_height"] = map_height > 0.0 ? map_height : (map_size.width - offset_y) + end + + loc + } + else + [] of Nil + end + end + + # Will return an array of MAC address strings + # lowercase with no seperation characters abcdeffd1234 etc + def macs_assigned_to(email : String? = nil, username : String? = nil) : Array(String) + user_lookup(username.presence || email.presence.not_nil!).try(&.to_a) || [] of String + end + + # Will return `nil` or `{"location": "wireless", "assigned_to": "bob123", "mac_address": "abcd"}` + def check_ownership_of(mac_address : String) : OwnershipMAC? + if location = locate_mac(mac_address) + { + location: "wireless", + assigned_to: format_username(location.raw_user_id), + mac_address: format_mac(mac_address), + } + end + end + + # Will return an array of devices and their x, y coordinates + def device_locations(zone_id : String, location : String? = nil) + logger.debug { "looking up device locations in #{zone_id}" } + return [] of Nil if location.presence && location != "wireless" + + # Find the floors associated with the provided zone id + floors = [] of String + adjustments = {} of String => Tuple(Float64, Float64, Float64, Float64) + @floorplan_mappings.each do |floor_id, data| + if data.values.includes?(zone_id) + floors << floor_id + offset_x = (data["offset_x"]? || 0.0).as(Float64) + offset_y = (data["offset_y"]? || 0.0).as(Float64) + map_width = (data["map_width"]? || -1.0).as(Float64) + map_height = (data["map_height"]? || -1.0).as(Float64) + adjustments[floor_id] = {offset_x, offset_y, map_width, map_height} + end + end + logger.debug { "found matching meraki floors: #{floors}" } + return [] of Nil if floors.empty? + + checking_count = @locations.size + wrong_floor = 0 + too_old = 0 + + # Find the devices that are on the matching floors + oldest_location = @max_location_age.ago.to_unix + + matching = locations(&.compact_map { |mac, loc| + if loc.last_seen < oldest_location + too_old += 1 + next + end + if (floors & loc.location_mappings.values).empty? + wrong_floor += 1 + next + end + + # ensure the formatted mac is being used + loc.device.mac_address = mac + loc + }) + + logger.debug { "found #{matching.size} matching devices\nchecked #{checking_count} locations, #{wrong_floor} were on the wrong floor, #{too_old} were too old" } + + matching.group_by(&.map_id).flat_map { |map_id, locations| + map_width = -1.0 + map_height = -1.0 + offset_x = 0.0 + offset_y = 0.0 + + # any adjustments required for these locations? + locations.first.location_mappings.each do |tag, location_id| + if level_data = adjustments[location_id]? + offset_x, offset_y, map_width, map_height = level_data + break + end + end + + if map_width == -1.0 || map_height == -1.0 + if map_size = get_map_details(map_id) + map_width = map_width > -1.0 ? map_width : (map_size.length - offset_x) + map_height = map_height > -1.0 ? map_height : (map_size.width - offset_y) + end + end + + locations.map do |loc| + lat = loc.latitude + lon = loc.longitude + + { + location: :wireless, + coordinates_from: "top-left", + x: loc.x_pos - offset_x, + y: loc.y_pos - offset_y, + lon: lon, + lat: lat, + s2_cell_id: S2Cells::LatLon.new(lat, lon).to_token(@s2_level), + mac: loc.device.mac_address, + variance: loc.unc, + last_seen: loc.last_seen, + map_width: map_width, + map_height: map_height, + ssid: loc.ssid, + manufacturer: loc.device.manufacturer, + os: loc.device.os, + } + end + } + end + + def format_mac(address : String) + address.gsub(/(0x|[^0-9A-Fa-f])*/, "").downcase + end + + def format_username(user : String) + if user.includes? "@" + user = user.split("@")[0] + elsif user.includes? "\\" + user = user.split("\\")[1] + end + user.downcase + end + + # This provides the DNA Spaces dashboard with stream consumption status + @[Security(PlaceOS::Driver::Level::Administrator)] + def update_monitoring_status(running : Bool = true) : Nil + response = put("/api/partners/v1/monitoring/status", headers: { + "Content-Type" => "application/json", + "X-API-KEY" => @api_key, + }, body: { + data: { + overallStatus: { + status: running ? "up" : "down", + notices: [] of Nil, + }, + instanceDetails: { + ipAddress: "", + instanceId: module_id, + }, + cloudFirehose: { + status: @stream_active ? "connected" : "disconnected", + lastReceived: @last_received, + }, + localFirehose: { + status: "disconnected", + lastReceived: 0, + }, + subsystems: [] of Nil, + }, + }.to_json) + raise "failed to update status, code #{response.status_code}\n#{response.body}" unless response.success? + end +end + +require "./dna_spaces/events" diff --git a/drivers/cisco/dna_spaces/activation_publickey.cr b/drivers/cisco/dna_spaces/activation_publickey.cr new file mode 100644 index 00000000000..eb30ec06dd3 --- /dev/null +++ b/drivers/cisco/dna_spaces/activation_publickey.cr @@ -0,0 +1,14 @@ +require "./events" + +class Cisco::DNASpaces::ActivactionPublicKey + include JSON::Serializable + + getter version : String + + @[JSON::Field(key: "publicKey")] + getter public_key : String + + def public_key + "-----BEGIN PUBLIC KEY-----\n#{@public_key}\n-----END PUBLIC KEY-----\n" + end +end diff --git a/drivers/cisco/dna_spaces/app_activaction.cr b/drivers/cisco/dna_spaces/app_activaction.cr new file mode 100644 index 00000000000..36202b21424 --- /dev/null +++ b/drivers/cisco/dna_spaces/app_activaction.cr @@ -0,0 +1,21 @@ +require "./events" + +class Cisco::DNASpaces::AppActivaction + include JSON::Serializable + + @[JSON::Field(key: "spacesTenantName")] + getter spaces_tenant_name : String + + @[JSON::Field(key: "spacesTenantId")] + getter spaces_tenant_id : String + + @[JSON::Field(key: "partnerTenantId")] + getter partner_tenant_id : String + getter name : String + + @[JSON::Field(key: "referenceId")] + getter reference_id : String + + @[JSON::Field(key: "instanceName")] + getter instance_name : String +end diff --git a/drivers/cisco/dna_spaces/device.cr b/drivers/cisco/dna_spaces/device.cr new file mode 100644 index 00000000000..7f6c09daaed --- /dev/null +++ b/drivers/cisco/dna_spaces/device.cr @@ -0,0 +1,39 @@ +require "./events" + +class Cisco::DNASpaces::Device + include JSON::Serializable + + @[JSON::Field(key: "deviceId")] + getter device_id : String + + @[JSON::Field(key: "userId")] + getter user_id : String + + getter tags : Array(String) + getter mobile : String + getter email : String + getter gender : String + + @[JSON::Field(key: "firstName")] + getter first_name : String + + @[JSON::Field(key: "lastName")] + getter last_name : String + + @[JSON::Field(key: "postalCode")] + getter postal_code : String + + # optIns + # otherFields + # socialNetworkInfo + + # We make this editable so we can store the formatted version here + @[JSON::Field(key: "macAddress")] + property mac_address : String + getter manufacturer : String + getter os : String + + @[JSON::Field(key: "osVersion")] + getter os_version : String + getter type : String +end diff --git a/drivers/cisco/dna_spaces/device_count.cr b/drivers/cisco/dna_spaces/device_count.cr new file mode 100644 index 00000000000..c799d9b8286 --- /dev/null +++ b/drivers/cisco/dna_spaces/device_count.cr @@ -0,0 +1,22 @@ +require "./events" + +class Cisco::DNASpaces::DeviceCount + include JSON::Serializable + + getter location : Location + + @[JSON::Field(key: "associatedCount")] + getter associated_count : Int32 + + @[JSON::Field(key: "estimatedProbingCount")] + getter estimated_probing_count : Int32 + + @[JSON::Field(key: "probingRandomizedPercentage")] + getter probing_randomized_percentage : Float64 + + @[JSON::Field(key: "estimatedDensity")] + getter estimated_density : Float64 + + @[JSON::Field(key: "estimatedCapacityPercentage")] + getter estimated_capacity_percentage : Float64 +end diff --git a/drivers/cisco/dna_spaces/device_entry.cr b/drivers/cisco/dna_spaces/device_entry.cr new file mode 100644 index 00000000000..befd6dd137e --- /dev/null +++ b/drivers/cisco/dna_spaces/device_entry.cr @@ -0,0 +1,26 @@ +require "./events" + +class Cisco::DNASpaces::DeviceEntry + include JSON::Serializable + + getter device : Device + getter location : Location + + @[JSON::Field(key: "visitId")] + getter visit_id : String + + @[JSON::Field(key: "entryTimestamp")] + getter entry_timestamp : Int64 + + @[JSON::Field(key: "entryDateTime")] + getter entry_datetime : String + + @[JSON::Field(key: "timeZone")] + getter time_zone : String + + @[JSON::Field(key: "deviceClassification")] + getter device_classification : String + + @[JSON::Field(key: "daysSinceLastVisit")] + getter days_sinc_last_visit : Int32 +end diff --git a/drivers/cisco/dna_spaces/device_exit.cr b/drivers/cisco/dna_spaces/device_exit.cr new file mode 100644 index 00000000000..151ef54cd5f --- /dev/null +++ b/drivers/cisco/dna_spaces/device_exit.cr @@ -0,0 +1,38 @@ +require "./events" + +class Cisco::DNASpaces::DeviceExit + include JSON::Serializable + + getter device : Device + getter location : Location + + @[JSON::Field(key: "visitId")] + getter visit_id : String + + @[JSON::Field(key: "visitDurationMinutes")] + getter visit_duration_minutes : Int32 + + @[JSON::Field(key: "visitDurationMinutes")] + getter visit_duration_minutes : Int32 + + @[JSON::Field(key: "entryTimestamp")] + getter entry_timestamp : Int64 + + @[JSON::Field(key: "entryDateTime")] + getter entry_datetime : String + + @[JSON::Field(key: "exitTimestamp")] + getter exit_timestamp : Int64 + + @[JSON::Field(key: "exitDateTime")] + getter exit_datetime : String + + @[JSON::Field(key: "timeZone")] + getter time_zone : String + + @[JSON::Field(key: "deviceClassification")] + getter device_classification : String + + @[JSON::Field(key: "visitClassification")] + getter visit_classification : String +end diff --git a/drivers/cisco/dna_spaces/device_location_update.cr b/drivers/cisco/dna_spaces/device_location_update.cr new file mode 100644 index 00000000000..393017be928 --- /dev/null +++ b/drivers/cisco/dna_spaces/device_location_update.cr @@ -0,0 +1,51 @@ +require "./events" + +class Cisco::DNASpaces::DeviceLocationUpdate + include JSON::Serializable + + getter device : Device + getter location : Location + + getter ssid : String + + @[JSON::Field(key: "rawUserId")] + getter raw_user_id : String + + @[JSON::Field(key: "visitId")] + getter visit_id : String + + @[JSON::Field(key: "lastSeen")] + property last_seen : Int64 + + @[JSON::Field(key: "deviceClassification")] + getter device_classification : String + + @[JSON::Field(key: "mapId")] + property map_id : String + + @[JSON::Field(key: "xPos")] + getter x_pos : Float64 + + @[JSON::Field(key: "yPos")] + getter y_pos : Float64 + + @[JSON::Field(key: "confidenceFactor")] + getter confidence_factor : Float64 + getter latitude : Float64 + getter longitude : Float64 + getter unc : Float64 + + @[JSON::Field(ignore: true)] + @location_mappings : Hash(String, String)? = nil + + # Ensure we only process these once + def location_mappings : Hash(String, String) + if mappings = @location_mappings + mappings + else + mappings = location.details + @location_mappings = mappings + mappings + end + end +end diff --git a/drivers/cisco/dna_spaces/device_presence.cr b/drivers/cisco/dna_spaces/device_presence.cr new file mode 100644 index 00000000000..2c3973a4035 --- /dev/null +++ b/drivers/cisco/dna_spaces/device_presence.cr @@ -0,0 +1,54 @@ +require "./events" + +class Cisco::DNASpaces::DevicePresence + include JSON::Serializable + + @[JSON::Field(key: "presenceEventType")] + getter presence_event_type : String + + @[JSON::Field(key: "wasInActive")] + getter was_in_active : Bool + getter device : Device + getter location : Location + + getter ssid : String + + @[JSON::Field(key: "rawUserId")] + getter raw_user_id : String + + @[JSON::Field(key: "visitId")] + getter visit_id : String + + @[JSON::Field(key: "daysSinceLastVisit")] + getter days_since_last_visit : Int32 + + @[JSON::Field(key: "entryTimestamp")] + getter entry_timestamp : Int64 + + @[JSON::Field(key: "entryDateTime")] + getter entry_datetime : String + + @[JSON::Field(key: "exitTimestamp")] + getter exit_timestamp : Int64 + + @[JSON::Field(key: "exitDateTime")] + getter exit_date_time : String + + @[JSON::Field(key: "visitDurationMinutes")] + getter visit_duration_minutes : Int32 + + @[JSON::Field(key: "timeZone")] + getter time_zone : String + + @[JSON::Field(key: "deviceClassification")] + getter device_classification : String + + @[JSON::Field(key: "visitClassification")] + getter visit_classification : String + + @[JSON::Field(key: "activeDevicesCount")] + getter active_devices_count : Int32 + + @[JSON::Field(key: "inActiveDevicesCount")] + getter inactive_devices_count : Int32 +end diff --git a/drivers/cisco/dna_spaces/events.cr b/drivers/cisco/dna_spaces/events.cr new file mode 100644 index 00000000000..db66a992585 --- /dev/null +++ b/drivers/cisco/dna_spaces/events.cr @@ -0,0 +1,118 @@ +require "json" +require "../dna_spaces" +require "./location" +require "./device" +require "./*" + +# This is used to map the various events into a simpler data structure +abstract class Cisco::DNASpaces::Events + include JSON::Serializable + + # event type hint + use_json_discriminator "eventType", { + "KEEP_ALIVE" => KeepAlive, + "DEVICE_ENTRY" => DeviceEntryWrapper, + "DEVICE_EXIT" => DeviceExitWrapper, + "PROFILE_UPDATE" => ProfileUpdateWrapper, + "LOCATION_CHANGE" => LocationChangeWrapper, + "DEVICE_LOCATION_UPDATE" => DeviceLocationUpdateWrapper, + "TP_PEOPLE_COUNT_UPDATE" => PeopleCountUpdateWrapper, + "DEVICE_PRESENCE" => DevicePresenceWrapper, + "USER_PRESENCE" => UserPresenceWrapper, + "APP_ACTIVATION" => AppActivactionWrapper, + "DEVICE_COUNT" => DeviceCountWrapper, + } + + @[JSON::Field(key: "recordUid")] + getter record_uid : String + + @[JSON::Field(key: "recordTimestamp")] + getter record_timestamp : Int64 + + @[JSON::Field(key: "spacesTenantId")] + getter spaces_tenant_id : String + + @[JSON::Field(key: "spacesTenantName")] + getter spaces_tenant_name : String + + @[JSON::Field(key: "partnerTenantId")] + getter partner_tenant_id : String +end + +class Cisco::DNASpaces::KeepAlive < Cisco::DNASpaces::Events + getter eventType : String = "KEEP_ALIVE" + + def payload + nil + end +end + +class Cisco::DNASpaces::DeviceEntryWrapper < Cisco::DNASpaces::Events + getter eventType : String = "DEVICE_ENTRY" + + @[JSON::Field(key: "deviceEntry")] + getter payload : DeviceEntry +end + +class Cisco::DNASpaces::DeviceExitWrapper < Cisco::DNASpaces::Events + getter eventType : String = "DEVICE_EXIT" + + @[JSON::Field(key: "deviceExit")] + getter payload : DeviceExit +end + +class Cisco::DNASpaces::ProfileUpdateWrapper < Cisco::DNASpaces::Events + getter eventType : String = "PROFILE_UPDATE" + + @[JSON::Field(key: "deviceProfileUpdate")] + getter payload : Device +end + +class Cisco::DNASpaces::LocationChangeWrapper < Cisco::DNASpaces::Events + getter eventType : String = "LOCATION_CHANGE" + + @[JSON::Field(key: "locationHierarchyChange")] + getter payload : LocationChange +end + +class Cisco::DNASpaces::DeviceLocationUpdateWrapper < Cisco::DNASpaces::Events + getter eventType : String = "DEVICE_LOCATION_UPDATE" + + @[JSON::Field(key: "deviceLocationUpdate")] + getter payload : DeviceLocationUpdate +end + +class Cisco::DNASpaces::PeopleCountUpdateWrapper < Cisco::DNASpaces::Events + getter eventType : String = "TP_PEOPLE_COUNT_UPDATE" + + @[JSON::Field(key: "tpPeopleCountUpdate")] + getter payload : PeopleCountUpdate +end + +class Cisco::DNASpaces::DevicePresenceWrapper < Cisco::DNASpaces::Events + getter eventType : String = "DEVICE_PRESENCE" + + @[JSON::Field(key: "devicePresence")] + getter payload : DevicePresence +end + +class Cisco::DNASpaces::UserPresenceWrapper < Cisco::DNASpaces::Events + getter eventType : String = "USER_PRESENCE" + + @[JSON::Field(key: "userPresence")] + getter payload : UserPresence +end + +class Cisco::DNASpaces::AppActivactionWrapper < Cisco::DNASpaces::Events + getter eventType : String = "APP_ACTIVATION" + + @[JSON::Field(key: "appActivation")] + getter payload : AppActivaction +end + +class Cisco::DNASpaces::DeviceCountWrapper < Cisco::DNASpaces::Events + getter eventType : String = "DEVICE_COUNT" + + @[JSON::Field(key: "deviceCounts")] + getter payload : DeviceCount +end diff --git a/drivers/cisco/dna_spaces/location.cr b/drivers/cisco/dna_spaces/location.cr new file mode 100644 index 00000000000..12e2228beca --- /dev/null +++ b/drivers/cisco/dna_spaces/location.cr @@ -0,0 +1,30 @@ +require "./events" + +class Cisco::DNASpaces::Location + include JSON::Serializable + + @[JSON::Field(key: "locationId")] + getter location_id : String + getter name : String + + # TODO:: this might be better as an enum + # if there are only limited types + @[JSON::Field(key: "inferredLocationTypes")] + getter tags : Array(String) + + getter parent : Location? + + # Maps tag names to location_ids + def details(mappings = {} of String => String) + parent.try &.details(mappings) + tags.each { |tag| mappings[tag] = location_id } + mappings + end + + # Maps location_ids to location names + def descriptions(mappings = {} of String => String) + parent.try &.descriptions(mappings) + mappings[location_id] = name + mappings + end +end diff --git a/drivers/cisco/dna_spaces/location_change.cr b/drivers/cisco/dna_spaces/location_change.cr new file mode 100644 index 00000000000..6043e172f14 --- /dev/null +++ b/drivers/cisco/dna_spaces/location_change.cr @@ -0,0 +1,32 @@ +require "./events" + +class Cisco::DNASpaces::LocationChange + include JSON::Serializable + + @[JSON::Field(key: "changeType")] + getter change_type : String + getter location : Location + + class Metadata + include JSON::Serializable + + getter key : String + getter values : Array(String) + end + + class LocationDetails + include JSON::Serializable + + @[JSON::Field(key: "timeZone")] + getter time_zone : String + getter city : String + getter state : String + getter country : String + getter category : String + + getter latitude : Float64 + getter longitude : Float64 + + getter metadata : Array(Metadata) + end +end diff --git a/drivers/cisco/dna_spaces/location_details.cr b/drivers/cisco/dna_spaces/location_details.cr new file mode 100644 index 00000000000..c69048af78f --- /dev/null +++ b/drivers/cisco/dna_spaces/location_details.cr @@ -0,0 +1,16 @@ +require "./events" + +class Cisco::DNASpaces::LocationDetails + include JSON::Serializable + + @[JSON::Field(key: "timeZone")] + getter time_zone : String + + getter city : String + getter state : String + getter country : String + getter category : String + + getter latitude : Float64 + getter longitude : Float64 +end diff --git a/drivers/cisco/dna_spaces/map_info.cr b/drivers/cisco/dna_spaces/map_info.cr new file mode 100644 index 00000000000..31be13841d7 --- /dev/null +++ b/drivers/cisco/dna_spaces/map_info.cr @@ -0,0 +1,30 @@ +require "./events" + +class Cisco::DNASpaces::Dimension + include JSON::Serializable + + getter length : Float64 + getter width : Float64 + getter height : Float64 + + @[JSON::Field(key: "offsetX")] + getter offset_x : Float64 + + @[JSON::Field(key: "offsetY")] + getter offset_y : Float64 +end + +class Cisco::DNASpaces::MapInfo + include JSON::Serializable + + @[JSON::Field(key: "mapId")] + getter id : String + + @[JSON::Field(key: "imageWidth")] + getter image_width : Float64 + + @[JSON::Field(key: "imageHeight")] + getter image_height : Float64 + + getter dimension : Cisco::DNASpaces::Dimension +end diff --git a/drivers/cisco/dna_spaces/people_count_update.cr b/drivers/cisco/dna_spaces/people_count_update.cr new file mode 100644 index 00000000000..99d8f35cd22 --- /dev/null +++ b/drivers/cisco/dna_spaces/people_count_update.cr @@ -0,0 +1,32 @@ +require "./events" + +# This is triggered from telepresence devices +class Cisco::DNASpaces::PeopleCountUpdate + include JSON::Serializable + + @[JSON::Field(key: "tpDeviceId")] + getter tp_device_id : String + getter location : Location + getter presence : Bool + + @[JSON::Field(key: "peopleCount")] + getter people_count : Int32 + + @[JSON::Field(key: "standbyState")] + getter standby_state : Int32 + + @[JSON::Field(key: "ambientNoise")] + getter ambient_noise : Int32 + + @[JSON::Field(key: "drynessScore")] + getter dryness_score : Int32 + + @[JSON::Field(key: "activeCalls")] + getter active_calls : Int32 + + @[JSON::Field(key: "presentationState")] + getter presentation_state : Int32 + + @[JSON::Field(key: "timeStamp")] + getter timestamp : Int64 +end diff --git a/drivers/cisco/dna_spaces/user_presence.cr b/drivers/cisco/dna_spaces/user_presence.cr new file mode 100644 index 00000000000..1882f4b25a2 --- /dev/null +++ b/drivers/cisco/dna_spaces/user_presence.cr @@ -0,0 +1,83 @@ +require "./events" + +class Cisco::DNASpaces::UserPresence + include JSON::Serializable + + class User + include JSON::Serializable + + @[JSON::Field(key: "userId")] + getter user_id : String + + @[JSON::Field(key: "deviceIds")] + getter device_ids : Array(String) + getter tags : Array(String) + getter mobile : String + getter email : String + getter gender : String + + @[JSON::Field(key: "firstName")] + getter first_name : String + + @[JSON::Field(key: "lastName")] + getter last_name : String + + @[JSON::Field(key: "postalCode")] + getter postal_code : String + + # otherFields + # socialNetworkInfo + end + + class UserCount + include JSON::Serializable + + @[JSON::Field(key: "usersWithUserId")] + getter users_with_user_id : Int64 + + @[JSON::Field(key: "usersWithoutUserId")] + getter users_without_user_id : Int64 + + @[JSON::Field(key: "totalUsers")] + getter total_users : Int64 + end + + @[JSON::Field(key: "presenceEventType")] + getter presence_event_type : String + + @[JSON::Field(key: "wasInActive")] + getter was_in_active : Bool + + getter user : User + getter location : Location + + @[JSON::Field(key: "rawUserId")] + getter raw_user_id : String + + @[JSON::Field(key: "visitId")] + getter visit_id : String + + @[JSON::Field(key: "entryTimestamp")] + getter entry_timestamp : Int64 + + @[JSON::Field(key: "entryDateTime")] + getter entry_datetime : String + + @[JSON::Field(key: "exitTimestamp")] + getter exit_timestamp : Int64 + + @[JSON::Field(key: "exitDateTime")] + getter exit_datetime : String + + @[JSON::Field(key: "visitDurationMinutes")] + getter visit_duration_minutes : Int32 + + @[JSON::Field(key: "timeZone")] + getter time_zone : String + + @[JSON::Field(key: "activeUsersCount")] + getter active_users_count : UserCount + + @[JSON::Field(key: "inActiveUsersCount")] + getter inactive_users_count : UserCount +end diff --git a/drivers/cisco/dna_spaces_spec.cr b/drivers/cisco/dna_spaces_spec.cr new file mode 100644 index 00000000000..c3df6cba22c --- /dev/null +++ b/drivers/cisco/dna_spaces_spec.cr @@ -0,0 +1,16 @@ +DriverSpecs.mock_driver "Cisco::DNASpaces" do + # The dashboard should request the streaming API + expect_http_request do |request, response| + headers = request.headers + if headers["X-API-KEY"]? == "X-API-KEY" + response.headers["Transfer-Encoding"] = "chunked" + response.status_code = 200 + response << %({"recordUid":"event-85b84f15","recordTimestamp":1605502585236,"spacesTenantId":"","spacesTenantName":"","partnerTenantId":"","eventType":"KEEP_ALIVE"}) + else + response.status_code = 401 + end + end + + # Should standardise the format of MAC addresses + exec(:format_mac, "0x12:34:A6-789B").get.should eq %(1234a6789b) +end diff --git a/drivers/cisco/meraki/captive_portal.cr b/drivers/cisco/meraki/captive_portal.cr new file mode 100644 index 00000000000..5866d8d181b --- /dev/null +++ b/drivers/cisco/meraki/captive_portal.cr @@ -0,0 +1,145 @@ +module Cisco; end + +module Cisco::Meraki; end + +require "json" +require "openssl" + +class Cisco::Meraki::CaptivePortal < PlaceOS::Driver + # Discovery Information + descriptive_name "Cisco Meraki Captive Portal" + generic_name :CaptivePortal + description %( + for more information visit: https://meraki.cisco.com/lib/pdf/meraki_whitepaper_captive_portal.pdf + ) + + default_settings({ + wifi_secret: "anything really", + default_timezone: "Australia/Sydney", + date_format: "%Y%m%d", + # duration of access in hours + access_duration: 12, + # Length of the clients wifi code + code_length: 4, + success_url: "https://company.com/welcome", + }) + + def on_load + on_update + end + + @wifi_secret : String = "" + @date_format : String = "%Y%m%d" + @success_url : String = "https://place.technology/" + @default_timezone : Time::Location = Time::Location.load("Australia/Sydney") + @access_duration : Time::Span = 12.hours + @code_length : Int32 = 4 + + @denied : UInt64 = 0_u64 + @granted : UInt64 = 0_u64 + @errors : UInt64 = 0_u64 + + @guests : Hash(String, ChallengePayload) = {} of String => ChallengePayload + + def on_update + @wifi_secret = setting?(String, :wifi_secret) || "anything really" + @date_format = setting?(String, :date_format) || "%Y%m%d" + @success_url = setting?(String, :success_url) || "https://place.technology/" + @access_duration = (setting?(Int32, :access_duration) || 12).hours + @code_length = setting?(Int32, :code_length) || 4 + + time_zone = setting?(String, :default_timezone).presence + @default_timezone = Time::Location.load(time_zone) if time_zone + end + + @[Security(Level::Support)] + def guests + @guests + end + + @[Security(Level::Support)] + def lookup(mac : String) + @guests[format_mac(mac)] + end + + def generate_guest_data(email : String, time : Int64, time_zone : String? = nil) + time_zone = time_zone.presence ? Time::Location.load(time_zone.not_nil!) : @default_timezone + date = Time.unix(time).in(time_zone).to_s(@date_format) + guest_string = "#{email.downcase}-#{date}-#{@wifi_secret}" + + OpenSSL::Digest.new("SHA256").update(guest_string).final.hexstring + end + + # Splits the SHA256 into code length and then randomly selects one + def generate_guest_token(email : String, time : Int64, time_zone : String? = nil) + generate_guest_data(email, time, time_zone).scan(/.{#{@code_length}}/).sample(1)[0][0] + end + + class ChallengePayload + include JSON::Serializable + + property ap_mac : String + property client_ip : String + property client_mac : String + property base_grant_url : String + property user_continue : String? + + # key they were provided in their invite email + property code : String + property email : String + property timezone : String? + + property expires : Time? = nil + end + + EMPTY_HEADERS = {} of String => String + JSON_HEADERS = { + "Content-Type" => "application/json", + } + + # Webhook for providing guest access + def challenge(method : String, headers : Hash(String, Array(String)), body : String) + logger.debug { "guest access attempt: #{method},\nheaders #{headers},\nbody #{body}" } + + challenge = ChallengePayload.from_json(body) + + check_code = challenge.code + guest_codes = generate_guest_data(challenge.email, Time.utc.to_unix, challenge.timezone) + matched = guest_codes.scan(/.{#{@code_length}}/).count { |code| code[0] == check_code } > 0 + + if matched + challenge.expires = @access_duration.from_now + @guests[format_mac(challenge.client_mac)] = challenge + @granted += 1_u64 + self[:granted_access] = @granted + + redirect_url = "#{challenge.base_grant_url}?duration=#{@access_duration.to_i}&continue_url=#{challenge.user_continue || @success_url}" + response = { + redirect_to: redirect_url, + }.to_json + + logger.debug { "successful joined network #{challenge.inspect}" } + + # Redirect to the success URL + {HTTP::Status::OK, JSON_HEADERS, response} + else + @denied += 1_u64 + self[:denied_access] = @denied + + logger.debug { "failed wifi access attempt by #{challenge.inspect}" } + + {HTTP::Status::NOT_ACCEPTABLE, JSON_HEADERS, "{}"} + end + rescue error + @errors += 1_u64 + self[:errors] = @errors + last_error = error.inspect_with_backtrace + self[:last_error] = last_error + logger.error { "failed to parse wifi challenge payload\n#{error}" } + {HTTP::Status::INTERNAL_SERVER_ERROR, EMPTY_HEADERS, nil} + end + + protected def format_mac(address : String) + address.gsub(/(0x|[^0-9A-Fa-f])*/, "").downcase + end +end diff --git a/drivers/cisco/meraki/captive_portal_spec.cr b/drivers/cisco/meraki/captive_portal_spec.cr new file mode 100644 index 00000000000..a627b8b4b81 --- /dev/null +++ b/drivers/cisco/meraki/captive_portal_spec.cr @@ -0,0 +1,15 @@ +require "openssl" + +DriverSpecs.mock_driver "Cisco::Meraki::CaptivePortal" do + date = Time.unix(1599477274).in(Time::Location.load("Australia/Sydney")).to_s("%Y%m%d") + hexdigest = OpenSSL::Digest.new("SHA256").update("guest@email.com-#{date}-anything really").final.hexstring + + # Check the hex codes match + retval = exec(:generate_guest_data, "guest@email.com", 1599477274, "Australia/Sydney") + retval.get.should eq hexdigest + + # check it matches on of the codes + codes = hexdigest.scan(/.{4}/).map { |code| code[0] } + retval = exec(:generate_guest_token, "guest@email.com", 1599477274, "Australia/Sydney") + codes.includes?(retval.get.not_nil!.as_s).should eq true +end diff --git a/drivers/cisco/meraki/dashboard.cr b/drivers/cisco/meraki/dashboard.cr new file mode 100644 index 00000000000..866f6a28054 --- /dev/null +++ b/drivers/cisco/meraki/dashboard.cr @@ -0,0 +1,821 @@ +module Cisco; end + +module Cisco::Meraki; end + +require "uri" +require "json" +require "s2_cells" +require "link-header" +require "./scanning_api" +require "placeos-driver/interface/locatable" + +class Cisco::Meraki::Dashboard < PlaceOS::Driver + include Interface::Locatable + + # Discovery Information + descriptive_name "Cisco Meraki Dashboard" + generic_name :Dashboard + uri_base "https://api.meraki.com" + description %( + for more information visit: + * Dashboard API: https://documentation.meraki.com/zGeneral_Administration/Other_Topics/The_Cisco_Meraki_Dashboard_API + * Scanning API: https://developer.cisco.com/meraki/scanning-api/#!introduction/scanning-api + + NOTE:: API Call volume is rate limited to 5 calls per second per organization + ) + + default_settings({ + meraki_validator: "configure if scanning API is enabled", + meraki_secret: "configure if scanning API is enabled", + meraki_api_key: "configure for the dashboard API", + + # We will always accept a reading with a confidence lower than this + acceptable_confidence: 5.0, + + # Max Uncertainty in meters - we don't accept positions that are less certain + maximum_uncertainty: 25.0, + + # can we use the meraki dashboard API for user lookups + default_network_id: "network_id", + + # Max requests a second made to the dashboard + rate_limit: 4, + + # Area index each point on a floor lands on + # 21 == ~4 meters squared, which given wifi variance is good enough for tracing + # S2 cell levels: https://s2geometry.io/resources/s2cell_statistics.html + s2_level: 21, + debug_webhook: false, + + # Level mappings, level name for human readability + floorplan_mappings: { + "g_727894289773756672" => { + "building": "zone-12345", + "level": "zone-123456", + "level_name": "BUILDING - L1", + }, + }, + + # Time before a user location is considered probably too old + max_location_age: 10, + + # Ignore certain usernames from the dashboard + ignore_usernames: ["host/"], + + # Enable / Disable dashboard username lookup completely + disable_username_lookup: false, + }) + + def on_load + # We want to store our user => mac_address mappings in redis + @user_mac_mappings = PlaceOS::Driver::RedisStorage.new(module_id, "user_macs") + spawn { rate_limiter } + on_update + end + + @scanning_validator : String = "" + @scanning_secret : String = "" + @api_key : String = "" + + @acceptable_confidence : Float64 = 5.0 + @maximum_uncertainty : Float64 = 25.0 + + @time_multiplier : Float64 = 0.0 + @confidence_multiplier : Float64 = 0.0 + @max_location_age : Time::Span = 6.minutes + @drift_location_age : Time::Span = 4.minutes + @confidence_time : Time::Span = 2.minutes + + @rate_limit : Int32 = 4 + @channel : Channel(Nil) = Channel(Nil).new(1) + @queue_lock : Mutex = Mutex.new + @queue_size = 0 + @wait_time : Time::Span = 300.milliseconds + + @storage_lock : Mutex = Mutex.new + @user_mac_mappings : PlaceOS::Driver::RedisStorage? = nil + @default_network : String = "" + @floorplan_mappings : Hash(String, Hash(String, String | Float64)) = Hash(String, Hash(String, String | Float64)).new + @floorplan_sizes = {} of String => FloorPlan + + @s2_level : Int32 = 21 + @debug_webhook : Bool = false + @debug_payload : Bool = false + @ignore_usernames : Array(String) = [] of String + + def on_update + @scanning_validator = setting?(String, :meraki_validator) || "" + @scanning_secret = setting?(String, :meraki_secret) || "" + @api_key = setting?(String, :meraki_api_key) || "" + + @rate_limit = setting?(Int32, :rate_limit) || 4 + @wait_time = 1.second / @rate_limit + + @default_network = setting?(String, :default_network_id) || "" + + @acceptable_confidence = setting?(Float64, :acceptable_confidence) || 5.0 + @maximum_uncertainty = setting?(Float64, :maximum_uncertainty) || 25.0 + + @max_location_age = (setting?(UInt32, :max_location_age) || 6).minutes + # Age we keep a confident value (without drifting towards less confidence) + @confidence_time = @max_location_age / 3 + # Age at which we discard a drifting value (accepting a less confident value) + @drift_location_age = @max_location_age - @confidence_time + + # How much confidence do we have in this new value, relative to an old confident value + @time_multiplier = 1.0_f64 / (@drift_location_age.to_i - @confidence_time.to_i).to_f64 + @confidence_multiplier = 1.0_f64 / (@maximum_uncertainty.to_i - @acceptable_confidence.to_i).to_f64 + + @floorplan_mappings = setting?(Hash(String, Hash(String, String | Float64)), :floorplan_mappings) || @floorplan_mappings + + @s2_level = setting?(Int32, :s2_level) || 21 + @debug_webhook = setting?(Bool, :debug_webhook) || false + @debug_payload = setting?(Bool, :debug_payload) || false + + @ignore_usernames = setting?(Array(String), :ignore_usernames) || [] of String + disable_username_lookup = setting?(Bool, :disable_username_lookup) || false + + schedule.clear + if @default_network.presence + schedule.every(2.minutes) { map_users_to_macs } unless disable_username_lookup + schedule.every(29.minutes, immediate: true) { sync_floorplan_sizes } + end + schedule.every(30.minutes) { cleanup_caches } + end + + protected def user_mac_mappings + @storage_lock.synchronize { + yield @user_mac_mappings.not_nil! + } + end + + # Perform fetch with the required API request limits in place + @[Security(PlaceOS::Driver::Level::Support)] + def fetch(location : String) + req(location) { |response| response.body } + end + + protected def req(location : String) + if (@wait_time * @queue_size) > 10.seconds + raise "wait time would be exceeded for API request, #{@queue_size} requests already queued" + end + + @queue_lock.synchronize { @queue_size += 1 } + @channel.receive + @queue_lock.synchronize { @queue_size -= 1 } + + headers = HTTP::Headers{ + "X-Cisco-Meraki-API-Key" => @api_key, + "Content-Type" => "application/json", + "Accept" => "application/json", + "User-Agent" => "PlaceOS/2.0 PlaceTechnology", + } + + uri = URI.parse(location) + response = if uri.host.nil? + get(location, headers: headers) + else + HTTP::Client.get(location, headers: headers) + end + + if response.success? + yield response + elsif response.status.found? + # Meraki might return a `302` on GET requests + response = HTTP::Client.get(response.headers["Location"], headers: headers) + if response.success? + yield response + else + raise "request #{location} failed with status: #{response.status_code}" + end + else + raise "request #{location} failed with status: #{response.status_code}" + end + end + + EMPTY_HEADERS = {} of String => String + SUCCESS_RESPONSE = {HTTP::Status::OK, EMPTY_HEADERS, nil} + + struct Lookup + include JSON::Serializable + + property time : Time + property mac : String + + def initialize(@time, @mac) + end + end + + # MAC Address => Location + @locations : Hash(String, Location) = {} of String => Location + @ip_lookup : Hash(String, Lookup) = {} of String => Lookup + + def lookup_ip(address : String) + @ip_lookup[address.downcase]? + end + + def locate_mac(address : String) + @locations[format_mac(address)]? + end + + @[Security(PlaceOS::Driver::Level::Support)] + def inspect_state + logger.debug { + "IP Mappings: #{@ip_lookup.keys}\n\nMAC Locations: #{@locations.keys}\n\nClient Details: #{@client_details.keys}" + } + {ip_mappings: @ip_lookup.size, tracking: @locations.size, client_details: @client_details.size} + end + + # Returns the list of users who can be located + @[Security(PlaceOS::Driver::Level::Support)] + def locateable + too_old = location_max_age = @max_location_age.ago + @client_details.compact_map do |mac, client| + location = @locations[mac]? + client.user if location && ((location.time > too_old) || (client.time_added > too_old)) + end + end + + @[Security(PlaceOS::Driver::Level::Support)] + def poll_clients(network_id : String? = nil, timespan : UInt32 = 900_u32) + network_id = network_id.presence || @default_network + + clients = [] of Client + next_page = "/api/v1/networks/#{network_id}/clients?perPage=1000×pan=#{timespan}" + + loop do + break unless next_page + + next_page = req(next_page) do |response| + clients.concat Array(Client).from_json(response.body) + LinkHeader.new(response)["next"]? + end + end + + clients + end + + @client_details : Hash(String, Client) = {} of String => Client + + @[Security(PlaceOS::Driver::Level::Support)] + def map_users_to_macs(network_id : String? = nil) + network_id = network_id.presence || @default_network + + logger.debug { "mapping users to device MACs" } + clients = poll_clients(network_id) + + new_devices = 0 + updated_dev = 0 + now = Time.utc + + logger.debug { "mapping found #{clients.size} devices" } + + user_mac_mappings do |storage| + clients.each do |client| + # So we can merge additional details into device location responses + user_mac = format_mac(client.mac) + client.time_added = now + + user_id = client.user + + if user_id + @ignore_usernames.each do |name| + if user_id.starts_with?(name) + client.user = user_id = nil + break + end + end + end + + # Attempt to lookup username via learning + if user_id.nil? + if known_id = storage[user_mac]? + client.user = known_id + end + end + + @client_details[user_mac] = client + next unless user_id + + was_update, was_new = map_user_mac(user_mac, user_id, storage) + updated_dev += 1 if was_update + new_devices += 1 if was_new + end + end + + logger.debug { "mapping assigned #{new_devices} new devices, #{updated_dev} user updated" } + nil + end + + protected def map_user_mac(user_mac, user_id, storage) + updated_dev = false + new_devices = false + user_id = format_username(user_id) + + # Check if mac mapping already exists + existing_user = storage[user_mac]? + return {false, false} if existing_user == user_id + + # Remove any pervious mappings + if existing_user + updated_dev = true + if user_macs = storage[existing_user]? + macs = Array(String).from_json(user_macs) + macs.delete(user_mac) + storage[existing_user] = macs.to_json + end + else + new_devices = true + end + + # Update the user mappings + storage[user_mac] = user_id + macs = if user_macs = storage[user_id]? + tmp_macs = Array(String).from_json(user_macs) + tmp_macs.unshift(user_mac) + tmp_macs.uniq! + tmp_macs[0...9] + else + [user_mac] + end + storage[user_id] = macs + + {updated_dev, new_devices} + end + + def format_username(user : String) + if user.includes? "@" + user = user.split("@")[0] + elsif user.includes? "\\" + user = user.split("\\")[1] + end + user.downcase + end + + def macs_assigned_to(email : String? = nil, username : String? = nil) : Array(String) + username = format_username(username.presence || email.presence.not_nil!) + if macs = user_mac_mappings { |s| s[username]? } + Array(String).from_json(macs) + else + [] of String + end + end + + def check_ownership_of(mac_address : String) : OwnershipMAC? + lookup = format_mac(mac_address) + if user = user_mac_mappings { |s| s[lookup]? } + { + location: "wireless", + assigned_to: user, + mac_address: lookup, + } + end + end + + # returns locations based on most recently seen + # versus most accurate location + def locate_user(email : String? = nil, username : String? = nil) + username = format_username(username.presence || email.presence.not_nil!) + + if macs = user_mac_mappings { |s| s[username]? } + location_max_age = @max_location_age.ago + + Array(String).from_json(macs).compact_map { |mac| + if location = locate_mac(mac) + client = @client_details[mac]? + + # We set these here to speed up processing + location.client = client + location.mac = mac + + if client && client.time_added > location_max_age + location + elsif location.time > location_max_age + location + end + end + }.sort { |a, b| + b.time <=> a.time + }.map { |location| + lat = location.lat + lon = location.lng + + loc = { + "location" => "wireless", + "coordinates_from" => "bottom-left", + "x" => location.x, + "y" => location.y, + "lon" => lon, + "lat" => lat, + "s2_cell_id" => lat ? S2Cells::LatLon.new(lat.not_nil!, lon.not_nil!).to_token(@s2_level) : nil, + "mac" => location.mac, + "variance" => location.variance, + "last_seen" => location.time.to_unix, + "meraki_floor_id" => location.floor_plan_id, + "meraki_floor_name" => location.floor_plan_name, + } + + # Add our zone IDs to the response + if level_data = @floorplan_mappings[location.floor_plan_id]? + level_data.each { |k, v| loc[k] = v } + end + + # Add meraki map information to the response + if map_size = @floorplan_sizes[location.floor_plan_id]? + loc["map_width"] = map_size.width + loc["map_height"] = map_size.height + end + + # Add additional client information if it's available + if client = location.client + loc["manufacturer"] = client.manufacturer if client.manufacturer + loc["os"] = client.os if client.os + loc["ssid"] = client.ssid if client.ssid + end + + loc + } + else + [] of Nil + end + end + + def device_locations(zone_id : String, location : String? = nil) + logger.debug { "looking up device locations in #{zone_id}" } + return [] of Nil if location.presence && location != "wireless" + + # Find the floors associated with the provided zone id + floors = [] of String + @floorplan_mappings.each do |floor_id, data| + floors << floor_id if data.values.includes?(zone_id) + end + logger.debug { "found matching meraki floors: #{floors}" } + return [] of Nil if floors.empty? + + checking_count = @locations.size + wrong_floor = 0 + too_old = 0 + + # Find the devices that are on the matching floors + oldest_location = @max_location_age.ago + matching = @locations.compact_map do |mac, loc| + # We set this here to speed up processing + client = @client_details[mac]? + loc.client = client + + if loc.time < oldest_location + if client + if client.time_added < oldest_location + too_old += 1 + next + end + else + too_old += 1 + next + end + end + if !floors.includes?(loc.floor_plan_id) + wrong_floor += 1 + next + end + # ensure the formatted mac is being used + loc.mac = mac + loc + end + + logger.debug { "found #{matching.size} matching devices\nchecked #{checking_count} locations, #{wrong_floor} were on the wrong floor, #{too_old} were too old" } + + # Build the payload on the matching locations + matching.group_by(&.floor_plan_id).flat_map { |floor_id, locations| + map_width = -1.0 + map_height = -1.0 + + if map_size = @floorplan_sizes[floor_id]? + map_width = map_size.width + map_height = map_size.height + elsif mappings = @floorplan_mappings[floor_id]? + map_width = (mappings["width"]? || map_width).as(Float64) + map_height = (mappings["height"]? || map_width).as(Float64) + end + + locations.map do |loc| + lat = loc.lat + lon = loc.lng + + # Add additional client information if it's available + if client = @client_details[loc.mac]? + manufacturer = client.manufacturer + os = client.os + ssid = client.ssid + end + + { + location: :wireless, + coordinates_from: "bottom-left", + x: loc.x, + y: loc.y, + lon: lon, + lat: lat, + s2_cell_id: lat ? S2Cells::LatLon.new(lat.not_nil!, lon.not_nil!).to_token(@s2_level) : nil, + mac: loc.mac, + variance: loc.variance, + last_seen: loc.time.to_unix, + map_width: map_width, + map_height: map_height, + manufacturer: manufacturer, + os: os, + ssid: ssid, + } + end + } + end + + @[Security(PlaceOS::Driver::Level::Support)] + def cleanup_caches : Nil + logger.debug { "removing IP and location data that is over 30 minutes old" } + + # IP => MAC mappings + old = 30.minutes.ago + remove_keys = [] of String + @ip_lookup.each { |ip, lookup| remove_keys << ip if lookup.time < old } + remove_keys.each { |ip| @ip_lookup.delete(ip) } + logger.debug { "removed #{remove_keys.size} IP => MAC mappings" } + + # IP => Username mappings + remove_keys.clear + @ip_usernames.each { |ip, lookup| remove_keys << ip if lookup.time < old } + remove_keys.each { |ip| @ip_usernames.delete(ip) } + logger.debug { "removed #{remove_keys.size} IP => Username mappings" } + + # Client details + remove_keys.clear + @client_details.each { |mac, client| remove_keys << mac if client.time_added < old } + remove_keys.each { |mac| @client_details.delete(mac) } + logger.debug { "removed #{remove_keys.size} client details" } + + # MACs + remove_keys.clear + @locations.each do |mac, location| + if location.time < old + if client = @client_details[mac]? + remove_keys << mac if client.time_added < old + else + remove_keys << mac + end + end + end + remove_keys.each { |mac| @locations.delete(mac) } + logger.debug { "removed #{remove_keys.size} MACs" } + end + + class FloorPlan + include JSON::Serializable + + @[JSON::Field(key: "floorPlanId")] + property id : String + property width : Float64 + property height : Float64 + + # This is useful for when we have to map meraki IDs to our zones + property name : String? + end + + @[Security(PlaceOS::Driver::Level::Support)] + def sync_floorplan_sizes(network_id : String? = nil) + network_id = network_id.presence || @default_network + logger.debug { "syncing floor plan sizes for network #{network_id}" } + + floor_plans = {} of String => FloorPlan + + req("/api/v1/networks/#{network_id}/floorPlans") { |response| + Array(FloorPlan).from_json(response.body).each do |plan| + floor_plans[plan.id] = plan + end + nil + } + + @floorplan_sizes = floor_plans + end + + # Webhook endpoint for scanning API, expects version 3 + def scanning_api(method : String, headers : Hash(String, Array(String)), body : String) + logger.debug { "scanning API received: #{method},\nheaders #{headers},\nbody size #{body.size}" } + logger.debug { body } if @debug_payload + + # Return the scanning API validator code on a GET request + return {HTTP::Status::OK.to_i, EMPTY_HEADERS, @scanning_validator} if method == "GET" + + # Check the version matches + if !body.starts_with?(%({"version":"3.0")) + logger.warn { "unknown scanning API message received:\n#{body[0..96]}" } + return SUCCESS_RESPONSE + end + + locations_updated = 0 + + # Parse the data posted + begin + seen = DevicesSeen.from_json(body) + logger.debug { "parsed meraki payload" } + + # We're only interested in Wifi at the moment + if seen.message_type != "WiFi" + logger.debug { "ignoring message type: #{seen.message_type}" } + return SUCCESS_RESPONSE + end + + # Check the secret matches + raise "secret mismatch, sent: #{seen.secret}" unless seen.secret == @scanning_secret + + # Extract coordinate data against the MAC address and save IP address mappings + observations = seen.data.observations.reject(&.locations.empty?) + + ignore_older = @max_location_age.ago.in Time::Location::UTC + drift_older = @drift_location_age.ago.in Time::Location::UTC + current_time = Time.utc + current_time_unix = current_time.to_unix + + observations.each do |observation| + client_mac = format_mac(observation.client_mac) + existing = @locations[client_mac]? + + logger.debug { "parsing new observation for #{client_mac}" } if @debug_webhook + location = parse(existing, ignore_older, drift_older, current_time_unix, observation.latest_record.time, observation.locations) + if location + @locations[client_mac] = location + locations_updated += 1 + end + update_ipv4(observation.ipv4, client_mac, current_time) + update_ipv6(observation.ipv6.try(&.downcase), client_mac, current_time) + end + rescue e + logger.error { "failed to parse meraki scanning API payload\n#{e.inspect_with_backtrace}" } + logger.debug { "failed payload body was\n#{body}" } + end + + logger.debug { "updated #{locations_updated} locations" } + + # Return a 200 response + SUCCESS_RESPONSE + end + + protected def parse(existing, ignore_older, drift_older, current_time, latest_raw, locations_raw) : Location? + # deal with times in a relative way + adjust_by = (current_time - latest_raw.to_unix).seconds + + # existing.time is our ajusted time + if existing_time = existing.try &.time + existing = nil if existing_time < ignore_older + end + + # remove locations that don't have an x,y or very uncertain or very old + locations = locations_raw.reject do |loc| + loc.time = loc.time + adjust_by + loc.get_x.nil? || loc.variance > @maximum_uncertainty + end + + if locations.empty? + logger.debug { + if locations_raw.empty? + "ignored as no location data provided" + else + "ignored as no location in observation met minimum requirements, had coordinates: #{!!locations_raw[0].get_x}, uncertainty: #{locations_raw[0].variance}" + end + } if @debug_webhook + return existing + end + + # ensure oldest -> newest (we adjusted these already) + locations = locations.sort { |a, b| a.time <=> b.time } + + # estimate the location given the current observations + location = existing || locations.shift + locations.each do |new_loc| + next unless new_loc.time >= location.time + + # If acceptable then this is newer + if new_loc.variance < @acceptable_confidence + location = new_loc + next + end + + # if more accurate and newer then we'll take this + if new_loc.variance < location.variance + location = new_loc + next + end + + # should we drift the older location towards a less accurate newer location + if location.time < drift_older + # has the floor changed, we should probably accept the newer less accurate location + if location.floor_plan_id != new_loc.floor_plan_id + location = new_loc + next + end + + new_uncertainty = new_loc.variance + old_uncertainty = location.variance + + confidence_factor = 1.0 - (@confidence_multiplier * (new_uncertainty - @acceptable_confidence)) + confidence_factor = 0.0 if confidence_factor < 0 + + time_diff = new_loc.time.to_unix - location.time.to_unix + time_factor = @time_multiplier * (time_diff - @confidence_time.to_i).to_f + time_factor = 0.0 if time_factor < 0 + + # Average of the confidence factors + average_multiplier = (confidence_factor + time_factor) / 2.0 + + new_x = new_loc.x! + new_y = new_loc.y! + old_x = location.x! + old_y = location.y! + + # 7.5 = 5 + (( 10 - 5 ) * 0.5) + new_x = old_x + ((new_x - old_x) * average_multiplier) + new_y = old_y + ((new_y - old_y) * average_multiplier) + new_uncertainty = old_uncertainty + ((new_uncertainty - old_uncertainty) * average_multiplier) + + new_loc.x = new_x + new_loc.y = new_y + new_loc.variance = new_uncertainty + location = new_loc + end + end + + location + end + + protected def update_ipv4(ipv4, client_mac, current_time) + return unless ipv4 + + lookup = @ip_lookup[ipv4]? || Lookup.new(current_time, client_mac) + lookup.time = current_time + lookup.mac = client_mac + @ip_lookup[ipv4] = lookup + + if lookup = @ip_usernames[ipv4]? + username = lookup.mac + user_mac_mappings { |storage| map_user_mac(client_mac, username, storage) } + end + end + + protected def update_ipv6(ipv6, client_mac, current_time) + return unless ipv6 + + lookup = @ip_lookup[ipv6]? || Lookup.new(current_time, client_mac) + lookup.time = current_time + lookup.mac = client_mac + @ip_lookup[ipv6] = lookup + + if lookup = @ip_usernames[ipv6]? + username = lookup.mac + user_mac_mappings { |storage| map_user_mac(client_mac, username, storage) } + end + end + + def format_mac(address : String) + address.gsub(/(0x|[^0-9A-Fa-f])*/, "").downcase + end + + protected def rate_limiter + loop do + begin + @channel.send(nil) + rescue error + logger.error(exception: error) { "issue with rate limiter" } + ensure + sleep @wait_time + end + end + rescue + # Possible error with logging exception, restart rate limiter silently + spawn { rate_limiter } + end + + # ip => {username, time} + @ip_usernames : Hash(String, Lookup) = {} of String => Lookup + + @[Security(PlaceOS::Driver::Level::Administrator)] + def ip_username_mappings(ip_map : Array(Tuple(String, String, String, String?))) : Nil + now = Time.utc + user_mac_mappings do |storage| + ip_map.each do |(ip, username, domain, hostname)| + username = format_username(username) + @ip_usernames[ip] = Lookup.new(now, username) + + if lookup = @ip_lookup[ip]? + map_user_mac(lookup.mac, username, storage) + end + end + end + end + + @[Security(PlaceOS::Driver::Level::Administrator)] + def mac_address_mappings(username : String, macs : Array(String), domain : String = "") + username = format_username(username) + user_mac_mappings do |storage| + macs.each { |mac| map_user_mac(format_mac(mac), username, storage) } + end + end +end diff --git a/drivers/cisco/meraki/dashboard_spec.cr b/drivers/cisco/meraki/dashboard_spec.cr new file mode 100644 index 00000000000..bdb18cc485c --- /dev/null +++ b/drivers/cisco/meraki/dashboard_spec.cr @@ -0,0 +1,32 @@ +DriverSpecs.mock_driver "Cisco::Meraki::Dashboard" do + # The dashboard should request the floorplan sizes on load + expect_http_request do |request, response| + headers = request.headers + if headers["X-Cisco-Meraki-API-Key"]? == "configure for the dashboard API" + response.status_code = 200 + response << %([{"floorPlanId":"floor-123","name":"Level 1","width":30.5,"height":20}]) + else + response.status_code = 401 + end + end + + # Send the request + retval = exec(:fetch, "/api/v0/organizations") + + # The dashboard should send a HTTP request with the API key + expect_http_request do |request, response| + headers = request.headers + if headers["X-Cisco-Meraki-API-Key"]? == "configure for the dashboard API" + response.status_code = 202 + response << %([{"id":"org id","name":"place tech"}]) + else + response.status_code = 401 + end + end + + # Should return the payload + retval.get.should eq %([{"id":"org id","name":"place tech"}]) + + # Should standardise the format of MAC addresses + exec(:format_mac, "0x12:34:A6-789B").get.should eq %(1234a6789b) +end diff --git a/drivers/cisco/meraki/scanning_api.cr b/drivers/cisco/meraki/scanning_api.cr new file mode 100644 index 00000000000..ec45ab57a46 --- /dev/null +++ b/drivers/cisco/meraki/scanning_api.cr @@ -0,0 +1,162 @@ +module Cisco; end + +require "json" + +module Cisco::Meraki + ISO8601 = "%FT%T%z" + + class Client + include JSON::Serializable + + property id : String + property mac : String + property description : String? + + property ip : String? + property ip6 : String? + + @[JSON::Field(key: "ip6Local")] + property ip6_local : String? + + property user : String? + + # 2020-09-29T07:53:08Z + @[JSON::Field(key: "firstSeen")] + property first_seen : String + + @[JSON::Field(key: "lastSeen")] + property last_seen : String + + property manufacturer : String? + property os : String? + + @[JSON::Field(key: "recentDeviceMac")] + property recent_device_mac : String? + property ssid : String? + property vlan : Int32? + property switchport : String? + property status : String + property notes : String? + + @[JSON::Field(ignore: true)] + property! time_added : Time + end + + class RSSI + include JSON::Serializable + + @[JSON::Field(key: "apMac")] + property access_point_mac : String + property rssi : Int32 + end + + class Location + include JSON::Serializable + + # NOTE:: This is not part of the location response, + # it is here to simplify processing + @[JSON::Field(ignore: true)] + property mac : String? + + # NOTE:: this is not part of the location response, + # it is here to speed up processing + @[JSON::Field(ignore: true)] + property client : Client? = nil + + # Multiple types as the location when parsed might include javascript `"NaN"` + property x : Float64 | String | Nil + property y : Float64 | String | Nil + property lng : Float64? + property lat : Float64? + property variance : Float64 + + @[JSON::Field(key: "floorPlanId")] + property floor_plan_id : String? + + @[JSON::Field(key: "floorPlanName")] + property floor_plan_name : String? + + @[JSON::Field(converter: Time::Format.new(Cisco::Meraki::ISO8601))] + property time : Time + + @[JSON::Field(key: "nearestApTags")] + property nearest_ap_tags : Array(String) + + @[JSON::Field(key: "rssiRecords")] + property rssi_records : Array(RSSI) + + def x! + get_x.not_nil! + end + + def y! + get_y.not_nil! + end + + def get_x : Float64? + if tmp = x + if tmp.is_a?(Float64) + tmp + end + end + end + + def get_y : Float64? + if tmp = y + if tmp.is_a?(Float64) + tmp + end + end + end + end + + class LatestRecord + include JSON::Serializable + + @[JSON::Field(key: "nearestApMac")] + property nearest_ap_mac : String + + @[JSON::Field(key: "nearestApRssi")] + property nearest_ap_rssi : Int32 + + @[JSON::Field(converter: Time::Format.new(Cisco::Meraki::ISO8601))] + property time : Time + end + + class Observation + include JSON::Serializable + + @[JSON::Field(key: "clientMac")] + property client_mac : String + + property manufacturer : String? + property ipv4 : String? + property ipv6 : String? + property ssid : String? + property os : String? + + @[JSON::Field(key: "latestRecord")] + property latest_record : LatestRecord + property locations : Array(Location) + end + + class Data + include JSON::Serializable + + @[JSON::Field(key: "networkId")] + property network_id : String + property observations : Array(Observation) + end + + class DevicesSeen + include JSON::Serializable + + property version : String + property secret : String + + @[JSON::Field(key: "type")] + property message_type : String + + property data : Data + end +end diff --git a/drivers/cisco/switch/snooping_catalyst.cr b/drivers/cisco/switch/snooping_catalyst.cr new file mode 100644 index 00000000000..5ac7662860e --- /dev/null +++ b/drivers/cisco/switch/snooping_catalyst.cr @@ -0,0 +1,275 @@ +module Cisco; end + +module Cisco::Switch; end + +require "set" + +class Cisco::Switch::SnoopingCatalyst < PlaceOS::Driver + # Discovery Information + descriptive_name "Cisco Catalyst Switch IP Snooping" + generic_name :Snooping + tcp_port 22 + + # Communication settings + # tokenize delimiter: /\n|-- / + + default_settings({ + ssh: { + username: :cisco, + password: :cisco, + }, + building: "building_code", + ignore_macs: { + "Cisco Phone Dock" => "7001b5", + }, + }) + + # Interfaces that indicate they have a device connected + @check_interface = ::Set(String).new + + # MAC, IP, Interface + @snooping = [] of Tuple(String, String, String) + + # interface to MAC address mappings + @interface_macs = {} of String => String + @devices = {} of String => NamedTuple(mac: String, ip: String) + + @hostname : String? = nil + @switch_name : String? = nil + @ignore_macs = ::Set(String).new + + def on_load + # "--More--" is sent without a newline + transport.tokenizer = Tokenizer.new("\n", "--More--") + + on_update + end + + def on_update + @ignore_macs = ::Set.new((setting?(Hash(String, String), :ignore_macs) || {} of String => String).values) + + self[:name] = @switch_name = setting?(String, :switch_name) + self[:ip_address] = config.ip.not_nil!.downcase + self[:building] = setting?(String, :building) + self[:level] = setting?(String, :level) + self[:last_successful_query] ||= 0 + end + + def connected + schedule.in(1.second) { query_connected_devices } + schedule.every(1.minute) { query_connected_devices } + end + + def disconnected + schedule.clear + queue.clear + end + + # Don't want the every day user using this method + @[Security(Level::Administrator)] + def run(command : String) + do_send command + end + + def query_interface_status + do_send "show interfaces status" + end + + def query_mac_addresses + @interface_macs.clear + do_send "show mac address-table" + end + + def query_snooping_bindings + @snooping.clear + do_send "show ip dhcp snooping binding" + end + + @querying_devices : Bool = false + + def query_connected_devices + return if @querying_devices + @querying_devices = true + + logger.debug { "Querying for connected devices" } + + query_interface_status.get + sleep 3.seconds + + query_mac_addresses.get + sleep 3.seconds + + query_snooping_bindings.get + sleep 2.seconds + + nil + ensure + @querying_devices = false + end + + def received(data, task) + data = String.new(data) + logger.debug { "Switch sent: #{data}" } + + # determine the hostname + if @hostname.nil? + parts = data.split(">") + if parts.size == 2 + self[:hostname] = @hostname = parts[0] + + # Exit early as this line is not a response + return task.try &.success + end + end + + # Detect more data available + # ==> --More-- + if data =~ /More/ + send(" ", priority: 99, retries: 0) + return task.try &.success + end + + # Interface MAC Address detection + # 33 e4b9.7aa5.aa7f STATIC Gi3/0/8 + # 10 f4db.e618.10a4 DYNAMIC Te2/0/40 + if data =~ /STATIC|DYNAMIC/ + parts = data.split(/\s+/).reject(&.empty?) + mac = format(parts[1]) + interface = normalise(parts[-1]) + + @interface_macs[interface] = mac if mac && interface + + return :success + end + + # Interface change detection + # 07-Aug-2014 17:28:26 %LINK-I-Up: gi2 + # 07-Aug-2014 17:28:31 %STP-W-PORTSTATUS: gi2: STP status Forwarding + # 07-Aug-2014 17:44:43 %LINK-I-Up: gi2, aggregated (1) + # 07-Aug-2014 17:44:47 %STP-W-PORTSTATUS: gi2: STP status Forwarding, aggregated (1) + # 07-Aug-2014 17:45:24 %LINK-W-Down: gi2, aggregated (2) + if data =~ /%LINK/ + interface = normalise(data.split(",")[0].split(/\s/)[-1]) + + if data =~ /Up:/ + logger.debug { "Notify Up: #{interface}" } + @check_interface << interface + + # Delay here is to give the PC some time to negotiate an IP address + # schedule.in(3000) { query_snooping_bindings } + elsif data =~ /Down:/ + logger.debug { "Notify Down: #{interface}" } + # We are no longer interested in this interface + @check_interface.delete(interface) + end + + self[:interfaces] = @check_interface + + return task.try &.success + end + + if data.starts_with?("Total number") + logger.debug { "Processing #{@snooping.size} bindings" } + checked = Set(String).new + devices = {} of String => NamedTuple(mac: String, ip: String) + state_changed = false + + @snooping.each do |mac, ip, interface| + next unless @check_interface.includes?(interface) + next unless @interface_macs[interface]? == mac + next if checked.includes?(interface) + + checked << interface + iface = @devices[interface]? || {mac: "", ip: ""} + + if iface[:ip] != ip || iface[:mac] != mac + logger.debug { "New connection on #{interface} with #{ip}: #{mac}" } + devices[interface] = {mac: mac, ip: ip} + state_changed = true + else + devices[interface] = iface + end + end + + # did an interface change state + if state_changed + @devices = devices + self[:devices] = devices + end + + # As a link up or down might have modified this list + if @check_interface != checked + @check_interface = checked + self[:interfaces] = checked + end + + self[:last_successful_query] = Time.utc.to_unix + + return task.try &.success + end + + # Grab the parts of the response + entries = data.split(/\s+/).reject(&.empty?) + + # show interfaces status + # Port Name Status Vlan Duplex Speed Type + # Gi1/1 notconnect 1 auto auto No Gbic + # Fa6/1 connected 1 a-full a-100 10/100BaseTX + if entries.includes?("connected") + interface = entries[0].downcase + return task.try &.success if @check_interface.includes? interface + + logger.debug { "Interface Up: #{interface}" } + @check_interface << interface + + return task.try &.success + elsif entries.includes?("notconnect") + interface = entries[0].downcase + return task.try &.success unless @check_interface.includes? interface + + # Delete the lookup records + logger.debug { "Interface Down: #{interface}" } + @check_interface.delete(interface) + + return task.try &.success + end + + # We are looking for MAC to IP address mappings + # ============================================= + # MacAddress IpAddress Lease(sec) Type VLAN Interface + # ------------------ --------------- ---------- ------------- ---- -------------------- + # 00:21:CC:D5:33:F4 10.151.130.1 16283 dhcp-snooping 113 GigabitEthernet3/0/43 + # Total number of bindings: 3 + if entries.size > 2 + interface = normalise(entries[-1]) + + # We only want entries that are currently active + if @check_interface.includes? interface + # Ensure the data is valid + mac = entries[0] + if mac =~ /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/ + mac = format(mac) + ip = entries[1] + + @snooping << {mac, ip, interface} unless @ignore_macs.includes?(mac[0..5]) + end + end + end + + task.try &.success + end + + protected def do_send(cmd, **options) + logger.debug { "requesting: #{cmd}" } + send("#{cmd}\n", **options) + end + + protected def format(mac) + mac.gsub(/(0x|[^0-9A-Fa-f])*/, "").downcase + end + + protected def normalise(interface) + # Port-channel == po + interface.downcase.gsub("tengigabitethernet", "te").gsub("twogigabitethernet", "tw").gsub("gigabitethernet", "gi").gsub("fastethernet", "fa") + end +end diff --git a/drivers/cisco/switch/snooping_catalyst_spec.cr b/drivers/cisco/switch/snooping_catalyst_spec.cr new file mode 100644 index 00000000000..2cb082d8412 --- /dev/null +++ b/drivers/cisco/switch/snooping_catalyst_spec.cr @@ -0,0 +1,61 @@ +DriverSpecs.mock_driver "Cisco::Switch::SnoopingCatalyst" do + transmit "SG-MARWFA61301>" + sleep 1.5.seconds + + should_send "show interfaces status\n" + transmit "show interfaces status\n" + status[:hostname].should eq("SG-MARWFA61301") + + transmit %(Port Name Status Vlan Duplex Speed Type +Gi1/0/1 notconnect 113 auto auto 10/100/1000BaseTX +Gi1/0/2 notconnect 113 auto auto 10/100/1000BaseTX +Gi2/0/11 notconnect 113 auto auto 10/100/1000BaseTX +Gi2/0/12 notconnect 113 auto auto 10/100/1000BaseTX +Gi2/0/13 notconnect 113 auto auto 10/100/1000BaseTX +Gi2/0/14 notconnect 113 auto auto 10/100/1000BaseTX +Gi2/0/15 notconnect 113 auto auto 10/100/1000BaseTX +Gi2/0/16 notconnect 113 auto auto 10/100/1000BaseTX +Gi2/0/17 notconnect 113 auto auto 10/100/1000BaseTX +Gi3/0/8 connected 33 auto auto 10/100/1000BaseTX + --More--) + + should_send " " + transmit %( +Gi4/0/48 notconnect 113 auto auto 10/100/1000BaseTX +Gi4/1/1 notconnect 1 auto auto unknown +Gi4/1/2 notconnect 1 auto auto unknown +Te4/1/4 connected trunk full 10G SFP-10GBase-SR +Po1 connected trunk a-full a-10G +) + + sleep 3.1.seconds + + should_send "show mac address-table\n" + transmit "show mac address-table\n" + + transmit %(Vlan MAC Type Port +33 e4b9.7aa5.aa7f STATIC Gi3/0/8 +10 f4db.e618.10a4 DYNAMIC Te2/0/40 +) + + sleep 3.1.seconds + + should_send "show ip dhcp snooping binding\n" + transmit %(MacAddress IpAddress Lease(sec) Type VLAN Interface +------------------ --------------- ---------- ------------- ---- -------------------- +38:C9:86:17:A2:07 192.168.1.15 19868 dhcp-snooping 113 tenGigabitEthernet4/1/4 +E4:B9:7A:A5:AA:7F 10.151.128.150 16532 dhcp-snooping 33 GigabitEthernet3/0/8 +00:21:CC:D5:33:F4 10.151.130.1 16283 dhcp-snooping 113 GigabitEthernet3/0/34 +Total number of bindings: 3 + +) + + status["devices"].should eq({ + "gi3/0/8" => { + "mac" => "e4b97aa5aa7f", + "ip" => "10.151.128.150", + }, + }) + + status["interfaces"].should eq(["gi3/0/8"]) +end diff --git a/drivers/epson/projector/esc_vp21.cr b/drivers/epson/projector/esc_vp21.cr new file mode 100644 index 00000000000..0555e875f0b --- /dev/null +++ b/drivers/epson/projector/esc_vp21.cr @@ -0,0 +1,224 @@ +require "placeos-driver/interface/muteable" +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/switchable" + +class Epson::Projector::EscVp21 < PlaceOS::Driver + include Interface::Powerable + include Interface::Muteable + + enum Input + HDMI = 0x30 + HDBaseT = 0x80 + end + + include Interface::InputSelection(Input) + + # Discovery Information + tcp_port 3629 + descriptive_name "Epson Projector" + generic_name :Display + + @power_target : Bool? = nil + @unmute_volume : Int32? = nil + + def on_load + transport.tokenizer = Tokenizer.new("\r") + self[:type] = :projector + end + + def connected + # Have to init comms + send("ESC/VP.net\x10\x03\x00\x00\x00\x00") + schedule.every(52.seconds, true) { do_poll } + end + + def disconnected + schedule.clear + self[:power] = false + end + + def power(state : Bool) + if state + @power_target = true + logger.debug { "-- epson Proj, requested to power on" } + do_send(:power, "ON", delay: 40.seconds, name: "power") + else + @power_target = false + logger.debug { "-- epson Proj, requested to power off" } + do_send(:power, "OFF", delay: 10.seconds, name: "power") + end + power? + end + + def power?(**options) : Bool + do_send(:power, **options, name: :power?).get + !!self[:power]?.try(&.as_bool) + end + + def switch_to(input : Input) + logger.debug { "-- epson Proj, requested to switch to: #{input}" } + do_send(:input, input.value.to_s(16), name: :input) + + # for a responsive UI + self[:input] = input # for a responsive UI + self[:video_mute] = false + input? + end + + def input? + do_send(:input, name: :input_query, priority: 0).get + self[:input] + end + + # Volume commands are sent using the inpt command + def volume(vol : Int32, **options) + vol = vol.clamp(0, 255) + @unmute_volume = self[:volume].as_i if (mute = vol == 0) && self[:volume]? + do_send(:volume, vol, **options, name: :volume) + + # for a responsive UI + self[:volume] = vol + self[:audio_mute] = mute + volume? + end + + def volume? + do_send(:volume, name: :volume?, priority: 0).get + self[:volume]?.try(&.as_i) + end + + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + case layer + when .audio_video? + do_send(:av_mute, state ? "ON" : "OFF", name: :mute) + do_send(:av_mute, name: :mute?, priority: 0) + when .video? + do_send(:video_mute, state ? "ON" : "OFF", name: :video_mute) + video_mute? + when .audio? + val = state ? 0 : @unmute_volume.not_nil! + volume(val) + end + end + + def video_mute? + do_send(:video_mute, name: :video_mute?, priority: 0).get + !!self[:video_mute]?.try(&.as_bool) + end + + ERRORS = [ + "00: no error", + "01: fan error", + "03: lamp failure at power on", + "04: high internal temperature", + "06: lamp error", + "07: lamp cover door open", + "08: cinema filter error", + "09: capacitor is disconnected", + "0A: auto iris error", + "0B: subsystem error", + "0C: low air flow error", + "0D: air flow sensor error", + "0E: ballast power supply error", + "0F: shutter error", + "10: peltiert cooling error", + "11: pump cooling error", + "12: static iris error", + "13: power supply unit error", + "14: exhaust shutter error", + "15: obstacle detection error", + "16: IF board discernment error", + ] + + def inspect_error + do_send(:error, priority: 0) + end + + COMMAND = { + power: "PWR", + input: "SOURCE", + volume: "VOL", + av_mute: "MUTE", + video_mute: "MSEL", + error: "ERR", + lamp: "LAMP", + } + RESPONSE = COMMAND.to_h.invert + + def received(data, task) + return task.try(&.success) if data.size <= 2 + data = String.new(data[1..-2]) + logger.debug { "epson Proj sent: #{data}" } + + data = data.split('=') + case RESPONSE[data[0]] + when :error + if data[1]? + code = data[1].to_i(16) + self[:last_error] = ERRORS[code]? || "#{data[1]}: unknown error code #{code}" + return task.try(&.success("Epson PJ error was #{self[:last_error]}")) + else # Lookup error! + return task.try(&.abort("Epson PJ sent error response for #{task.not_nil!.name || "unknown"}")) + end + when :power + state = data[1].to_i + self[:power] = state < 3 + self[:warming] = state == 2 + self[:cooling] = state == 3 + + if self[:warming].as_bool || self[:cooling].as_bool + schedule.in(5.seconds) { power?(priority: 0) } + end + + if (power_target = @power_target) && self[:power] == power_target + @power_target = nil + self[:video_mute] = false unless self[:power].as_bool + end + when :av_mute + self[:video_mute] = self[:audio_mute] = data[1] == "ON" + self[:volume] = 0 + when :video_mute + self[:video_mute] = data[1] == "ON" + when :volume + vol = data[1].to_i + self[:volume] = vol + mute = vol == 0 + self[:audio_mute] = mute if mute + @unmute_volume ||= vol unless mute + when :lamp + self[:lamp_usage] = data[1].to_i + when :input + self[:input] = Input.from_value(data[1].to_i(16)) || "unknown" + end + + task.try(&.success) + end + + def do_poll + if power?(priority: 0) + if power_target = @power_target + if self[:power]? != power_target + power(power_target) + else + @power_target = nil + end + else + input? + video_mute? + volume? + end + end + do_send(:lamp, priority: 0) + end + + private def do_send(command, param = nil, **options) + command = COMMAND[command] + cmd = param ? "#{command} #{param}\r" : "#{command}?\r" + logger.debug { "Epson proj sending #{command}: #{cmd}" } + send(cmd, **options) + end +end diff --git a/drivers/epson/projector/esc_vp21_spec.cr b/drivers/epson/projector/esc_vp21_spec.cr new file mode 100644 index 00000000000..a37f904101b --- /dev/null +++ b/drivers/epson/projector/esc_vp21_spec.cr @@ -0,0 +1,59 @@ +DriverSpecs.mock_driver "Epson::Projector::EscVp21" do + # connected + should_send("ESC/VP.net\x10\x03\x00\x00\x00\x00") + responds(":\r") + # do_poll + # power? + should_send("PWR?\r") + responds(":PWR=01\r") + status[:power].should eq(true) + # input? + should_send("SOURCE?\r") + responds(":SOURCE=30\r") + status[:input].should eq("HDMI") + # video_mute? + should_send("MSEL?\r") + responds(":MSEL=0\r") + status[:video_mute].should eq(false) + # volume? + should_send("VOL?\r") + responds(":VOL=10\r") + status[:volume].should eq(10) + # lamp + should_send("LAMP?\r") + responds(":LAMP=20\r") + status[:lamp_usage].should eq(20) + + exec(:mute) + should_send("MUTE ON\r") + responds(":\r") + should_send("MUTE?\r") + responds(":MUTE=ON\r") + status[:video_mute].should eq(true) + status[:audio_mute].should eq(true) + status[:volume].should eq(0) + + exec(:switch_to, "HDBaseT") + should_send("SOURCE 80\r") + responds(":\r") + should_send("SOURCE?\r") + responds(":SOURCE=80\r") + status[:input].should eq("HDBaseT") + status[:video_mute].should eq(false) + + exec(:mute_audio, false) + should_send("VOL 10\r") + responds(":\r") + should_send("VOL?\r") + responds(":VOL=10\r") + status[:volume].should eq(10) + status[:audio_mute].should eq(false) + + exec(:volume, 50) + should_send("VOL 50\r") + responds(":\r") + should_send("VOL?\r") + responds(":VOL=50\r") + status[:volume].should eq(50) + status[:audio_mute].should eq(false) +end diff --git a/drivers/extron/matrix.cr b/drivers/extron/matrix.cr new file mode 100644 index 00000000000..134836cacce --- /dev/null +++ b/drivers/extron/matrix.cr @@ -0,0 +1,118 @@ +require "./sis" + +class Extron::Matrix < PlaceOS::Driver + include Extron::SIS + + generic_name :Switcher + descriptive_name "Extron matrix switcher" + description "Audio-visual signal distribution device" + tcp_port SSH_PORT + + def on_load + transport.tokenizer = Tokenizer.new DELIMITER + end + + def connected + send Command['I'], Response::SwitcherInformation do |info| + @device_size = info + end + end + + def disconnected + @device_size = nil + end + + getter device_size do + empty = MatrixSize.new 0, 0 + SwitcherInformation.new empty, empty + end + + alias Outputs = Array(Output) + + alias SignalMap = Hash(Input, Output | Outputs) + + # Connect a signal *input* to an *output* at the specified *layer*. + # + # `0` may be used as either an input or output to specify a disconnection at + # the corresponding signal point. For example, to disconnect input 1 from all + # outputs is is currently feeding `switch(1, 0)`. + def switch(input : Input, output : Output, layer : SwitchLayer = SwitchLayer::All) + send Command[input, '*', output, layer], Response::Tie, &->update_io(Tie) + end + + # Connect *input* to all outputs at the specified *layer*. + def switch_to(input : Input, layer : SwitchLayer = SwitchLayer::All) + send Command[input, '*', layer], Response::Switch, &->update_io(Switch) + end + + # Applies a `SignalMap` as a single operation. All included ties will take + # simultaneously on the device. + def switch_map(map : SignalMap, layer : SwitchLayer = SwitchLayer::All) + ties = map.flat_map do |(input, outputs)| + if outputs.is_a? Enumerable + outputs.each.map { |output| Tie.new input, output, layer } + else + Tie.new input, outputs, layer + end + end + + conflicts = ties - ties.uniq(&.output) + unless conflicts.empty? + raise ArgumentError.new "map contains conflicts for output(s) #{conflicts.map(&.output).join ","}" + end + + send Command["\e+Q", ties.map { |tie| [tie.input, '*', tie.output, tie.layer] }, '\r'], Response::Qik do + ties.each &->update_io(Tie) + end + end + + # Send *command* to the device and yield a parsed response to *block*. + private def send(command, parser : SIS::Response::Parser(T), &block : T -> _) forall T + send command do |data, task| + case response = Response.parse data, parser + in T + task.success block.call response + in Error + response.retryable? ? task.retry response : task.abort response + in Response::ParseError + task.abort response + end + end + end + + private def send(command, parser : SIS::Response::Parser(T)) forall T + send command, parser, &.itself + end + + # Response callback for async responses. + def received(data, task) + case response = Response.parse data, as: Response::Unsolicited + in Tie + update_io response + in Error, Response::ParseError + logger.error { response } + in Ok + # Nothing to see here, one of the Ignorable responses + logger.debug { response } + end + end + + private def update_io(input : Input, output : Output, layer : SwitchLayer) + self["audio#{output}"] = input if layer.includes_audio? + self["video#{output}"] = input if layer.includes_video? + end + + private def update_io(tie : Tie) + update_io tie.input, tie.output, tie.layer + end + + # Update exposed driver state to include *switch*. + private def update_io(switch : Switch) + if switch.layer.includes_video? + device_size.video.outputs.times { |o| update_io switch.input, Output.new(o + 1), SwitchLayer::Vid } + end + if switch.layer.includes_audio? + device_size.audio.outputs.times { |o| update_io switch.input, Output.new(o + 1), SwitchLayer::Aud } + end + end +end diff --git a/drivers/extron/matrix_spec.cr b/drivers/extron/matrix_spec.cr new file mode 100644 index 00000000000..42729aa37ea --- /dev/null +++ b/drivers/extron/matrix_spec.cr @@ -0,0 +1,39 @@ +DriverSpecs.mock_driver "Extron::Matrix" do + should_send "I" + responds "V8X4 A8X4\r\n" + + switch = exec :switch, input: 3, output: 2 + should_send "3*2!" + responds "Out2 In3 All\r\n" + status["video2"].should eq 3 + + switch_to = exec :switch_to, input: 2 + should_send "2*!" + responds "In2 All\r\n" + status["video1"].should eq 2 + status["video2"].should eq 2 + status["video3"].should eq 2 + status["video4"].should eq 2 + status["audio1"].should eq 2 + status["audio2"].should eq 2 + status["audio3"].should eq 2 + status["audio4"].should eq 2 + + switch_map = exec :switch_map, {1 => [2, 3, 4]} + should_send "\e+Q1*2!1*3!1*4!\r" + responds "Qik\r\n" + status["video2"].should eq 1 + status["video3"].should eq 1 + status["video4"].should eq 1 + + expect_raises PlaceOS::Driver::RemoteException do + conflict = exec :switch_map, {1 => 1, 2 => 1} + conflict.get + end + + expect_raises PlaceOS::Driver::RemoteException do + invalid = exec :switch_to, input: 999 + responds "E01\r\n" + invalid.get + end +end diff --git a/drivers/extron/sis.cr b/drivers/extron/sis.cr new file mode 100644 index 00000000000..fcfad4afcd5 --- /dev/null +++ b/drivers/extron/sis.cr @@ -0,0 +1,73 @@ +require "./sis/*" + +# Implementation, types and utilities for working with the Extron Simple +# Instruction Set (SIS) device control protocol. +# +# This protocol is used for control of all Extron signal distribution, +# processing and general audio-visual products via SSH, telnet and serial +# control. +module Extron::SIS + TELNET_PORT = 21 + SSH_PORT = 22023 + + DELIMITER = "\r\n" + + # Illegal characters for use in property names. + SPECIAL_CHARS = "+-,@=‘[]{}<>`“;:|?".chars + + # Symbolic type for representating a successfull interactions no useful data. + struct Ok; end + + # Device error numbers + enum Error + InvalidInput = 1 + InvalidCommand = 10 + InvalidPresent = 11 + InvalidOutput = 12 + InvalidParameter = 13 + InvalidForConfig = 14 + Timeout = 17 + Busy = 22 + PrivilegesViolation = 24 + DeviceNotPresent = 25 + MaxConnectionsExceeded = 26 + InvalidEventNumber = 27 + FileNotFound = 28 + + def retryable? + timeout? || busy? + end + end + + alias Input = UInt16 + + alias Output = UInt16 + + # Layers for targetting signal distribution operations. + enum SwitchLayer : UInt8 + All = 0x21 # '!' + Aud = 0x24 # '$' + Vid = 0x25 # '%' + RGB = 0x26 # '&' + + def includes_video? + All || Vid || RGB + end + + def includes_audio? + All || Aud + end + end + + # Struct for representing a matrix signal path. + record Tie, input : Input, output : Output, layer : SwitchLayer + + # Struct for representing a broadcast signal path, or single output switch. + record Switch, input : Input, layer : SwitchLayer + + # IO capacity for a switching layer. + record MatrixSize, inputs : Input, outputs : Output + + # IO capacity for a full device. + record SwitcherInformation, video : MatrixSize, audio : MatrixSize +end diff --git a/drivers/extron/sis/command.cr b/drivers/extron/sis/command.cr new file mode 100644 index 00000000000..87e710b0a4c --- /dev/null +++ b/drivers/extron/sis/command.cr @@ -0,0 +1,34 @@ +# Structure for representing a SIS device command. +# +# Commands are composed from a set of *fields*. The contents and types of these +# are arbitrary, however they must be capable of serialising to an IO. +struct Extron::SIS::Command(*T) + def initialize(*fields : *T) + @fields = fields + end + + # Serialises `self` in a format suitable for log messages. + def to_s(io : IO) + io << '‹' + to_io io + io << '›' + end + + # Writes `self` to the passed *io*. + def to_io(io : IO, format = IO::ByteFormat::SystemEndian) + @fields.each.flatten.each do |field| + if field.is_a? Enum + io.write_byte field.value + else + io << field + end + end + end + + # Syntactical suger for `Command` definition. Provides the ability to express + # command fields in the same way as `Byte` objects and other similar + # collections from the Crystal std lib. + macro [](*fields) + Extron::SIS::Command.new({{*fields}}) + end +end diff --git a/drivers/extron/sis/response.cr b/drivers/extron/sis/response.cr new file mode 100644 index 00000000000..79df4123cb2 --- /dev/null +++ b/drivers/extron/sis/response.cr @@ -0,0 +1,91 @@ +require "pars" + +# Parsers for responses and asynchronous messages originating from Extron SIS +# devices. +module Extron::SIS::Response + include Pars + + # Parses a response packet with specified *parser*. + # + # Returns the parser output, a parse error or a device error. + def self.parse(data : String, as parser : Parser(T)) forall T + (parser | DeviceError | "unhandled device response").parse data + end + + # :ditto: + def self.parse(data : Bytes, as parser : Parser(T)) forall T + parse String.new(data), parser + end + + # Parses a number from the input into *type*. + private def self.num(type : T.class) forall T + Parse.integer.map &->T.new(String) + end + + # Parses a word from the input into an enum of *type*. + private def self.word_as_enum(type : T.class) forall T + Parse.word.map &->T.parse(String) + end + + # :nodoc: + Delimiter = Parse.string SIS::DELIMITER + + # Parse a full command response as a String. Delimiter is optional as it may + # have already been dropped by an upstream tokenizer. + Raw = ((Parse.char ^ Delimiter) * (0..) << Delimiter * (0..1)).map &.join + + # Error codes returned from the device. + DeviceError = Parse.char('E') >> Parse.integer.map { |e| SIS::Error.new e.to_i } + + # Copyright message shown on connect. + Copyright = (Parse.string("(c) Copyright") + Raw).map &.join + + # Part of the copyright banner, but appears on a new line so will tokenize as + # as standalone message. + Clock = Raw.map { |date| Time.parse_utc date, "%a, %b %d, %Y, %T" } + + # Quick response, occurs following quick tie, or switching interaction from + # the device's front panel. + Qik = Parse.string("Qik") >> Parse.const(Ok.new) + + # Matrix signal route update. + Tie = Parse.do({ + output <= Parse.string("Out") >> num(Output), + _ <= Parse.char(' '), + input <= Parse.string("In") >> num(Input), + _ <= Parse.char(' '), + layer <= word_as_enum(SwitchLayer), + Parse.const SIS::Tie.new input, output, layer, + }) + + # Broadcast or single output route update. + Switch = Parse.do({ + input <= Parse.string("In") >> num(Input), + _ <= Parse.char(' '), + layer <= word_as_enum(SwitchLayer), + Parse.const SIS::Switch.new input, layer, + }) + + MatrixSize = Parse.do({ + inputs <= num(Input), + _ <= Parse.char('X'), + outputs <= num(Output), + Parse.const SIS::MatrixSize.new inputs, outputs, + }) + + SwitcherInformation = Parse.do({ + _ <= Parse.char('V'), + video <= MatrixSize, + _ <= Parse.char(' '), + _ <= Parse.char('A'), + audio <= MatrixSize, + Parse.const SIS::SwitcherInformation.new video, audio, + }) + + # Parses for device messages that can be safely ignored - these exist mainly + # to flush initial connect banners + Ignorable = (Copyright | Clock) >> Parse.const(Ok.new) + + # Async messages that can be expected outside of a command -> response flow. + Unsolicited = DeviceError | Tie | Ignorable +end diff --git a/drivers/extron/sis_spec.cr b/drivers/extron/sis_spec.cr new file mode 100644 index 00000000000..e61c580b54e --- /dev/null +++ b/drivers/extron/sis_spec.cr @@ -0,0 +1,104 @@ +require "spec" +require "./sis" + +include Extron::SIS + +describe Command do + it "forms a Command from arbitrary field types" do + command = Command.new 42, 'a', "foo" + end + + it "serialises the command to an IO" do + command = Command[1, '*', 2, SwitchLayer::All] + io = IO::Memory.new + io.write_bytes command + io.to_s.should eq("1*2!") + end + + it "provides a string representation suitable for logging" do + command = Command[1, '*', 2, SwitchLayer::All] + command.to_s.should eq("‹1*2!›") + end + + it "flattens nested fields" do + routes = [ + [1, '*', 2, SwitchLayer::All], + [3, '*', 4, SwitchLayer::All], + ] + command = Command["\e+Q", routes, '\r'] + io = IO::Memory.new + io.write_bytes command + io.to_s.should eq("\e+Q1*2!3*4!\r") + end +end + +describe Response do + describe Response::DeviceError do + it "parses to a SIS::Error" do + error = Response::DeviceError.parse "E17" + error.should eq(Error::Timeout) + end + end + + describe Response::Copyright do + it "parses and provides the full banner" do + message = "(c) Copyright YYYY, Extron Electronics, Model Name, Vx.xx, nn-nnnn-nn" + parsed = Response::Copyright.parse message + parsed.should be_a(String) + parsed.should eq(message) + end + + it "does not parse other messages" do + parsed = Response::Copyright.parse "foo" + parsed.should be_a(Response::ParseError) + end + end + + describe Response::Clock do + it "parses" do + clock = "Fri, Feb 13, 2009, 23:31:30" + parsed = Response::Clock.parse clock + parsed.as(Time).to_unix.should eq(1234567890) + end + end + + describe Response::Tie do + it "parses" do + tie = Response::Tie.parse "Out2 In1 All" + tie.should be_a Tie + tie = tie.as Tie + tie.input.should eq(1) + tie.output.should eq(2) + tie.layer.should eq(SwitchLayer::All) + end + end + + describe Response::Switch do + it "parses" do + tie = Response::Switch.parse "In1 All" + tie.should be_a Switch + tie = tie.as Switch + tie.input.should eq(1) + tie.layer.should eq(SwitchLayer::All) + end + end + + describe Response::SwitcherInformation do + it "parses" do + info = Response::SwitcherInformation.parse "V1X2 A3X4" + info.should be_a SwitcherInformation + info = info.as SwitcherInformation + info.video.inputs.should eq 1 + info.video.outputs.should eq 2 + info.audio.inputs.should eq 3 + info.audio.outputs.should eq 4 + end + end + + describe ".parse" do + it "builds a parser that includes device errors" do + resp = Response.parse "Out4 In2 Aud", as: Response::Tie + typeof(resp).should eq (Tie | Error | Response::ParseError) + end + end +end diff --git a/drivers/floorsense/desks.cr b/drivers/floorsense/desks.cr new file mode 100644 index 00000000000..630d5b2b15e --- /dev/null +++ b/drivers/floorsense/desks.cr @@ -0,0 +1,137 @@ +require "uri" +require "jwt" +require "./models" + +module Floorsense; end + +# Documentation: +# https://apiguide.smartalock.com/ +# https://documenter.getpostman.com/view/8843075/SVmwvctF?version=latest#3bfbb050-722d-4433-889a-8793fa90af9c + +class Floorsense::Desks < PlaceOS::Driver + # Discovery Information + generic_name :Floorsense + descriptive_name "Floorsense Desk Tracking" + + default_settings({ + username: "srvc_acct", + password: "password!", + }) + + @username : String = "" + @password : String = "" + @auth_token : String = "" + @auth_expiry : Time = 1.minute.ago + + def on_load + on_update + end + + def on_update + @username = URI.encode_www_form setting(String, :username) + @password = URI.encode_www_form setting(String, :password) + end + + def expire_token! + @auth_expiry = 1.minute.ago + end + + def token_expired? + now = Time.utc + @auth_expiry < now + end + + def get_token + return @auth_token unless token_expired? + + response = post("/restapi/login", body: "username=#{@username}&password=#{@password}", headers: { + "Content-Type" => "application/x-www-form-urlencoded", + "Accept" => "application/json", + }) + + data = response.body.not_nil! + logger.debug { "received login response #{data}" } + + if response.success? + resp = AuthResponse.from_json(data) + token = resp.info.not_nil!.token + payload, _ = JWT.decode(token, verify: false, validate: false) + @auth_expiry = (Time.unix payload["exp"].as_i64) - 5.minutes + @auth_token = "Bearer #{token}" + else + case response.status_code + when 401 + resp = AuthResponse.from_json(data) + logger.warn { "#{resp.message} (#{resp.code})" } + else + logger.error { "authentication failed with HTTP #{response.status_code}" } + end + raise "failed to obtain access token" + end + end + + def floors + token = get_token + uri = "/restapi/floorplan-list" + + response = get(uri, headers: { + "Accept" => "application/json", + "Authorization" => token, + }) + + if response.success? + check_response FloorsResponse.from_json(response.body.not_nil!) + else + expire_token! if response.status_code == 401 + raise "unexpected response #{response.status_code}\n#{response.body}" + end + end + + def desks(plan_id : String) + token = get_token + uri = "/restapi/floorplan-desk?planid=#{plan_id}" + + response = get(uri, headers: { + "Accept" => "application/json", + "Authorization" => token, + }) + + if response.success? + check_response DesksResponse.from_json(response.body.not_nil!) + else + expire_token! if response.status_code == 401 + raise "unexpected response #{response.status_code}\n#{response.body}" + end + end + + def locate(key : String, controller_id : String? = nil) + token = get_token + uri = if controller_id + "/restapi/user-locate?cid=#{controller_id}&key=#{URI.encode_www_form key}" + else + "/restapi/user-locate?name=#{URI.encode_www_form key}" + end + + response = get(uri, headers: { + "Accept" => "application/json", + "Authorization" => token, + }) + + if response.success? + resp = LocateResponse.from_json(response.body.not_nil!) + # Select users where there is a desk key found + check_response(resp).select(&.key) + else + expire_token! if response.status_code == 401 + raise "unexpected response #{response.status_code}\n#{response.body}" + end + end + + protected def check_response(resp) + if resp.result + resp.info.not_nil! + else + raise "bad response result (#{resp.code}) #{resp.message}" + end + end +end diff --git a/drivers/floorsense/desks_spec.cr b/drivers/floorsense/desks_spec.cr new file mode 100644 index 00000000000..c337a454df1 --- /dev/null +++ b/drivers/floorsense/desks_spec.cr @@ -0,0 +1,25 @@ +DriverSpecs.mock_driver "Floorsense::Desks" do + # Send the request + retval = exec(:get_token) + + # We should request a new token from Floorsense + expect_http_request do |request, response| + if io = request.body + data = io.gets_to_end + + # The request is param encoded + if data == "username=srvc_acct&password=password%21" + response.status_code = 200 + response.output.puts %({"type":"response","result":true,"message":"Authentication successful","info":{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbWFydGFsb2NrLWQ1MGJjZC5sb2NhbGRvbWFpbiIsInN1YiI6ImFjYSIsImF1ZCI6ImFwaSIsImV4cCI6MTU3MjMwODMzMiwiaWF0IjoxNTcyMzA0NzMyfQ.KMlzvjYPFw9e5d5LQjb1BF5R1Je9KkgoigkNOUZnR4U","sessionid":"ace555fe-4914-4203-b0a3-a1a6f532fef7"}}) + else + response.status_code = 401 + response.output.puts %({"type":"response","result":false,"message":"Authentication failed","code":17}) + end + else + raise "expected request to include username and password" + end + end + + # What the function should return (for use in making further requests) + retval.get.should eq("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbWFydGFsb2NrLWQ1MGJjZC5sb2NhbGRvbWFpbiIsInN1YiI6ImFjYSIsImF1ZCI6ImFwaSIsImV4cCI6MTU3MjMwODMzMiwiaWF0IjoxNTcyMzA0NzMyfQ.KMlzvjYPFw9e5d5LQjb1BF5R1Je9KkgoigkNOUZnR4U") +end diff --git a/drivers/floorsense/location_service.cr b/drivers/floorsense/location_service.cr new file mode 100644 index 00000000000..a635d6938d4 --- /dev/null +++ b/drivers/floorsense/location_service.cr @@ -0,0 +1,91 @@ +module Floorsense; end + +require "json" +require "oauth2" +require "placeos-driver/interface/locatable" +require "./models" + +class Floorsense::LocationService < PlaceOS::Driver + include Interface::Locatable + + descriptive_name "Floorsense Location Service" + generic_name :FloorsenseLocationService + description %(collects desk booking data from the staff API and overlays Floorsense data for visualising on a map) + + accessor floorsense : Floorsense_1 + + default_settings({ + floor_mappings: { + "planid": { + building_id: "zone-building", + level_id: "zone-level", + name: "friendly name for documentation", + }, + }, + }) + + @floor_mappings : Hash(String, NamedTuple(building_id: String?, level_id: String)) = {} of String => NamedTuple(building_id: String?, level_id: String) + # Level zone => plan_id + @zone_mappings : Hash(String, String) = {} of String => String + # Level zone => building_zone + @building_mappings : Hash(String, String?) = {} of String => String? + + def on_load + on_update + end + + def on_update + @floor_mappings = setting(Hash(String, NamedTuple(building_id: String?, level_id: String)), :floor_mappings) + @floor_mappings.each do |plan_id, details| + level = details[:level_id] + @building_mappings[level] = details[:building_id] + @zone_mappings[level] = plan_id + end + end + + # =================================== + # Locatable Interface functions + # =================================== + def locate_user(email : String? = nil, username : String? = nil) + logger.debug { "sensor incapable of locating #{email} or #{username}" } + [] of Nil + end + + def macs_assigned_to(email : String? = nil, username : String? = nil) : Array(String) + logger.debug { "sensor incapable of tracking #{email} or #{username}" } + [] of String + end + + def check_ownership_of(mac_address : String) : OwnershipMAC? + logger.debug { "sensor incapable of tracking #{mac_address}" } + nil + end + + def device_locations(zone_id : String, location : String? = nil) + logger.debug { "searching locatable in zone #{zone_id}" } + return [] of Nil if location && location != "desk" + + plan_id = @zone_mappings[zone_id]? + return [] of Nil unless plan_id + + raw_desks = floorsense.desks(plan_id).get.to_json + Array(DeskStatus).from_json(raw_desks).compact_map do |desk| + if desk.occupied + { + location: "desk", + at_location: desk.occupied ? 1 : 0, + map_id: desk.key, + level: zone_id, + building: @building_mappings[zone_id]?, + capacity: 1, + + # So we can look up who is at a desk at some point in the future + mac: "cid:#{desk.cid}-#{desk.key}", + + floorsense_status: desk.status, + floorsense_desk_type: desk.desk_type, + } + end + end + end +end diff --git a/drivers/floorsense/location_service_spec.cr b/drivers/floorsense/location_service_spec.cr new file mode 100644 index 00000000000..96c845c5e56 --- /dev/null +++ b/drivers/floorsense/location_service_spec.cr @@ -0,0 +1,69 @@ +DriverSpecs.mock_driver "Floorsense::LocationService" do + system({ + Floorsense: {Floorsense}, + }) + + resp = exec(:device_locations, "zone-level").get + resp.should eq([ + {"location" => "desk", "at_location" => 0, "map_id" => "915-09", "level" => "zone-level", "building" => "zone-building", "capacity" => 1, "mac" => "cid:14-915-09", "floorsense_status" => 17, "floorsense_desk_type" => "a"}, + {"location" => "desk", "at_location" => 1, "map_id" => "D403-01", "level" => "zone-level", "building" => "zone-building", "capacity" => 1, "mac" => "cid:3-D403-01", "floorsense_status" => 17, "floorsense_desk_type" => "a"}, + ]) +end + +class Floorsense < DriverSpecs::MockDriver + def desks(plan_id : String) + JSON.parse %([ + { + "cid": 14, + "status": 17, + "cached": true, + "eui64": "00124b0018ae56d0", + "occupied": false, + "freq": "915", + "groupid": 0, + "netid": 3, + "key": "915-09", + "reservable": true, + "bkid": "", + "deskid": 2, + "hwfeat": 0, + "created": 1568887923, + "hardware": "E-20", + "firmware": "401", + "type": "a", + "planid": 6, + "reserved": false, + "features": 0, + "confirmed": false, + "privacy": false, + "uid": "", + "occupiedtime": 0 + }, + { + "cid": 3, + "status": 17, + "cached": true, + "eui64": "00124b0018ae54e5", + "occupied": true, + "freq": "", + "groupid": 0, + "netid": 2, + "key": "D403-01", + "reservable": false, + "bkid": "", + "deskid": 129, + "hwfeat": 0, + "created": 1568887941, + "hardware": "", + "firmware": "", + "type": "a", + "planid": 6, + "reserved": false, + "features": 0, + "confirmed": false, + "privacy": false, + "uid": "", + "occupiedtime": 0 + }]) + end +end diff --git a/drivers/floorsense/models.cr b/drivers/floorsense/models.cr new file mode 100644 index 00000000000..00948d5de12 --- /dev/null +++ b/drivers/floorsense/models.cr @@ -0,0 +1,147 @@ +require "json" + +# Floorsense Data Models +module Floorsense + class AuthResponse + include JSON::Serializable + + class Info + include JSON::Serializable + + property token : String + property sessionid : String + end + + @[JSON::Field(key: "type")] + property msg_type : String + property result : Bool + property message : String? + + # Returned on failure + property code : Int32? + + # Returned on success + property info : Info? + end + + class DeskStatus + include JSON::Serializable + + property cid : Int32 + property cached : Bool + property reservable : Bool + property netid : Int32 + property status : Int32 + property deskid : Int32 + + property hwfeat : Int32 + property hardware : String + + @[JSON::Field(converter: Time::EpochConverter)] + property created : Time + property key : String + property occupied : Bool + property uid : String + property eui64 : String + + @[JSON::Field(key: "type")] + property desk_type : String + property firmware : String + property features : Int32 + property freq : String + property groupid : Int32 + property bkid : String + property planid : Int32 + property reserved : Bool + property confirmed : Bool + property privacy : Bool + property occupiedtime : Int32 + end + + class DesksResponse + include JSON::Serializable + + @[JSON::Field(key: "type")] + property msg_type : String + property result : Bool + + # Returned on failure + property message : String? + property code : Int32? + + # Returned on success + property info : Array(DeskStatus)? + end + + class UserLocation + include JSON::Serializable + + property name : String + property uid : String + + # Optional properties (when a user is located): + + @[JSON::Field(converter: Time::EpochConverter)] + property start : Time? + + @[JSON::Field(converter: Time::EpochConverter)] + property finish : Time? + + property planid : Int32? + property occupied : Bool? + property groupid : Int32? + property key : String? + property floorname : String? + property cid : Int32? + property occupiedtime : Int32? + property groupname : String? + property privacy : Bool? + property confirmed : Bool? + property active : Bool? + end + + class LocateResponse + include JSON::Serializable + + @[JSON::Field(key: "type")] + property msg_type : String + property result : Bool + + # Returned on failure + property message : String? + property code : Int32? + + # Returned on success + property info : Array(UserLocation)? + end + + class Floor + include JSON::Serializable + + property planid : Int32 + property name : String + + property imgname : String? + property imgwidth : Int32? + property imgheight : Int32? + + property location1 : String? + property location2 : String? + property location3 : String? + end + + class FloorsResponse + include JSON::Serializable + + @[JSON::Field(key: "type")] + property msg_type : String + property result : Bool + + # Returned on failure + property message : String? + property code : Int32? + + # Returned on success + property info : Array(Floor)? + end +end diff --git a/drivers/gantner/relaxx/json_models.cr b/drivers/gantner/relaxx/json_models.cr new file mode 100644 index 00000000000..c9e5fa66712 --- /dev/null +++ b/drivers/gantner/relaxx/json_models.cr @@ -0,0 +1,137 @@ +require "json" + +module Gantner; end + +module Gantner::Relaxx + class Result + include JSON::Serializable + + @[JSON::Field(key: "Successful")] + property successful : Bool + + @[JSON::Field(key: "Cancelled")] + property cancelled : Bool + + @[JSON::Field(key: "ResultText")] + property text : String + + @[JSON::Field(key: "ResultCode")] + property code : Int32 + end + + enum LockerState + Unknown = 0 + Disabled + Free + InUse + Locked + Alarmed + InUseExpired + Conflict + end + + enum LockerMode + Unknown = 0 + NotExisting + FreeLocker + PersonalLocker + ReservableLocker + DynamicLocker + end + + class Locker + include JSON::Serializable + + @[JSON::Field(key: "RecordId")] + property id : String + + @[JSON::Field(key: "LockerGroupId")] + property group_id : String + + @[JSON::Field(key: "LockerGroupName")] + property group_name : String + + @[JSON::Field(key: "Number")] + property locker_number : String + + @[JSON::Field(key: "Address")] + property address : Int32 + + @[JSON::Field(key: "State")] + property state : Int32 + + @[JSON::Field(key: "LockerMode")] + property mode : Int32 + + # Is it a personal locker or a free (no cost?) locker? + @[JSON::Field(key: "IsFreeLocker")] + property is_free : Bool + + @[JSON::Field(key: "IsDeleted")] + property is_deleted : Bool + + @[JSON::Field(key: "IsExisting")] + property is_existing : Bool + + @[JSON::Field(key: "LastClosedTime")] + property last_closed : String + + @[JSON::Field(key: "CardUIDInUse")] + property card_id : String + + def locker_state + LockerState.from_value self.state + end + + def locker_mode + LockerMode.from_value self.mode + end + end + + enum LockerEvent + Opened = 0 + Closed + Enabled + Disabled + Alarmed + end + + class LockerNotification + include JSON::Serializable + + @[JSON::Field(key: "Event")] + property event : Int32 + + @[JSON::Field(key: "PreviousState")] + property prev_state : Int32 + + @[JSON::Field(key: "EventDateTime")] + property time : String + + @[JSON::Field(key: "Locker")] + property locker : Locker + + @[JSON::Field(key: "LockerAreaId")] + property area_id : String + + @[JSON::Field(key: "LockerAreaName")] + property area_name : String + + @[JSON::Field(key: "WithMasterCard")] + property group_name : Bool + + @[JSON::Field(key: "WithSystemCard")] + property group_name : Bool + + @[JSON::Field(key: "WithMaintenanceCard")] + property group_name : Bool + + def locker_state + self.locker.state + end + + def previous_state + LockerState.from_value self.prev_state + end + end +end diff --git a/drivers/gantner/relaxx/protocol_json.cr b/drivers/gantner/relaxx/protocol_json.cr new file mode 100644 index 00000000000..f7c38b38c62 --- /dev/null +++ b/drivers/gantner/relaxx/protocol_json.cr @@ -0,0 +1,237 @@ +module Gantner; end + +require "openssl/cipher" +require "./json_models" +require "base64" +require "uuid" +require "set" + +# Documentation: https://aca.im/driver_docs/gantner/GAT-Relaxx-JSON-Interface-Description-2.10.pdf +# REST Docs: https://doc.gantner.com/RelaxxDocs/GatRelaxxRestAPI.yaml +# https://aca.im/driver_docs/gantner/GatRelaxxRestAPI.yaml +# PC Application +# User = Administrator +# PW = Mirone59 + +class Gantner::Relaxx::ProtocolJSON < PlaceOS::Driver + # Discovery Information + tcp_port 8237 + descriptive_name "Gantner GAT Relaxx JSON API" + generic_name :Lockers + + @authenticated : Bool = false + @password : String = "GAT" + + # Lists of locker IDs + @locker_ids : Set(String) = Set(String).new + @lockers_in_use : Set(String) = Set(String).new + + def on_load + # 0x02 (Start of frame) and 0x03 (End of frame) + transport.tokenizer = Tokenizer.new(Bytes[0x03]) + on_update + end + + def on_update + @password = setting?(String, :password) || "GAT" + end + + # Converts the data to bytes and wraps it into a frame + private def send_frame(data, **options) + logger.debug { "requesting #{data[:Caption]}, id #{data[:Id]}" } + send "\x02#{data.to_json}\x03", **options + end + + private def new_request_id + UUID.random.to_s.upcase + end + + def connected + self["authenticated"] = @authenticated = false + request_auth_string + + schedule.every(40.seconds) do + logger.debug { "-- maintaining connection" } + @authenticated ? keep_alive : request_auth_string + end + end + + def disconnected + schedule.clear + end + + def keep_alive + send_frame({ + Caption: "KeepAliveRequest", + Id: new_request_id, + }, priority: 0) + end + + def request_auth_string + send_frame({ + Caption: "AuthenticationRequestA", + Id: new_request_id, + }, priority: 9998) + end + + private def login(authentication_string : String) + cipher = OpenSSL::Cipher.new("aes-256-cbc") + cipher.padding = true + cipher.decrypt + + # LE for little endian and avoids a byte order mark + password = @password.encode("UTF-16LE") + + key = IO::Memory.new(Bytes.new(32)) + key.write password + + iv = IO::Memory.new(Bytes.new(16)) + iv.write password + + cipher.key = key.to_slice + cipher.iv = iv.to_slice + + decrypted_data = IO::Memory.new + content = Base64.decode(authentication_string) + decrypted_data.write cipher.update(content) + decrypted_data.write cipher.final + decrypted_data.rewind + + # Return the decrypted string + decrypted = String.new(decrypted_data.to_slice, "UTF-16LE") + + send_frame({ + Caption: "AuthenticationRequestB", + Id: new_request_id, + + # Locker system expects an integer here + AuthenticationString: decrypted.to_i, + }, priority: 9999) + end + + def open_locker(locker_number : String, locker_group : String? = nil) + set_open_state(true, locker_number, locker_group) + end + + def close_locker(locker_number : String, locker_group : String? = nil) + set_open_state(false, locker_number, locker_group) + end + + def set_open_state(open : Bool, locker_number : String, locker_group : String? = nil) + action = open ? "0" : "1" + + # Detect if this is a GUID + task = if locker_number.includes?("-") + send_frame({ + Caption: "ExecuteLockerActionRequest", + Id: new_request_id, + Action: action, + LockerId: locker_number, + }) + else + request = { + Caption: "ExecuteLockerActionRequest", + Id: new_request_id, + Action: action, + LockerNumber: locker_number, + } + if locker_group + send_frame(request.merge({LockerGroupId: locker_group})) + else + send_frame(request) + end + end + + task + end + + def query_lockers(free_only : Bool = false) + send_frame({ + Caption: "GetLockersRequest", + Id: new_request_id, + FreeLockersOnly: free_only, + PersonalLockersOnly: false, + }) + end + + def received(data, task) + # Ignore the framing bytes + data = String.new(data)[1..-2] + logger.debug { "Gantner Relaxx sent: #{data}" } + json = JSON.parse(data) + + # Ignore if a notification as we still might be expecting a response + return parse_notify(json["Caption"].as_s, data) if json["IsNotification"].as_bool + + # Check result of the request + result = Result.from_json(json["Result"].to_json) + if result.cancelled + return task.try &.abort("request cancelled, #{result.code}: #{result.text}") + end + if !result.successful + return task.try &.abort("request failed, #{result.code}: #{result.text}") + end + + # Process response + case json["Caption"].as_s + when "AuthenticationResponseA" + logged_in = json["LoggedIn"].as_bool + self["authenticated"] = @authenticated = logged_in + return task.try &.success if logged_in + login(json["AuthenticationString"].as_s) + when "AuthenticationResponseB" + logged_in = json["LoggedIn"].as_bool + self["authenticated"] = @authenticated = logged_in + if logged_in + logger.debug { "authentication success" } + + # Obtain the list of lockers and their current state + query_lockers if @locker_ids.empty? + else + logger.warn { "authentication failure - please check credentials" } + end + when "GetLockersResponse" + lockers = Array(Locker).from_json(json["Lockers"].to_json) + lockers.each do |locker| + locker_id = locker.id + @locker_ids << locker_id + if locker.locker_state != LockerState::Free + @lockers_in_use << locker_id + self["locker_#{locker_id}"] = locker.card_id + else + @lockers_in_use.delete(locker_id) + end + end + self[:locker_ids] = @locker_ids + self[:lockers_in_use] = @lockers_in_use + when "CommandNotSupportedResponse" + logger.warn { "Command not supported!" } + return task.try &.abort("Command not supported!") + end + + task.try &.success + end + + private def parse_notify(caption, json) + case caption + when "LockerEventNotification" + info = LockerNotification.from_json(json) + update_locker_state(info.locker_state != LockerState::Free, info.locker.id, info.locker.card_id) + else + logger.debug { "ignoring event: #{caption}" } + end + nil + end + + private def update_locker_state(in_use : Bool, locker_id : String, card_id : String) : Nil + @locker_ids << locker_id + if in_use + @lockers_in_use << locker_id + else + @lockers_in_use.delete(locker_id) + end + self["locker_#{locker_id}"] = card_id + self[:locker_ids] = @locker_ids + self[:lockers_in_use] = @lockers_in_use + end +end diff --git a/drivers/gantner/relaxx/protocol_json_spec.cr b/drivers/gantner/relaxx/protocol_json_spec.cr new file mode 100644 index 00000000000..4e76ca467f3 --- /dev/null +++ b/drivers/gantner/relaxx/protocol_json_spec.cr @@ -0,0 +1,131 @@ +require "json" +require "uuid" + +module Relaxx + SUCCESS = { + Successful: true, + Cancelled: false, + ResultText: "", + ResultCode: 0, + } + + def self.frame(data) + "\x02#{data.to_json}\x03" + end + + def self.parse(raw_data) + JSON.parse(String.new(raw_data)[1..-2]) + end +end + +DriverSpecs.mock_driver "Gantner::Relaxx::ProtocolJSON" do + # Should send an auth A request + data = Relaxx.parse(expect_send) + data["Caption"].as_s.should eq("AuthenticationRequestA") + id = data["Id"].as_s + + # Respond with auth A response + transmit Relaxx.frame({ + Caption: "AuthenticationResponseA", + Result: Relaxx::SUCCESS, + Id: UUID.random.to_s.upcase, + RequestId: id, + AuthenticationString: "wglgJg4kP8DHO+2+N6L8Hsu6mp3LSoe3/gIxDlZgu60=", + LoggedIn: false, + IsLoginCommand: true, + IsNotification: false, + IsResponse: true, + CustomTimeout: 0, + CompressContent: false, + }) + + # Should send an auth B request + data = Relaxx.parse(expect_send) + data["Caption"].as_s.should eq("AuthenticationRequestB") + id = data["Id"].as_s + + # password should be decrypted + data["AuthenticationString"].as_i64.should eq(499520882) + + # Respond with auth B response + transmit Relaxx.frame({ + Caption: "AuthenticationResponseB", + Result: Relaxx::SUCCESS, + Id: UUID.random.to_s.upcase, + RequestId: id, + LoggedIn: true, + IsLoginCommand: true, + IsNotification: false, + IsResponse: true, + CustomTimeout: 0, + CompressContent: false, + }) + + # Expect a locker state query + data = Relaxx.parse(expect_send) + data["Caption"].as_s.should eq("GetLockersRequest") + id = data["Id"].as_s + + locker_id1 = UUID.random.to_s + locker_id2 = UUID.random.to_s + + transmit Relaxx.frame({ + Caption: "GetLockersResponse", + Result: Relaxx::SUCCESS, + Id: UUID.random.to_s.upcase, + RequestId: id, + IsNotification: false, + IsResponse: true, + Lockers: [{ + RecordId: locker_id1, + LockerGroupId: UUID.random.to_s, + LockerGroupName: "Example Group", + Number: "1", + Address: 21, + State: 2, + LockerMode: 3, + IsFreeLocker: false, + IsDeleted: false, + IsExisting: true, + LastClosedTime: "", + CardUIDInUse: "", + }, { + RecordId: locker_id2, + LockerGroupId: UUID.random.to_s, + LockerGroupName: "Example Group", + Number: "2", + Address: 21, + State: 3, + LockerMode: 3, + IsFreeLocker: false, + IsDeleted: false, + IsExisting: true, + LastClosedTime: "", + CardUIDInUse: "12345", + }], + }) + + # send a keep alive request + exec(:keep_alive) + data = Relaxx.parse(expect_send) + data["Caption"].as_s.should eq("KeepAliveRequest") + id = data["Id"].as_s + + transmit Relaxx.frame({ + Caption: "KeepAliveResponse", + Result: Relaxx::SUCCESS, + Id: UUID.random.to_s.upcase, + RequestId: id, + LoggedIn: true, + IsLoginCommand: false, + IsNotification: false, + IsResponse: true, + CustomTimeout: 0, + CompressContent: false, + }) + + status[:authenticated].should eq(true) + status[:locker_ids].should eq([locker_id1, locker_id2]) + status[:lockers_in_use].should eq([locker_id2]) + status["locker_#{locker_id2}"].should eq("12345") +end diff --git a/drivers/global_cache/gc_100.cr b/drivers/global_cache/gc_100.cr new file mode 100644 index 00000000000..3541b414713 --- /dev/null +++ b/drivers/global_cache/gc_100.cr @@ -0,0 +1,171 @@ +class GlobalCache::Gc100 < PlaceOS::Driver + # Discovery Information + tcp_port 4999 + descriptive_name "GlobalCache IO Gateway" + generic_name :DigitalIO + + DELIMITER = "\r" + + # @relay_config maps the GC100 into a linear set of ir and relays so models can be swapped in and out + # E.g. @relay_config = {"relay" => {0 => "2:1",1 => "2:2",2 => "2:3",3 => "3:1"}} + @relay_config : Hash(String, Hash(Int32, String)) = {} of String => Hash(Int32, String) + @port_config : Hash(String, Tuple(String, Int32)) = {} of String => Tuple(String, Int32) + + def on_load + transport.tokenizer = Tokenizer.new(DELIMITER) + self[:num_relays] = 0 + self[:num_ir] = 0 + end + + def connected + @relay_config = {} of String => Hash(Int32, String) + @port_config = {} of String => Tuple(String, Int32) + self[:config_indexed] = false + + schedule.every(10.seconds, true) do + logger.debug { "-- Polling GC100" } + get_devices unless self[:config_indexed].as_bool + + # Low priority sent to maintain the connection + do_send("get_NET,0:1", priority: 0) + end + end + + def disconnected + schedule.clear + end + + def get_devices + do_send("getdevices") # , :max_waits => 100) + end + + def relay(index : Int32, state : Bool, **options) + if index < self[:num_relays].as_i + relays = (self[:relay_config]["relay"]? || self[:relay_config]["relaysensor"]?).not_nil!.as_h + logger.debug { "relays = #{relays}" } + connector = relays[index.to_s] + do_send("setstate,#{connector},#{state ? 1 : 0}", **options) + else + logger.warn { "Attempted to set relay on GlobalCache that does not exist: #{index}" } + end + end + + def ir(index : Int32, command : String, **options) + do_send("sendir,1:#{index},#{command}", **options) + end + + enum IrMode + IR + SENSOR + SENSOR_NOTIFY + IR_NOCARRIER + end + + def set_ir(index : Int32, mode : IrMode, **options) + if index < self[:num_ir].as_i + connector = self[:relay_config]["ir"][index.to_s] + do_send("set_IR,#{connector},#{mode}", **options) + else + logger.warn { "Attempted to set IR mode on GlobalCache that does not exist: #{index}" } + end + end + + def relay_status?(index : Int32, **options) + if index < self[:num_relays].as_i + connector = self[:relay_config]["relay"][index.to_s] + do_send("getstate,#{connector}", **options) + else + logger.warn { "Attempted to check IO on GlobalCache that does not exist: #{index}" } + end + end + + def ir_status?(index : Int32, **options) + if index < self[:num_ir].as_i + connector = self[:relay_config]["ir"][index.to_s] + do_send("getstate,#{connector}", **options) + else + logger.warn { "Attempted to check IO on GlobalCache that does not exist: #{index}" } + end + end + + def received(data, task) + # Remove the delimiter + data = String.new(data[0..-2]) + logger.debug { "GlobalCache sent #{data}" } + data = data.split(',') + task_name = task.try &.name || "unknown" + + case data[0] + when "state", "statechange" + type, index = self[:port_config][data[1]] + self["#{type}#{index}"] = data[2] == "1" # Is relay index on? + when "device" + address = data[1] + number, type = data[2].split(' ') # The response was "device,2,3 RELAY" + + type = type.downcase + + @relay_config[type] ||= {} of Int32 => String + current = @relay_config[type].size + + (current..(current + number.to_i - 1)).each_with_index(1) do |i, dev_index| + port = "#{address}:#{dev_index}" + @relay_config[type][i] = port + @port_config[port] = {type, i} + end + + return task.try &.success + when "endlistdevices" + self[:num_relays] = @relay_config["relay"].size if @relay_config["relay"]? + if @relay_config["relaysensor"]? + @relay_config["relaysensor"][1] = "1:2" + @relay_config["relaysensor"][2] = "1:3" + @relay_config["relaysensor"][3] = "1:4" + self[:num_relays] = @relay_config["relaysensor"].size + end + self[:num_ir] = @relay_config["ir"].size if @relay_config["ir"]? + self[:relay_config] = @relay_config + self[:port_config] = @port_config + logger.debug { "self[:relay_config] is #{self[:relay_config]}" } + logger.debug { "self[:port_config] is #{self[:port_config]}" } + @relay_config = {} of String => Hash(Int32, String) + @port_config = {} of String => Tuple(String, Int32) + self[:config_indexed] = true + + return task.try &.success + end + + if data.size == 1 + error = case data[0].split(' ')[1].to_i + when 1 then "Command was missing the carriage return delimiter" + when 2 then "Invalid module address when looking for version" + when 3 then "Invalid module address" + when 4 then "Invalid connector address" + when 5 then "Connector address 1 is set up as \"sensor in\" when attempting to send an IR command" + when 6 then "Connector address 2 is set up as \"sensor in\" when attempting to send an IR command" + when 7 then "Connector address 3 is set up as \"sensor in\" when attempting to send an IR command" + when 8 then "Offset is set to an even transition number, but should be set to an odd transition number in the IR command" + when 9 then "Maximum number of transitions exceeded (256 total on/off transitions allowed)" + when 10 then "Number of transitions in the IR command is not even (the same number of on and off transitions is required)" + when 11 then "Contact closure command sent to a module that is not a relay" + when 12 then "Missing carriage return. All commands must end with a carriage return" + when 13 then "State was requested of an invalid connector address, or the connector is programmed as IR out and not sensor in." + when 14 then "Command sent to the unit is not supported by the GC-100" + when 15 then "Maximum number of IR transitions exceeded" + when 16 then "Invalid number of IR transitions (must be an even number)" + when 21 then "Attempted to send an IR command to a non-IR module" + when 23 then "Command sent is not supported by this type of module" + else "Unknown error" + end + return task.try &.abort("GlobalCache error for command #{task_name}: #{error}") + end + + task.try &.success + end + + private def do_send(command : String, **options) + logger.debug { "-- GlobalCache, sending: #{command}" } + command = "#{command}#{DELIMITER}" + send(command, **options) + end +end diff --git a/drivers/global_cache/gc_100_spec.cr b/drivers/global_cache/gc_100_spec.cr new file mode 100644 index 00000000000..bc38de8280a --- /dev/null +++ b/drivers/global_cache/gc_100_spec.cr @@ -0,0 +1,44 @@ +DriverSpecs.mock_driver "GlobalCache::Gc100" do + # connected + # get_devices + should_send("getdevices\r") + responds("device,2,3 RELAY\r") + responds("device,1,2 RELAYSENSOR\r") + responds("device,3,1 IR\r") + responds("endlistdevices\r") + should_send("get_NET,0:1\r") + + sleep 1 + + status[:relay_config].should eq({ + "relay" => {"0" => "2:1", "1" => "2:2", "2" => "2:3"}, + "relaysensor" => {"0" => "1:1", "1" => "1:2", "2" => "1:3", "3" => "1:4"}, + "ir" => {"0" => "3:1"}, + }) + status[:port_config].should eq({ + "2:1" => ["relay", 0], "2:2" => ["relay", 1], "2:3" => ["relay", 2], "1:1" => ["relaysensor", 0], "1:2" => ["relaysensor", 1], "3:1" => ["ir", 0], + }) + + exec(:relay, 1, true) + should_send("setstate,2:2,1\r") + responds("state,2:2,1\r") + status[:relay1].should eq(true) + + exec(:ir, 0, "4444") + should_send("sendir,1:0,4444\r") + responds("completeir,1:0,4444\r") + + exec(:set_ir, 0, "ir") + should_send("set_IR,3:1,IR\r") + responds("TODO 1\r") + + exec(:relay_status?, 2) + should_send("getstate,2:3\r") + responds("state,2:3,0\r") + status[:relay2].should eq(false) + + exec(:ir_status?, 0) + should_send("getstate,3:1\r") + responds("state,3:1,1\r") + status[:ir0].should eq(true) +end diff --git a/drivers/helvar/helvar_net_protocol.md b/drivers/helvar/helvar_net_protocol.md new file mode 100644 index 00000000000..6ec61f6a72a --- /dev/null +++ b/drivers/helvar/helvar_net_protocol.md @@ -0,0 +1,95 @@ + +# Helvar.net Protocol + +Reference: https://aca.im/driver_docs/Helvar/HelvarNet-Overview.pdf + +For use with Helvar to DALI routers + +* TCP port: 50000 +* UDP port: 50001 + + +## Addressing + +The Helvar lighting router system would consist of a number of routers (910 or 920) that enable +connection to a variety of different inputs and outputs using a different data buses. + +The backbone structure of the system uses Ethernet Cat 5 cabling & the TCP/IP protocol. As +such each system (or workgroup) is a cluster of routers. The cluster (3rd Octet in IP addressing) +forms the first part of the unique device address + +Each router within the system will then have a unique IP address with the 4th octet providing the +unique router number. This number forms the second digit of the unique device address. + +The cluster.router is then followed by a subnet. The subnet refers to the data bus on which +inputs or output devices are connected. Depending on the router type (910 or 920) there are 2 +or 4 subnets available. In both case’s subnet 1 & 2 use the DALI protocol. For the 920 you have +additional subnets 3 (using S-Dim) and 4 (DMX). + +Following cluster.router.subnet is then the device address. This number is limited by the type of +subnet to which the device is connected and in the case of output devices completes the device +address. + +For input devices there is a further sub-device which will refer to a particular property of that +input device for example a control panel (device) would have a number of buttons (sub-device). + +So a full address would be written:- + +* Cluster (1..253), Router (1..254), Subnet (1..4), Device (1..255), Subdevice (1..16) +* cluster.router.subnet.address for output devices +* cluster.router.subnet.address for input devices +* cluster.router.subnet.address.sub-address for input sub-devices + +### Address Structure + +* Cluster = the 3rd octet of the IP address range used +* Router = the 4th octet of the IP address of that particular router +* Subnet = the data bus on which devices are connected (Dali 1 = 1, Dali 2 = 2, S-Dim = 3, DMX = 4) +* Address = the device address, dependant on the data bus (Dali = 1-64, S-Dim = 1-252, DMX = 1-512) +* Sub-address = the sub-device of the device (button, sensor, input etc.) + + +## Commands + +* `>V:` is the command prefix (`>V:2` represents the protocol version 2) +* `C:` is the command type + * `11` == select scene + * `13` == direct level group address + * `14` == direct level short address + * `109` == query selected scene +* `G:` specifies the lighting group +* `S:` specifices the lighting scene +* `F:` specifies the fade time (in 1/100ths of a second. So a fade of 900 is 9 seconds) +* `L:` specifies the level (between 1 and 100) +* `@` specifies the short address (looks like: 1.2.1.1) +* all commands end with a `#` + + +### Example Commands + +* Direct level, short address: `>V:1,C:14,L:{0},F:{1},@{2}#` + * {0} == level, {1} == fade_time, {2} == address +* Direct level, group address: `>V:1,C:13,G:{0},L:{1},F:{2}#` + * {0} == address, {1} == level, {2} == fade_time +* Keep socket alive: `>V:1,C:14,L:0,F:9000,@65#` + * Write to dummy address to keep socket alive + + +### Example Query + +* `>V:2,C:109,G:17#` query Group 17 as to which scene it is currently in + * responds with: `?V:2,C:109,G:17=14#` + * i.e. Group 17 is in scene 14 + +### Example Error + +* `>V:1,C:104,@:2.2.1.1#` query device type + * responds with: `!V:1,C:104,@:2.2.1.1=11#` + * i.e. error 11, device does not exist + + +References: + +* https://github.com/tkln/HelvarNet/blob/master/helvar.py +* https://github.com/houmio/houmio-driver-helvar-router/blob/master/src/driver.coffee + diff --git a/drivers/helvar/net.cr b/drivers/helvar/net.cr new file mode 100755 index 00000000000..e0e76899d12 --- /dev/null +++ b/drivers/helvar/net.cr @@ -0,0 +1,290 @@ +module Helvar; end + +# Documentation: https://aca.im/driver_docs/Helvar/HelvarNet-Overview.pdf + +class Helvar::Net < PlaceOS::Driver + # Discovery Information + tcp_port 50000 + descriptive_name "Helvar Net Lighting Gateway" + generic_name :Lighting + + default_settings({ + version: 2, + ignore_blocks: true, + poll_group: nil, + }) + + def on_load + transport.tokenizer = Tokenizer.new("#") + on_update + end + + def on_update + @version = setting?(Int32, :version) || 2 + @ignore_blocks = setting?(Bool, :ignore_blocks) || true + @poll_group = setting?(Int32, :poll_group) + end + + @poll_group : Int32? + + def connected + schedule.every(40.seconds) do + logger.debug { "-- Polling Helvar" } + if poll_group = @poll_group + get_current_preset poll_group + else + query_software_version + end + end + end + + def disconnected + schedule.clear + end + + def lighting(group : Int32, state : Bool) + level = state ? 100 : 0 + light_level(group, level) + end + + def light_level(group : Int32, level : Int32, fade : Int32 = 1000) + fade = (fade / 10).to_i + self["area#{group}_level"] = level + group_level(group: group, level: level, fade: fade, name: "group_level#{group}") + end + + def trigger(group : Int32, scene : Int32, fade : Int32 = 1000) + fade = (fade / 10).to_i + self["area#{group}"] = scene + group_scene(group: group, scene: scene, fade: fade, name: "group_scene#{group}") + end + + def get_current_preset(group : Int32) + query_last_scene(group: group) + end + + CMD_METHODS = { + group_scene: 11, + device_scene: 12, + group_level: 13, + device_level: 14, + group_proportion: 15, + device_proportion: 16, + group_modify_proportion: 17, + device_modify_proportion: 18, + group_emergency_test: 19, + device_emergency_test: 20, + group_emergency_duration_test: 21, + device_emergency_duration_test: 22, + group_emergency_stop: 23, + device_emergency_stop: 24, + + # Query commands + query_lamp_hours: 70, + query_ballast_hours: 71, + query_max_voltage: 72, + query_min_voltage: 73, + query_max_temp: 74, + query_min_temp: 75, + query_device_types_with_addresses: 100, + query_clusters: 101, + query_routers: 102, + query_LSIB: 103, + query_device_type: 104, + query_description_group: 105, + query_description_device: 106, + query_workgroup_name: 107, # must use UDP + query_workgroup_membership: 108, + query_last_scene: 109, + query_device_state: 110, + query_device_disabled: 111, + query_lamp_failure: 112, + query_device_faulty: 113, + query_missing: 114, + query_emergency_battery_failure: 129, + query_measurement: 150, + query_inputs: 151, + query_load: 152, + query_power_consumption: 160, + query_group_power_consumption: 161, + query_group: 164, + query_groups: 165, + query_scene_names: 166, + query_scene_info: 167, + query_emergency_func_test_time: 170, + query_emergency_func_test_state: 171, + query_emergency_duration_time: 172, + query_emergency_duration_state: 173, + query_emergency_battery_charge: 174, + query_emergency_battery_time: 175, + query_emergency_total_lamp_time: 176, + query_time: 185, + query_longitude: 186, + query_latitude: 187, + query_time_zone: 188, + query_daylight_savings: 189, + query_software_version: 190, + query_helvar_net: 191, + } + + # Dynamically define methods based on the tuple above + {% for name, command in CMD_METHODS %} + def {{name.id}}(group : Int32? = nil, block : Int32? = nil, level : Int32? = nil, scene : Int32? = nil, fade : Int32? = nil, addr : Int32? = nil, **options) + do_send({{command.id.stringify}}, @version, group, block, level, scene, fade, addr, **options) + end + {% end %} + + # Generate a String => String hash based on the data above + macro build_command_hash + COMMANDS = { + {% for name, command in CMD_METHODS %} + {{name.id.stringify}} => {{command.id.stringify}}, + {% end %} + } + COMMANDS.merge!(COMMANDS.invert) + end + + build_command_hash + + PARAMS = { + "V" => :ver, + "Q" => :seq, + "C" => :cmd, + "A" => :ack, + "@" => :addr, + "F" => :fade, + "T" => :time, + "L" => :level, + "G" => :group, + "S" => :scene, + "B" => :block, + "N" => :latitude, + "E" => :longitude, + "Z" => :time_zone, + # brighter or dimmer than the current level by a % of the difference + "P" => :proportion, + "D" => :display_screen, + "Y" => :daylight_savings, + "O" => :force_store_scene, + "K" => :constant_light_scene, + } + + def received(data, task) + data = String.new(data) + logger.debug { "Helvar sent: #{data}" } + + # Remove the # at the end of the message + data = data[0..-2] + + # Group level changed: ?V:2,C:109,G:12706=13 (query scene response) + # Update pushed >V:2,C:11,G:25007,B:1,S:13,F:100 (current scene level) + + # Remove junk data (when whitelisting gateway is in place) + start_of_message = data.index(/[\?\>\!]V:/i) + if start_of_message != 0 + logger.warn { "Lighting error response: #{data[0...start_of_message]}" } + data = data[start_of_message..-1] + end + + # remove connectors from multi-part responses + data = data.delete("$") + + indicator = data[0] + case indicator + when '?', '>' + # remove indicator + data = data[1..-1] + + # check if this is a result + parts = data.split("=") + data = parts[0] + value = parts[1]? + + # Extract components of the message + params = {} of Symbol => String + data.split(",").each do |param| + parts = param.split(":") + if parts.size > 1 + params[PARAMS[parts[0]]] = parts[1] + elsif parts[0][0] == '@' + params[:addr] == parts[0][1..-1] + else + logger.debug { "unknown param type #{param}" } + end + end + + # Check for :ack + ack = params[:ack]? + if ack + return task.try &.abort("request failed") if ack != "1" + return task.try &.success + end + + cmd = COMMANDS[params[:cmd]] + case cmd + when "query_last_scene" + self["area#{params[:group]}"] = value.try &.to_i + when "group_scene" + block = params[:block] + if block + if @ignore_blocks + self["area#{params[:group]}"] = params[:scene].to_i + else + self["area#{params[:group]}_block#{block}"] = params[:scene].to_i + end + else + self["area#{params[:group]}"] = params[:scene].to_i + end + else + logger.debug { "unknown response value\n#{cmd} = #{value}" } + end + when '!' + error = ERRORS[data.split("=")[1]] + error = "#{error} for #{data}" + self[:last_error] = error + logger.warn { error } + return task.try &.abort(error) + else + logger.info { "unknown request #{data}" } + end + + task.try &.success + end + + ERRORS = { + "0" => "success", + "1" => "invalid group index parameter", + "2" => "invalid cluster parameter", + "3" => "invalid router", + "4" => "invalid router subnet", + "5" => "invalid device parameter", + "6" => "invalid sub device parameter", + "7" => "invalid block parameter", + "8" => "invalid scene", + "9" => "cluster does not exist", + "10" => "router does not exist", + "11" => "device does not exist", + "12" => "property does not exist", + "13" => "invalid RAW message size", + "14" => "invalid messages type", + "15" => "invalid message command", + "16" => "missing ASCII terminator", + "17" => "missing ASCII parameter", + "18" => "incompatible version", + } + + protected def do_send(cmd : String, ver = @version, group = nil, block = nil, level = nil, scene = nil, fade = nil, addr = nil, **options) + req = String.build do |str| + str << ">V:" << ver << ",C:" << cmd + str << ",G:" << group if group + str << ",B:" << block if block + str << ",L:" << level if level + str << ",S:" << scene if scene + str << ",F:" << fade if fade + str << ",@:" << addr if addr + str << "#" + end + logger.debug { "Requesting helvar: #{req}" } + send(req, **options) + end +end diff --git a/drivers/helvar/net_spec.cr b/drivers/helvar/net_spec.cr new file mode 100644 index 00000000000..f4bea2e5fe0 --- /dev/null +++ b/drivers/helvar/net_spec.cr @@ -0,0 +1,25 @@ +DriverSpecs.mock_driver "Helvar::Net" do + # Perform actions + resp = exec(:trigger, group: 1, scene: 2, fade: 1100) + should_send(">V:2,C:11,G:1,S:2,F:110#") + responds(">V:2,C:11,G:1,S:2,F:110,A:1#") + resp.get + status[:area1].should eq(2) + + resp = exec(:get_current_preset, group: 17) + should_send(">V:2,C:109,G:17#") + responds("?V:2,C:109,G:17=14#") + resp.get + status[:area17].should eq(14) + + resp = exec(:get_current_preset, group: 20) + should_send(">V:2,C:109,G:20#") + responds("!V:2,C:109,G:20=1#") + expect_raises(PlaceOS::Driver::RemoteException, "invalid group index parameter for !V:2,C:109,G:20=1 (Abort)") do + resp.get + end + status[:last_error].should eq("invalid group index parameter for !V:2,C:109,G:20=1") + + transmit(">V:2,C:11,G:2001,B:1,S:1,F:100#") + status[:area2001].should eq(1) +end diff --git a/drivers/hitachi/projector/cp_tw_series_basic.cr b/drivers/hitachi/projector/cp_tw_series_basic.cr new file mode 100644 index 00000000000..6d2df9f8ab2 --- /dev/null +++ b/drivers/hitachi/projector/cp_tw_series_basic.cr @@ -0,0 +1,243 @@ +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/muteable" + +class Hitachi::Projector::CpTwSeriesBasic < PlaceOS::Driver + include Interface::Powerable + include Interface::Muteable + + # Discovery Information + tcp_port 23 + descriptive_name "Hitachi CP-TW Projector (no auth)" + generic_name :Display + + @recover_power : PlaceOS::Driver::Proxy::Scheduler::TaskWrapper? = nil + @recover_input : PlaceOS::Driver::Proxy::Scheduler::TaskWrapper? = nil + # nil by default (allows manual on and off) + @power_target : Bool? = nil + @input_target : Input? = nil + + def on_load + # Response time is slow + # and as a make break device it may take time + # to actually setup the connection with the projector + queue.delay = 100.milliseconds + queue.timeout = 5.seconds + queue.retries = 3 + + # Meta data for inquiring interfaces + self[:type] = :projector + end + + def connected + schedule.every(50.seconds, true) { poll_1 } + schedule.every(10.minutes, true) { poll_2 } + end + + def poll_1 + power?(priority: 0).get + if self[:power]?.try &.as_bool + input?(priority: 0) + audio_mute?(priority: 0) + video_mute?(priority: 0) + freeze?(priority: 0) + end + end + + def poll_2 + lamp?(priority: 0) + filter?(priority: 0) + error?(priority: 0) + end + + def disconnected + schedule.clear + @recover_power = nil + @recover_input = nil + end + + def power(state : Bool) + @power_target = state + if state + logger.debug { "requested to power on" } + do_send(:power_on) + else + logger.debug { "requested to power off" } + do_send(:power_off) + end + power? + end + + def switch_to(input : Input) + @input_target = input + do_send(input.to_s.downcase) + input? + end + + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + mute_video(state) if layer.video? || layer.audio_video? + mute_audio(state) if layer.audio? || layer.audio_video? + end + + def mute_video(state : Bool = true) + if state + do_send(:mute_video) + else + do_send(:unmute_video) + end + video_mute? + end + + def mute_audio(state : Bool = true) + if state + do_send(:mute_audio) + else + do_send(:unmute_audio) + end + audio_mute? + end + + def lamp_hours_reset + do_send(:lamp_hours_reset) + lamp? + end + + def filter_hours_reset + do_send(:filter_hours_reset) + filter? + end + + enum Response + Ack = 0x06 + Nak = 0x15 + Error = 0x1c + Data = 0x1d + Busy = 0x1f + end + + enum Input + Hdmi = 0x03 + Hdmi2 = 0x0d + HdbaSet = 0x11 + end + + enum Error + Normal + Cover + Fan + Lamp + Temp + AirFlow + Cold + Filter + end + + def received(data, task) + logger.debug { "received 0x#{data}" } + command = task.try &.name + + case Response.from_value(data[0]) + when .ack? + task.try &.success + when .nak? + task.try &.abort("NAK response") + when .error? + task.try &.abort("Error response") + when .data? + if command + case command + when "power?" + self[:power] = data[1] == 1 + self[:cooling] = data[1] == 2 + + if self[:power]? == @power_target + @power_target = nil + elsif @power_target && @recover_power.nil? + logger.debug { "recovering power state #{self[:power]} != target #{@power_target}" } + @recover_power = schedule.in(3.seconds) do + @recover_power = nil + power(@power_target.not_nil!) + end + end + when "input?" + input = Input.from_value?(data[1]) + self[:input] = input || "unknown" + if @input_target + if input == @input_target + @input_target = nil + elsif @recover_input.nil? + logger.debug { "recovering input #{self[:input]} != target #{@input_target}" } + @recover_input = schedule.in(3.seconds) do + @recover_input = nil + switch_to(@input_target.not_nil!) + end + end + end + when "error?" + self[:error_status] = Error.from_value?(data[1]) || "unknown" + when "freeze?" + self[:frozen] = data[1] == 1 + when "audio_mute?" + self[:audio_mute] = data[1] == 1 + when "video_mute?" + self[:video_mute] = data[1] == 1 + when "lamp?" + self[:lamp] = data[1] * data[2] + when "filter?" + self[:filter] = data[1] * data[2] + end + task.try &.success + else + task.try &.abort("data received for unknown command") + end + when .busy? + if data[1] == 4 && data[2] == 0 + task.try &.abort("authentication enabled, please disable") + else + task.try &.retry("projector busy, retrying") + end + end + end + + # Note: commands have spaces in between each byte for readability + Commands = { + # SetRequests + power_on: "BA D2 01 00 00 60 01 00", + power_off: "2A D3 01 00 00 60 00 00", + hdmi: "0E D2 01 00 00 20 03 00", + hdmi2: "6E D6 01 00 00 20 0D 00", + mute_video: "6E F1 01 00 A0 20 01 00", + unmute_video: "FE F0 01 00 A0 20 00 00", + mute_audio: "D6 D2 01 00 02 20 01 00", + unmute_audio: "46 D3 01 00 02 20 00 00", + lamp_hours_reset: "58 DC 06 00 30 70 00 00", + filter_hours_reset: "98 C6 06 00 40 70 00 00", + # GetRequests + power?: "19 D3 02 00 00 60 00 00", + input?: "CD D2 02 00 00 20 00 00", + error?: "D9 D8 02 00 20 60 00 00", + freeze?: "B0 D2 02 00 02 30 00 00", + audio_mute?: "75 D3 02 00 02 20 00 00", + video_mute?: "CD F0 02 00 A0 20 00 00", + lamp?: "C2 FF 02 00 90 10 00 00", + filter?: "C2 F0 02 00 A0 10 00 00", + } + + GetRequests = %i(power? input? error? freeze? audio_mute? video_mute? lamp? filter?) + {% for name in GetRequests %} + @[Security(Level::Administrator)] + def {{name.id}}(**options) + do_send({{name.id.stringify}}, **options) + end + {% end %} + + private def do_send(cmd, **options) + data = "BEEF030600 #{Commands[cmd]}" + logger.debug { "requesting \"0x#{data}\" name: #{cmd}" } + # Remove spaces that have been added for readability + send(data.delete(' ').hexbytes, **options, name: cmd) + end +end diff --git a/drivers/hitachi/projector/cp_tw_series_basic_spec.cr b/drivers/hitachi/projector/cp_tw_series_basic_spec.cr new file mode 100644 index 00000000000..83a377534be --- /dev/null +++ b/drivers/hitachi/projector/cp_tw_series_basic_spec.cr @@ -0,0 +1,80 @@ +require "placeos-driver" +require "./cp_tw_series_basic" + +DriverSpecs.mock_driver "Hitachi::Projector::CpTwSeriesBasic" do + c = Hitachi::Projector::CpTwSeriesBasic::Commands + + # connected + # power? + should_send("BEEF030600#{c[:power?]}".delete(' ').hexbytes) + responds("\x1d\x01\x00") + status[:power].should eq(true) + # lamp? + should_send("BEEF030600#{c[:lamp?]}".delete(' ').hexbytes) + responds("\x1d\x03\x01") + status[:lamp].should eq(3) + # filter? + should_send("BEEF030600#{c[:filter?]}".delete(' ').hexbytes) + responds("\x1d\x04\x01") + status[:filter].should eq(4) + # error? + should_send("BEEF030600#{c[:error?]}".delete(' ').hexbytes) + responds("\x1d\x00\x00") + status[:error_status].should eq("Normal") + # input? + should_send("BEEF030600#{c[:input?]}".delete(' ').hexbytes) + responds("\x1d\x0d\x00") + status[:input].should eq("Hdmi2") + # audio_mute? + should_send("BEEF030600#{c[:audio_mute?]}".delete(' ').hexbytes) + responds("\x1d\x01\x00") + status[:audio_mute].should eq(true) + # video_mute? + should_send("BEEF030600#{c[:video_mute?]}".delete(' ').hexbytes) + responds("\x1d\x01\x00") + status[:video_mute].should eq(true) + # freeze? + should_send("BEEF030600#{c[:freeze?]}".delete(' ').hexbytes) + responds("\x1d\x01\x00") + status[:frozen].should eq(true) + + exec(:mute, false) + should_send("BEEF030600#{c[:unmute_video]}".delete(' ').hexbytes) + responds("\x06") + should_send("BEEF030600#{c[:video_mute?]}".delete(' ').hexbytes) + responds("\x1d\x00\x00") + status[:video_mute].should eq(false) + should_send("BEEF030600#{c[:unmute_audio]}".delete(' ').hexbytes) + responds("\x06") + should_send("BEEF030600#{c[:audio_mute?]}".delete(' ').hexbytes) + responds("\x1d\x00\x00") + status[:video_mute].should eq(false) + + exec(:switch_to, "hdmi") + should_send("BEEF030600#{c[:hdmi]}".delete(' ').hexbytes) + responds("\x06") + should_send("BEEF030600#{c[:input?]}".delete(' ').hexbytes) + responds("\x1d\x03\x00") + status[:input].should eq("Hdmi") + + exec(:lamp_hours_reset) + should_send("BEEF030600#{c[:lamp_hours_reset]}".delete(' ').hexbytes) + responds("\x06") + should_send("BEEF030600#{c[:lamp?]}".delete(' ').hexbytes) + responds("\x1d\x00\x00") + status[:lamp].should eq(0) + + exec(:filter_hours_reset) + should_send("BEEF030600#{c[:filter_hours_reset]}".delete(' ').hexbytes) + responds("\x06") + should_send("BEEF030600#{c[:filter?]}".delete(' ').hexbytes) + responds("\x1d\x00\x00") + status[:filter].should eq(0) + + exec(:power, false) + should_send("BEEF030600#{c[:power_off]}".delete(' ').hexbytes) + responds("\x06") + should_send("BEEF030600#{c[:power?]}".delete(' ').hexbytes) + responds("\x1d\x00\x00") + status[:power].should eq(false) +end diff --git a/drivers/lg/displays/ls5.cr b/drivers/lg/displays/ls5.cr new file mode 100644 index 00000000000..fcef0f67754 --- /dev/null +++ b/drivers/lg/displays/ls5.cr @@ -0,0 +1,301 @@ +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/muteable" +require "placeos-driver/interface/switchable" + +class Lg::Displays::Ls5 < PlaceOS::Driver + include Interface::Powerable + include Interface::Muteable + + enum Input + Dvi = 0x70 + Hdmi = 0xA0 + HdmiDtv = 0x90 + Hdmi2 = 0xA1 + Hdmi2Dtv = 0x91 + DisplayPort = 0xD0 + DisplayPortDtv = 0xC0 + end + + include Interface::InputSelection(Input) + + # Discovery Information + tcp_port 9761 + descriptive_name "LG WebOS LCD Monitor" + generic_name :Display + # This device does not hold the connection open. Must be configured as makebreak + makebreak! + + default_settings({ + rs232_control: false, + display_id: 1, + }) + + @display_id : Int32 = 0 + @id_num : Int32 = 1 + @rs232 : Bool = false + @id : String = "" + @last_broadcast : String? = nil + @connected : Bool = false + + DELIMITER = 0x78_u8 # 'x' + + def on_load + # Communication settings + queue.delay = 150.milliseconds + transport.tokenizer = Tokenizer.new(Bytes[DELIMITER]) + on_update + end + + def on_update + @rs232 = setting(Bool, :rs232_control) + @id_num = setting(Int32, :display_id) + @id = @id_num.to_s.rjust(2, '0') + end + + def connected + @connected = true + self[:connected] = true + wake_on_lan + no_signal_off + auto_off + local_button_lock + pm_mode + schedule.every(50.seconds, true) do + do_poll + end + end + + def disconnected + @connected = false + self[:connected] = false + schedule.clear + end + + enum Command + Power = 0x61 # 'a' + Input = 0x62 # 'b' + AspectRatio = 0x63 # 'c' + ScreenMute = 0x64 # 'd' + VolumeMute = 0x65 # 'e' + Volume = 0x66 # 'f' + Contrast = 0x67 # 'g' + Brightness = 0x68 # 'h' + Sharpness = 0x6B # 'k' + AutoOff = 0x6E # 'n' + LocalButtonLock = 0x6F # 'o' + WakeOnLan = 0x77 # 'w' + NoSignalOff = 0x67 # 'g' + PmMode = 0x6E # 'n' + end + {% for name in Command.constants %} + @[Security(Level::Administrator)] + def {{name.id.underscore}}?(priority : Int32 = 0) + do_send(Command::{{name.id}}, 0xFF, priority: priority, name: {{name.id.underscore.stringify}} + "_status") + end + {% end %} + + def power(state : Bool, broadcast : String? = nil) + if state + if @rs232 + do_send(Command::Power, 1, name: "power", priority: 99) + else + wake(broadcast || @last_broadcast) + end + end + # To power on, unmute the display + # To power off, mute the display + mute(!state) if @connected + end + + def hard_off + do_send(Command::Power, 0, name: "power", priority: 99, clear_queue: true) + end + + def switch_to(input : Input, **options) + do_send(Command::Input, input.value, 'x', name: "input", delay: 2.seconds) + end + + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + if layer.video? || layer.audio_video? + do_send(Command::ScreenMute, state ? 1 : 0, name: "mute_video") + end + + if (layer.audio? || layer.audio_video?) && (self[:audio_mute]?.try &.as_bool) != state + do_send(Command::VolumeMute, state ? 0 : 1, name: "mute_audio") + end + + state + end + + enum Ratio + Square = 0x01 + Wide = 0x02 + Zoom = 0x04 + Scan = 0x09 + Program = 0x06 + end + + def aspect_ratio(ratio : Ratio) + do_send(Command::AspectRatio, ratio.value, name: "aspect_ratio", delay: 1.second) + end + + def do_poll + if @rs232 + power? + if self[:hard_power]?.try &.as_bool + screen_mute? + input? + volume_mute? + volume? + end + elsif @connected + screen_mute? + + if @id_num == 1 + input? + volume_mute? + volume? + end + elsif self[:power_target]?.try &.as_bool + power(true) + end + end + + def input?(priority : Int32 = 0) + do_send(Command::Input, 0xFF, 'x', priority: priority) + end + + {% for name in ["Volume", "Contrast", "Brightness", "Sharpness"] %} + @[Security(Level::Administrator)] + def {{name.id.downcase}}(value : Int32) + val = value.clamp(0, 100) + do_send(Command::{{name.id}}, val, name: {{name.id.downcase.stringify}}) + end + {% end %} + + # This is only necessary for Command::PmMode and Command::NoSignalOff + # Both the responses for contrast/no_signal_off will have data[0] == 'g' + # Same thing for auto_off/pm_mode with data[0] == 'n' + # We will use the send and callback method to ensure these responses are processed properly + private def process_response(data, task) + if (resp_value = get_response_value(data)) == -1 + task.abort + else + self[task.name] = task.name == "pm_mode" ? resp_value : resp_value == 1 + task.success + end + end + + def pm_mode(mode : Int32 = 3) + command = build_command(Command::PmMode, mode, 's') + send(command, name: "pm_mode") { |data, task| process_response(data, task) } + end + + def no_signal_off(state : Bool = false) + val = state ? 1 : 0 + command = build_command(Command::NoSignalOff, val, 'f') + send(command, name: "no_signal_off") { |data, task| process_response(data, task) } + end + + # 0 = Off, 1 = lock all except Power buttons, 2 = lock all buttons. Default to 2 as power off from local button results in network offline + def local_button_lock(state : Bool = true) + val = state ? 2 : 0 + do_send(Command::LocalButtonLock, val, 't', name: "local_button_lock") + end + + def auto_off(state : Bool = false) + val = state ? 1 : 0 + do_send(Command::AutoOff, val, 'm', name: "disable_auto_off") + end + + def wake_on_lan(state : Bool = true) + val = state ? 1 : 0 + do_send(Command::WakeOnLan, val, 'f', name: "enable_wake_on_lan") + end + + def wake(broadcast : String? = nil) + if mac = setting?(String, :mac_address) + # config is the database model representing this device + wake_device(mac, broadcast) + logger.debug { + info = "Wake on Lan for MAC #{mac}" + if b = broadcast + info += " directed to VLAN #{b}" + end + info + } + else + logger.warn { "No MAC address provided" } + end + end + + private def get_response_value(response : Bytes) + logger.debug { "LG sent #{response}" } + resp = String.new(response).split(' ').last + # Default to -1 which means an error + resp_value = -1 + if resp[0..1] == "OK" # Extract the response value + # Special case for PM Mode + if resp[2..3] == "0c" + resp_value = resp[4..-2].to_i(16) + else + resp_value = resp[2..-2].to_i(16) + end + end + resp_value + end + + def received(data, task) + return task.try &.abort if (resp_value = get_response_value(data)) == -1 + command = Command.from_value(data[0]) + logger.debug { "Received command #{command}" } + + case command + when .power? + self[:hard_power] = resp_value == 1 + self[:power] = false unless self[:hard_power].as_bool + when .input? + self[:input] = Input.from_value(resp_value) + when .aspect_ratio? + self[:aspect_ratio] = Ratio.from_value(resp_value) + when .screen_mute? + self[:power] = resp_value == 0 + when .volume_mute? + self[:audio_mute] = resp_value == 0 + when .contrast?, .brightness?, .sharpness?, .volume? + self[command.to_s.underscore] = resp_value + when .wake_on_lan?, .auto_off? + self[command.to_s.underscore] = resp_value == 1 + when .local_button_lock? + self[:local_button_lock] = resp_value == 2 + else + return task.try &.retry + end + + task.try &.success + end + + # From manual + # [Command1]: identifies between the factory setting and the user setting modes. + # Default c1 to 'k' which appears to be for user settings + # and which most commands use (e.g. Mute, Screen off, Volume, Brightness) + # Note: this is not a Command instance method as this needs access to @id + private def build_command(command : Command, data : Int, c1 : Char = 'k') + # Command::PmMode and Command::AutoOff both are equal to 0x6E == 'n' + # However, PmMode has c1 == 's' while AutoOff has c1 == 'm' + # So this is how we can differentiate whether the command we want to send is PmMode + if command.pm_mode? && c1 == 's' + "#{c1}#{command.value.chr} #{@id} 0c #{data.to_s(16, true).rjust(2, '0')}\r" + else + "#{c1}#{command.value.chr} #{@id} #{data.to_s(16, true).rjust(2, '0')}\r" + end + end + + private def do_send(command : Command, data : Int, c1 : Char = 'k', **options) + send(build_command(command, data, c1), **options) + end +end diff --git a/drivers/lg/displays/ls5_spec.cr b/drivers/lg/displays/ls5_spec.cr new file mode 100644 index 00000000000..6fc5141485c --- /dev/null +++ b/drivers/lg/displays/ls5_spec.cr @@ -0,0 +1,70 @@ +DriverSpecs.mock_driver "Lg::Displays::Ls5" do + # Execute a command (triggers the connection) + exec(:power?) + expect_reconnect + + # connected + # wake_on_lan(true) + should_send("fw 01 01\r") + responds("w 01 OK01x") + status[:wake_on_lan].should eq(true) + # no_signal_off(false) + should_send("fg 01 00\r") + responds("g 01 OK00x") + status[:no_signal_off].should eq(false) + # auto_off(false) + should_send("mn 01 00\r") + responds("n 01 OK00x") + status[:auto_off].should eq(false) + # local_button_lock(true) + should_send("to 01 02\r") + responds("o 01 OK02x") + status[:local_button_lock].should eq(true) + # pm_mode(3) + should_send("sn 01 0c 03\r") + responds("n 01 OK0c03x") + status[:pm_mode].should eq(3) + # do_poll && self[:connected] == true && @id_num == 1 + # screen_mute? + should_send("kd 01 FF\r") + responds("d 01 OK01x") + status[:power].should eq(false) + # input? + should_send("xb 01 FF\r") + responds("b 01 OKA0x") + status[:input].should eq("Hdmi") + # volume_mute? + should_send("ke 01 FF\r") + responds("e 01 OK00x") + status[:audio_mute].should eq(true) + # volume? + should_send("kf 01 FF\r") + responds("f 01 OK08x") + status[:volume].should eq(8) + + exec(:switch_to, "dvi") + should_send("xb 01 70\r") + responds("b 01 OK70x") + status[:input].should eq("Dvi") + + exec(:power, true) + sleep 2 # since switch_to has 2 seconds of delay + # mute_video(false) + should_send("kd 01 00\r") + responds("d 01 OK00x") + status[:power].should eq(true) + # mute_audio(false) + should_send("ke 01 01\r") + responds("e 01 OK01x") + status[:audio_mute].should eq(false) + + exec(:power, false) + # mute_video(true) + should_send("kd 01 01\r") + responds("d 01 OK01x") + status[:power].should eq(false) + # mute_audio(true) + should_send("ke 01 00\r") + responds("e 01 OK00x") + status[:audio_mute].should eq(true) +end diff --git a/drivers/lumens/dc193.cr b/drivers/lumens/dc193.cr new file mode 100644 index 00000000000..cb6005fa976 --- /dev/null +++ b/drivers/lumens/dc193.cr @@ -0,0 +1,245 @@ +module Lumens; end + +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/zoomable" + +# Documentation: https://aca.im/driver_docs/Lumens/DC193-Protocol.pdf +# RS232 controlled device + +class Lumens::DC193 < PlaceOS::Driver + include Interface::Powerable + include Interface::Zoomable + + # Discovery Information + descriptive_name "Lumens DC 193 Document Camera" + generic_name :DocCam + + # Global Cache Port + tcp_port 4999 + + def on_load + # Communication settings + queue.delay = 100.milliseconds + transport.tokenizer = Tokenizer.new(6) + + # Ensure range is roughly accurate + @zoom_range = 0..@zoom_max + end + + def connected + schedule.every(50.seconds) { query_status } + query_status + end + + def disconnected + schedule.clear + end + + def query_status + # Responses are JSON encoded + if power?.get == "true" + lamp? + zoom? + frozen? + max_zoom? + picture_mode? + end + end + + def power(state : Bool) + state = state ? 0x01_u8 : 0x00_u8 + send Bytes[0xA0, 0xB0, state, 0x00, 0x00, 0xAF], name: :power + power? + end + + def power? + # item 58 call system status + send Bytes[0xA0, 0xB7, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def lamp(state : Bool, head_led : Bool = false) + return false if @frozen + + lamps = if state && head_led + 1_u8 + elsif state + 2_u8 + elsif head_led + 3_u8 + else + 0_u8 + end + + send Bytes[0xA0, 0xC1, lamps, 0x00, 0x00, 0xAF], name: :lamp + end + + def lamp? + send Bytes[0xA0, 0x50, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def zoom_to(position : Int32, auto_focus : Bool = true, index : Int32 | String = 0) + return false if @frozen + + position = (position < 0 ? 0 : @zoom_max) unless @zoom_range.includes?(position) + low = (position & 0xFF).to_u8 + high = ((position >> 8) & 0xFF).to_u8 + auto_focus = auto_focus ? 0x1F_u8 : 0x13_u8 + send Bytes[0xA0, auto_focus, low, high, 0x00, 0xAF], name: :zoom_to + end + + def zoom(direction : ZoomDirection, index : Int32 | String = 1) + return false if @frozen + + case direction + when ZoomDirection::Stop + send Bytes[0xA0, 0x10, 0x00, 0x00, 0x00, 0xAF] + # Ensures this request is at the normal priority and ordering is preserved + zoom?(priority: queue.priority) + # This prevents the auto-focus if someone starts zooming again + auto_focus(name: "zoom") + when ZoomDirection::In + send Bytes[0xA0, 0x11, 0x00, 0x00, 0x00, 0xAF], name: :zoom + when ZoomDirection::Out + send Bytes[0xA0, 0x11, 0x01, 0x00, 0x00, 0xAF], name: :zoom + end + end + + def auto_focus(name : String = "auto_focus") + return false if @frozen + + send Bytes[0xA0, 0xA3, 0x01, 0x00, 0x00, 0xAF], name: name + end + + def zoom?(priority : Int32 = 0) + send Bytes[0xA0, 0x60, 0x00, 0x00, 0x00, 0xAF], priority: priority + end + + def freeze(state : Bool) + state = state ? 1_u8 : 0_u8 + send Bytes[0xA0, 0x2C, state, 0x00, 0x00, 0xAF], name: :freeze + end + + def frozen? + send Bytes[0xA0, 0x78, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def picture_mode(state : String) + return false if @frozen + + mode = case state.downcase + when "photo" + 0x00_u8 + when "text" + 0x01_u8 + when "greyscale", "grayscale" + 0x02_u8 + else + raise ArgumentError.new("unknown picture mode #{state}") + end + send Bytes[0xA0, 0xA7, mode, 0x00, 0x00, 0xAF], name: :picture_mode + end + + def picture_mode? + send Bytes[0xA0, 0x51, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + def max_zoom? + send Bytes[0xA0, 0x8A, 0x00, 0x00, 0x00, 0xAF], priority: 0 + end + + @[Flags] + enum Status + Error + Ignored + Reserved1 + Reserved2 + Focusing + Zooming + Iris + Reserved3 + end + + COMMANDS = { + 0xC1_u8 => :lamp, + 0xB0_u8 => :power, + 0xB7_u8 => :power_staus, + 0xA7_u8 => :picture_mode, + 0xA3_u8 => :auto_focus, + 0x8A_u8 => :max_zoom, + 0x78_u8 => :frozen_status, + 0x60_u8 => :zoom_staus, + 0x51_u8 => :picture_mode_staus, + 0x50_u8 => :lamp_staus, + 0x2C_u8 => :freeze, + 0x1F_u8 => :zoom_direct_auto_focus, + 0x13_u8 => :zoom_direct, + 0x11_u8 => :zoom, + 0x10_u8 => :zoom_stop, + } + + @ready : Bool = true + @power : Bool = false + @zoom_max : Int32 = 864 + @lamp : Bool = false + @head_led : Bool = false + @frozen : Bool = false + + PICTURE_MODES = {:photo, :test, :greyscale} + + def received(data, task) + logger.debug { "Lumens sent: #{data.hexstring}" } + + status = Status.from_value(data[4].to_i) + self[:zooming] = status.zooming? + self[:focusing] = status.focusing? + self[:iris_adjusting] = status.iris? + + return task.try &.abort("bad request") if status.error? + return task.try &.retry("device busy") if status.ignored? + + result = case COMMANDS[data[1]]? + when :power + data[2] == 0x01_u8 + when :power_staus + @ready = data[2] == 0x01_u8 + @power = data[3] == 0x01_u8 + logger.debug { "System power: #{@power}, ready: #{@ready}" } + self[:ready] = @ready + self[:power] = @power + when :max_zoom + @zoom_max = data[2].to_i + (data[3].to_i << 8) + @zoom_range = 0..@zoom_max + self[:zoom_range] = {min: 0, max: @zoom_max} + when :frozen_status, :freeze + self[:frozen] = @frozen = data[2] == 1_u8 + when :zoom_staus, :zoom_direct_auto_focus, :zoom_direct + @zoom = data[2].to_i + (data[3].to_i << 8) + self[:zoom] = @zoom + when :picture_mode_staus, :picture_mode + self[:picture_mode] = PICTURE_MODES[data[2].to_i] + when :lamp_staus, :lamp + case data[2] + when 0_u8 + @head_led = @lamp = false + when 1_u8 + @head_led = @lamp = true + when 2_u8 + @head_led = false + @lamp = true + when 3_u8 + @head_led = true + @lamp = false + end + self[:head_led] = @head_led + self[:lamp] = @lamp + when :auto_focus + # Can ignore this response + else + error = "Unknown command #{data[1]}" + logger.debug { error } + return task.try &.abort(error) + end + + task.try &.success(result) + end +end diff --git a/drivers/lumens/dc193_spec.cr b/drivers/lumens/dc193_spec.cr new file mode 100644 index 00000000000..8ec70b4692d --- /dev/null +++ b/drivers/lumens/dc193_spec.cr @@ -0,0 +1,8 @@ +DriverSpecs.mock_driver "Lumens::DC193" do + # On connect it queries the state of the device + should_send(Bytes[0xA0, 0xB7, 0x00, 0x00, 0x00, 0xAF]) + transmit(Bytes[0xA0, 0xB7, 0x01, 0x00, 0x00, 0xAF]) + + status[:ready].should be_true + status[:power].should be_false +end diff --git a/drivers/lutron/lighting.cr b/drivers/lutron/lighting.cr new file mode 100644 index 00000000000..fd06cccd15a --- /dev/null +++ b/drivers/lutron/lighting.cr @@ -0,0 +1,219 @@ +module Lutron; end + +# Documentation: https://aca.im/driver_docs/Lutron/lutron-lighting.pdf + +# Device defaults +# Login #1: nwk +# Login #2: nwk2 + +# Login: lutron +# Password: integration + +class Lutron::Lighting < PlaceOS::Driver + # Discovery Information + tcp_port 23 + descriptive_name "Lutron Lighting Gateway" + generic_name :Lighting + + def on_load + # Communication settings + queue.wait = false + queue.delay = 100.milliseconds + transport.tokenizer = Tokenizer.new("\r\n") + + on_update + end + + @trigger_type : String = "area" + @login : String = "nwk" + + def on_update + @login = setting?(String, :login) || "nwk" + @trigger_type = setting?(String, :trigger) || "area" + end + + def connected + send "#{@login}\r\n", priority: 9999 + + schedule.every(40.seconds) do + logger.debug { "-- Polling Lutron" } + scene? 1 + end + end + + def disconnected + schedule.clear + end + + def restart + send_cmd "RESET", 0 + end + + # on or off + def lighting(device : Int32, state : Bool, action : Int32 = 1) + level = state ? 100 : 0 + light_level(device, level) + end + + # =============== + # OUTPUT COMMANDS + # =============== + + # dimmers, CCOs, or other devices in a system that have a controllable output + def level( + device : Int32, + level : Int32, + rate : Int32 = 1000, + component : String = "output" + ) + level = level.clamp(0, 100) + seconds = rate / 1000 + min = seconds / 60 + seconds -= min * 60 + time = "#{min.to_s.rjust(2, '0')}:#{seconds.to_s.rjust(2, '0')}" + send_cmd component.upcase, device, 1, level, time + end + + def blinds(device : String, action : String, component : String = "shadegrp") + case action.downcase + when "raise", "up" + send_cmd component.upcase, device, 3 + when "lower", "down" + send_cmd component.upcase, device, 2 + when "stop" + send_cmd component.upcase, device, 4 + end + end + + # ============= + # AREA COMMANDS + # ============= + def scene(area : Int32, scene : Int32, component : String = "area") + send_cmd(component.upcase, area, 6, scene).get + scene?(area, component) + end + + def scene?(area : Int32, component : String = "area") + send_query component.upcase, area, 6 + end + + def occupancy?(area : Int32) + send_query "AREA", area, 8 + end + + def daylight_mode?(area : Int32) + send_query "AREA", area, 7 + end + + def daylight(area : Int32, mode : Bool) + val = mode ? 1 : 2 + send_cmd "AREA", area, 7, val + end + + # =============== + # DEVICE COMMANDS + # =============== + def button_press(area : Int32, button : Int32) + send_cmd "DEVICE", area, button, 3 + end + + def led(area : Int32, device : Int32, state : Int32 | Bool) + val = if state.is_a?(Int32) + state + else + state ? 1 : 0 + end + + send_cmd "DEVICE", area, device, 9, val + end + + def led?(area : Int32, device : Int32) + send_query "DEVICE", area, device, 9 + end + + # ============= + # COMPATIBILITY + # ============= + def trigger(area : Int32, scene : Int32) + scene(area, scene, @trigger_type) + end + + def light_level(area : Int32, level : Int32, component : String? = nil, fade : Int32 = 1000) + if component + level(area, level, fade, component) + else + level(area, level, fade, "area") + end + end + + Errors = { + "1" => "Parameter count mismatch", + "2" => "Object does not exist", + "3" => "Invalid action number", + "4" => "Parameter data out of range", + "5" => "Parameter data malformed", + "6" => "Unsupported Command", + } + + Occupancy = { + "1" => "unknown", + "2" => "inactive", + "3" => "occupied", + "4" => "unoccupied", + } + + def received(data, task) + data = String.new(data) + logger.debug { "Lutron sent: #{data}" } + + parts = data.split(",") + component = parts[0][1..-1].downcase + + case component + when "area", "output", "shadegrp" + area = parts[1] + action = parts[2].to_i + param = parts[3] + + case action + when 1 # level + self["#{component}#{area}_level"] = param.to_f + when 6 # Scene + self["#{component}#{area}"] = param.to_i + when 7 + self["#{component}#{area}_daylight"] = param == "1" + when 8 + self["#{component}#{area}_occupied"] = Occupancy[param] + end + when "device" + area = parts[1] + device = parts[2] + action = parts[3].to_i + + case action + when 7 # Scene + self["device#{area}_#{device}"] = parts[4].to_i + when 9 # LED state + self["device#{area}_#{device}_led"] = parts[4].to_i + end + when "error" + error = "error #{parts[1]}: #{Errors[parts[1]]}" + logger.warn { error } + return task.try &.abort(error) + end + + task.try &.success + end + + protected def send_cmd(*command) + cmd = "##{command.join(",")}" + logger.debug { "Requesting: #{cmd}" } + send("#{cmd}\r\n") + end + + protected def send_query(*command) + cmd = "?#{command.join(",")}" + logger.debug { "Querying: #{cmd}" } + send("#{cmd}\r\n") + end +end diff --git a/drivers/lutron/lighting_spec.cr b/drivers/lutron/lighting_spec.cr new file mode 100644 index 00000000000..c7a6b30945b --- /dev/null +++ b/drivers/lutron/lighting_spec.cr @@ -0,0 +1,34 @@ +DriverSpecs.mock_driver "Lutron::Lighting" do + # Module waits for this text to become ready + transmit "login: " + should_send "nwk\r\n" + transmit "connection established\r\n" + + # Perform actions + response = exec(:scene?, area: 1) + should_send("?AREA,1,6\r\n") + responds("~AREA,1,6,2\r\n") + response.get + status[:area1].should eq(2) + + transmit "~DEVICE,1,6,9,1\r\n" + status[:device1_6_led].should eq(1) + + transmit "~AREA,1,6,1\r\n" + status[:area1].should eq(1) + + transmit "~OUTPUT,53,1,100.00\r\n" + status[:output53_level].should eq(100.00) + + transmit "~SHADEGRP,26,1,100.00\r\n" + status[:shadegrp26_level].should eq(100.00) + + exec(:scene, area: 1, scene: 3) + should_send("#AREA,1,6,3\r\n") + responds("\r\n") + + should_send("?AREA,1,6\r\n") + transmit "~AREA,1,6,3\r\n" + + status[:area1].should eq(3) +end diff --git a/drivers/message_media/sms.cr b/drivers/message_media/sms.cr new file mode 100644 index 00000000000..f593f310a47 --- /dev/null +++ b/drivers/message_media/sms.cr @@ -0,0 +1,62 @@ +module MessageMedia; end + +# Documentation: https://developers.messagemedia.com/code/messages-api-documentation/ +require "placeos-driver/interface/sms" + +class MessageMedia::SMS < PlaceOS::Driver + include Interface::SMS + + # Discovery Information + generic_name :SMS + descriptive_name "MessageMedia SMS service" + uri_base "https://api.messagemedia.com" + + default_settings({ + basic_auth: { + username: "srvc_acct", + password: "password!", + }, + }) + + def on_load + on_update + end + + def on_update + end + + def send_sms( + phone_numbers : String | Array(String), + message : String, + format : String? = "SMS", + source : String? = nil + ) + phone_numbers = [phone_numbers] unless phone_numbers.is_a?(Array) + + # Could be MMS etc + format = format || "SMS" + + numbers = phone_numbers.map do |number| + payload = { + :content => message, + :destination_number => number, + :format => format, + } + if source + payload[:source_number] = source.to_s + payload[:source_number_type] = "ALPHANUMERIC" + end + payload + end + + response = post("/v1/messages", body: { + messages: numbers, + }.to_json, headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + }) + + raise "request failed with #{response.status_code}" unless response.status_code == 202 + nil + end +end diff --git a/drivers/message_media/sms_spec.cr b/drivers/message_media/sms_spec.cr new file mode 100644 index 00000000000..537be2d7cea --- /dev/null +++ b/drivers/message_media/sms_spec.cr @@ -0,0 +1,27 @@ +DriverSpecs.mock_driver "MessageMedia::SMS" do + # Send the request + retval = exec(:send_sms, + phone_numbers: "+61418419954", + message: "hello steve" + ) + + # sms should send a HTTP request + expect_http_request do |request, response| + headers = request.headers + io = request.body + if io + data = io.gets_to_end + request = JSON.parse(data) + if request["messages"][0]["content"] == "hello steve" && headers["Authorization"]? == "Basic #{Base64.strict_encode("srvc_acct:password!")}" + response.status_code = 202 + else + response.status_code = 401 + end + else + raise "expected request to include dialing details #{request.inspect}" + end + end + + # What the sms function should return + retval.get.should eq(nil) +end diff --git a/drivers/microsoft/FindMe Service API.TXT b/drivers/microsoft/FindMe Service API.TXT new file mode 100644 index 00000000000..d0a726c550e --- /dev/null +++ b/drivers/microsoft/FindMe Service API.TXT @@ -0,0 +1,58 @@ +*List of available buildings and levels. Returns number of people "findable" on each level* + +GET /FindMeService/api/MeetingRooms/BuildingLevelsWithMeetingRooms + +[{"Building":"SYDNEY","Level":"0","Online":13},{"Building":"SYDNEY","Level":"2","Online":14},{"Building":"SYDNEY","Level":"3","Online":18}] + +*List of meeting rooms in a building/level* + +GET /FindMeService/api/MeetingRooms/Level/SYDNEY/2 + +[{"Alias":"cf2020","Name":"Minogue","Building":"SYDNEY","Level":"2","LocationDescription":"2020","X":null,"Y":null,"Capacity":4,"Features":null,"CanBeBooked":true,"PhotoUrl":null,"HasAV":false,"HasDeskPhone":true,"HasSpeakerPhone":false,"HasWhiteboard":true}] + +You need to honour the CanBeBooked flag - don't try to book room that can't be booked! + +*Meetings for all rooms on a given level* + +Due to the design of our kiosk and web site we always get all meetings for all rooms. + +GET /FindMeService/api/MeetingRooms/Meetings/SYDNEY/2/2015-11-12T02:11:41/2015-11-15T02:11:41 +GET /FindMeService/api/MeetingRooms/Meetings/Building/Level/StartDate/EndDate + +[{"ConferenceRoomAlias":"cfsydinx","Start":"2015-11-11T23:30:00+00:00","End":"2015-11-12T00:00:00+00:00","Subject":"","Location":"Pty MR Syd L2 INXS (10) RT Int","BookingUserAlias":null,"StartTimeZoneName":null,"EndTimeZoneName":null},{"ConferenceRoomAlias":"cfsydinx","Start":"2015-11-12T23:00:00+00:00","End":"2015-11-13T00:00:00+00:00","Subject":"","Location":"Pty MR Syd L2 INXS (10) RT Int","BookingUserAlias":null,"StartTimeZoneName":null,"EndTimeZoneName":null},{"ConferenceRoomAlias":"cfsydsky","Start":"2015-11-13T01:00:00+00:00","End":"2015-11-13T03:00:00+00:00","Subject":"","Location":"Sydney team: Pty MR Syd L2 Skyhooks (10) RT","BookingUserAlias":null,"StartTimeZoneName":null,"EndTimeZoneName":null}] + +*Schedule a Meeting* + +POST /FindMeService/api/MeetingRooms/ScheduleMeeting + +{"ConferenceRoomAlias":"cf2205","Start":"2015-11-13T18:00:00","End":"2015-11-13T18:30:00","Subject":" String?, + headers : Hash(String, String) | HTTP::Headers = HTTP::Headers.new + ) : String + headers["Authorization"] = @auth_token unless @auth_token.empty? + response = http(method, path, body, params, headers) + + if response.status_code == 401 && response.headers["WWW-Authenticate"]? + supported = response.headers.get("WWW-Authenticate") + raise "doesn't support NTLM auth: #{supported}" unless supported.includes?("NTLM") + + # Negotiate NTLM + headers["Authorization"] = NTLM.negotiate_http(@domain) + response = http(method, path, body, params, headers) + + # Extract the challenge + raise "unexpected response #{response.status_code}" unless response.status_code == 401 && response.headers["WWW-Authenticate"]? + challenge = response.headers["WWW-Authenticate"] + + # Authenticate the client + @auth_token = NTLM.authenticate_http(challenge, @username, @password) + headers["Authorization"] = @auth_token + response = http(method, path, body, params, headers) + end + + raise "request #{path} failed with status: #{response.status_code}" unless response.success? + + response.body + end + + def levels + data = make_request("GET", "/FindMeService/api/MeetingRooms/BuildingLevelsWithMeetingRooms") + logger.debug { "levels request returned: #{data}" } + + levels = Array(Microsoft::Level).from_json(data) + buildings = Hash(String, Array(String)).new { |hash, key| hash[key] = [] of String } + levels.each { |level| buildings[level.building] << level.name } + + buildings + end + + def user_details(usernames : String | Array(String)) + users = usernames.is_a?(String) ? [usernames] : usernames + data = make_request("GET", "/FindMeService/api/ObjectLocation/Users/#{users.join(",")}?getExtendedData=true") + + logger.debug { "user details request returned #{data}" } + + Array(Microsoft::Location).from_json(data).reject { |loc| {"NoRecentData", "NoData"}.includes?(loc.status) } + end + + def users_on(building : String, level : String) + # Same response as above with or without ExtendedUserData + uri = "/FindMeService/api/ObjectLocation/Level/#{building}/#{level}" + # uri += "?getExtendedData=true" if extended_data + + data = make_request("GET", uri) + + begin + Array(Microsoft::Location).from_json(data).reject { |loc| {"NoRecentData", "NoData"}.includes?(loc.status) } + rescue error + logger.debug { "failed to parse location data\n#{data}" } + raise error + end + end +end diff --git a/drivers/microsoft/find_me_location_service.cr b/drivers/microsoft/find_me_location_service.cr new file mode 100644 index 00000000000..e800f579529 --- /dev/null +++ b/drivers/microsoft/find_me_location_service.cr @@ -0,0 +1,198 @@ +module Microsoft; end + +require "json" +require "oauth2" +require "s2_cells" +require "placeos-driver/interface/locatable" +require "./find_me_models" + +class Microsoft::FindMeLocationService < PlaceOS::Driver + include Interface::Locatable + + descriptive_name "FindMe Location Service" + generic_name :FindMeLocationService + description %(collects desk usage and wireless locations for visualising on a map) + + accessor findme : FindMe_1 + + default_settings({ + map_id_prefix: "table-", + + floor_mappings: { + "zone-id": { + building: "SYDNEY", + level: "L14", + }, + }, + + building_zone: "zone-building", + s2_level: 21, + }) + + @building_zone : String = "" + @floor_mappings : Hash(String, NamedTuple(building: String, level: String)) = {} of String => NamedTuple(building: String, level: String) + @zone_filter : Array(String) = [] of String + @map_id_prefix : String = "table-" + @s2_level : Int32 = 21 + + def on_load + on_update + end + + def on_update + @map_id_prefix = setting?(String, :map_id_prefix).presence || "table-" + + @building_zone = setting(String, :building_zone) + @floor_mappings = setting(Hash(String, NamedTuple(building: String, level: String)), :floor_mappings) + @zone_filter = @floor_mappings.keys + @s2_level = setting?(Int32, :s2_level) || 21 + end + + # =================================== + # Locatable Interface functions + # =================================== + def locate_user(email : String? = nil, username : String? = nil) + logger.debug { "searching for #{email}, #{username}" } + + locations_raw = findme.user_details(username).get.to_json + locations = Array(Microsoft::Location).from_json locations_raw + + locations = locations.compact_map do |location| + coords = location.coordinates + next unless coords + + level = findme_building = findme_level = "" + @floor_mappings.each do |zone, details| + findme_building = details[:building] + findme_level = details[:level] + + if findme_building == coords.building && findme_level == coords.level + level = zone + break + end + end + + next if level.empty? + + build_location_response(location, level, findme_building, findme_level) + end + + locations + end + + def macs_assigned_to(email : String? = nil, username : String? = nil) : Array(String) + logger.debug { "listing MAC addresses assigned to #{email}, #{username}" } + + active_users_raw = findme.user_details(username || email).get.to_json + active_users = Array(Microsoft::Location).from_json active_users_raw + + found = [] of String + if user_details = active_users[0]? + found << user_details.username + end + found + end + + def check_ownership_of(mac_address : String) : OwnershipMAC? + logger.debug { "searching for owner of #{mac_address}" } + + active_users_raw = findme.user_details(mac_address).get.to_json + active_users = Array(Microsoft::Location).from_json active_users_raw + + if user_details = active_users[0]? + { + location: user_details.located_using == "FixedLocation" ? "desk" : "wireless", + assigned_to: user_details.user_data.not_nil!.email_address || "", + mac_address: mac_address, + } + end + end + + def device_locations(zone_id : String, location : String? = nil) + logger.debug { "searching devices in zone #{zone_id}" } + return [] of Nil unless @zone_filter.includes?(zone_id) + + findme_details = @floor_mappings[zone_id]? + return [] of Nil unless findme_details + + findme_building = findme_details[:building] + findme_level = findme_details[:level] + active_users_raw = findme.users_on(findme_building, findme_level).get.to_json + active_users = Array(Microsoft::Location).from_json active_users_raw + + locations = active_users.compact_map do |loc| + build_location_response(loc, zone_id, findme_building, findme_level, location) + end + + locations + end + + protected def build_location_response(location, zone_id, findme_building, findme_level, loc_type = nil) + case location.located_using + when "FixedLocation" + return if loc_type.presence && loc_type != "desk" + + location_id = "#{@map_id_prefix}#{location.location_id}" + + loc = { + location: :desk, + at_location: 1, + map_id: location_id, + level: zone_id, + building: @building_zone, + mac: location.username, + last_seen: location.last_update.to_unix, + capacity: 1, + + findme_building: findme_building, + findme_level: findme_level, + findme_status: location.status, + findme_type: location.type, + } + + loc + when "WiFi" + return if loc_type.presence && loc_type != "wireless" + + coordinates = location.coordinates + return unless coordinates + + if gps = location.gps + lat = gps.latitude + lon = gps.longitude + end + + # Based on the confidence % and a max variance of 20m + variance = 20 - (20 * (location.confidence / 100)) + + loc = { + location: :wireless, + coordinates_from: "top-left", + x: coordinates.x, + y: coordinates.y, + # x,y coordinates are % based so map width and height are out of 100 + map_width: 100, + # by not returning map height, it indicates that a relative height should be calculated + # map_height: 100, + lon: lon, + lat: lat, + s2_cell_id: lat ? S2Cells::LatLon.new(lat.not_nil!, lon.not_nil!).to_token(@s2_level) : nil, + + mac: location.username, + variance: variance, + + last_seen: location.last_update.to_unix, + level: zone_id, + building: @building_zone, + + findme_building: findme_building, + findme_level: findme_level, + findme_status: location.status, + findme_type: location.type, + } + else + logger.info { "unexpected location type #{location.located_using}" } + nil + end + end +end diff --git a/drivers/microsoft/find_me_location_service_spec.cr b/drivers/microsoft/find_me_location_service_spec.cr new file mode 100644 index 00000000000..a5e94949941 --- /dev/null +++ b/drivers/microsoft/find_me_location_service_spec.cr @@ -0,0 +1,83 @@ +DriverSpecs.mock_driver "Microsoft::FindMeLocationService" do + system({ + FindMe: {FindMe}, + }) + + now = Time.local + start = now.at_beginning_of_day.to_unix + ending = now.at_end_of_day.to_unix + + resp = exec(:device_locations, "zone-id").get + puts resp + resp.should eq([ + { + "location" => "wireless", + "coordinates_from" => "top-left", + "x" => 76.0, + "y" => 29.0, + "map_width" => 100, + "lon" => 151.1382508278, + "lat" => -33.796597429, + "s2_cell_id" => "6b12a5f8f0c4", + "mac" => "dwatson", + "variance" => 0.0, + "last_seen" => 1447295150, + "level" => "zone-id", + "building" => "zone-building", + "findme_building" => "SYDNEY", + "findme_level" => "L14", + "findme_status" => "Located", + "findme_type" => "Person", + }, { + "location" => "desk", + "at_location" => 1, + "map_id" => "table-11.097", + "level" => "zone-id", + "building" => "zone-building", + "mac" => "acorder003", + "last_seen" => 1608185586, + "capacity" => 1, + "findme_building" => "SYDNEY", + "findme_level" => "L14", + "findme_status" => "NoRecentData", + "findme_type" => "Person", + }, + ]) +end + +class FindMe < DriverSpecs::MockDriver + def user_details(usernames : String | Array(String)) + JSON.parse %([{"Alias":"dwatson","LastUpdate":"2015-11-12T02:25:50.017Z","Confidence":100, + "Coordinates":{"Building":"SYDNEY","Level":"L14","X":76,"Y":29,"LocationDescription":"2140","MapByLocationId":true}, + "GPS":{"Latitude":-33.796597429,"Longitude":151.1382508278,"Accuracy":0.0,"LocationDescription":null}, + "LocationIdentifier":null,"Status":"Located","LocatedUsing":"FixedLocation","Type":"Person","Comments":null, + "ExtendedUserData":{"Alias":"dwatson","DisplayName":"David Watson","EmailAddress":"David.Watson@microsoft.com","LyncSipAddress":"dwatson@microsoft.com"}}]) + end + + def users_on(building : String, level : String) + # Wireless and a desk + JSON.parse %([{"Alias":"dwatson","LastUpdate":"2015-11-12T02:25:50.017Z","Confidence":100, + "Coordinates":{"Building":"SYDNEY","Level":"L14","X":76,"Y":29,"LocationDescription":"2140","MapByLocationId":true}, + "GPS":{"Latitude":-33.796597429,"Longitude":151.1382508278,"Accuracy":0.0,"LocationDescription":null}, + "LocationIdentifier":null,"Status":"Located","LocatedUsing":"WiFi","Type":"Person","Comments":null, + "ExtendedUserData":{"Alias":"dwatson","DisplayName":"David Watson","EmailAddress":"David.Watson@microsoft.com","LyncSipAddress":"dwatson@microsoft.com"}}, + + { + "Alias": "acorder003", + "LastUpdate": "2020-12-17T06:13:06.797Z", + "CurrentUntil": "2020-12-17T06:16:06.797Z", + "Confidence": 100, + "Coordinates": null, + "GPS": null, + "LocationIdentifier": "11.097", + "Status": "NoRecentData", + "LocatedUsing": "FixedLocation", + "Type": "Person", + "Comments": null, + "ExtendedUserData": null, + "WiFiScale": 1.00, + "userTypes": [] + } + ]) + end +end diff --git a/drivers/microsoft/find_me_models.cr b/drivers/microsoft/find_me_models.cr new file mode 100644 index 00000000000..a1a4c84999f --- /dev/null +++ b/drivers/microsoft/find_me_models.cr @@ -0,0 +1,108 @@ +require "json" + +module Microsoft + class Level + include JSON::Serializable + + @[JSON::Field(key: "Building")] + getter building : String + + @[JSON::Field(key: "Level")] + getter name : String + + @[JSON::Field(key: "Online")] + getter online : Int32 + end + + class Coordinates + include JSON::Serializable + + @[JSON::Field(key: "Building")] + getter building : String + + @[JSON::Field(key: "Level")] + getter level : String + + @[JSON::Field(key: "X")] + getter x : Float64 + + @[JSON::Field(key: "Y")] + getter y : Float64 + end + + class GPS + include JSON::Serializable + + @[JSON::Field(key: "Latitude")] + getter latitude : Float64 + + @[JSON::Field(key: "Longitude")] + getter longitude : Float64 + end + + class UserData + include JSON::Serializable + + @[JSON::Field(key: "Alias")] + getter username : String? + + @[JSON::Field(key: "DisplayName")] + getter display_name : String? + + @[JSON::Field(key: "EmailAddress")] + getter email_address : String? + end + + # Example Response: + # [{"Alias":"dwatson","LastUpdate":"2015-11-12T02:25:50.017Z","Confidence":100, + # "Coordinates":{"Building":"SYDNEY","Level":"2","X":76,"Y":29,"LocationDescription":"2140","MapByLocationId":true}, + # "GPS":{"Latitude":-33.796597429,"Longitude":151.1382508278,"Accuracy":0.0,"LocationDescription":null}, + # "LocationIdentifier":null,"Status":"Located","LocatedUsing":"FixedLocation","Type":"Person","Comments":null, + # "ExtendedUserData":{"Alias":"dwatson","DisplayName":"David Watson","EmailAddress":"David.Watson@microsoft.com","LyncSipAddress":"dwatson@microsoft.com"}}] + class Location + include JSON::Serializable + + module RFC3339Converter + def self.from_json(value : JSON::PullParser) : Time + Time::Format::RFC_3339.parse(value.read_string) + end + + def self.to_json(value : Time, json : JSON::Builder) + json.string(Time::Format::RFC_3339.format(value, 1)) + end + end + + @[JSON::Field(key: "Alias")] + getter username : String + + @[JSON::Field( + key: "LastUpdate", + converter: Microsoft::Location::RFC3339Converter + )] + getter last_update : Time + + @[JSON::Field(key: "Confidence")] + getter confidence : Float64 + + @[JSON::Field(key: "Coordinates")] + getter coordinates : Coordinates? + + @[JSON::Field(key: "GPS")] + getter gps : GPS? + + @[JSON::Field(key: "LocationIdentifier")] + getter location_id : String? + + @[JSON::Field(key: "Status")] + getter status : String + + @[JSON::Field(key: "LocatedUsing")] + getter located_using : String? + + @[JSON::Field(key: "Type")] + getter type : String? + + @[JSON::Field(key: "ExtendedUserData")] + getter user_data : UserData? + end +end diff --git a/drivers/microsoft/find_me_spec.cr b/drivers/microsoft/find_me_spec.cr new file mode 100644 index 00000000000..54399cf1401 --- /dev/null +++ b/drivers/microsoft/find_me_spec.cr @@ -0,0 +1,33 @@ +DriverSpecs.mock_driver "Microsoft::FindMe" do + # Send the request + retval = exec(:levels) + + # sms should send a HTTP request + expect_http_request do |request, response| + response.status_code = 200 + response << %([{"Building":"SYDNEY","Level":"0","Online":13},{"Building":"SYDNEY","Level":"2","Online":14}]) + end + + # What the sms function should return + retval.get.should eq({ + "SYDNEY" => ["0", "2"], + }) + + # Send the request + retval = exec(:user_details, "mbenz") + details_response = %([{"Alias":"mbenz","LastUpdate":"2020-12-15T13:22:00.8675244Z","CurrentUntil":"0001-01-01T00:00:00","Confidence":0,"Coordinates":null,"GPS":null,"LocationIdentifier":null,"Status":"NoData","LocatedUsing":null,"Type":null,"Comments":null,"ExtendedUserData":null,"WiFiScale":0.0,"userTypes":null}]) + expect_http_request do |request, response| + response.status_code = 200 + response << details_response + end + retval.get.should eq([] of JSON::Any) + + # Check the time format works + retval = exec(:user_details, "mbenz") + details_response = %([{"Alias":"mbenz","LastUpdate":"2020-12-15T13:22:00Z","CurrentUntil":"0001-01-01T00:00:00","Confidence":0,"Coordinates":null,"GPS":null,"LocationIdentifier":null,"Status":"NoData","LocatedUsing":null,"Type":null,"Comments":null,"ExtendedUserData":null,"WiFiScale":0.0,"userTypes":null}]) + expect_http_request do |request, response| + response.status_code = 200 + response << details_response + end + retval.get.should eq([] of JSON::Any) +end diff --git a/drivers/mulesoft/booking_api.cr b/drivers/mulesoft/booking_api.cr new file mode 100644 index 00000000000..878aa994820 --- /dev/null +++ b/drivers/mulesoft/booking_api.cr @@ -0,0 +1,178 @@ +module MuleSoft; end + +require "./models" + +class MuleSoft::BookingsAPI < PlaceOS::Driver + descriptive_name "MuleSoft Bookings API" + generic_name :Bookings + description %(Retrieves and creates bookings using the MuleSoft API) + uri_base "https://api.sydney.edu.au" + + default_settings({ + venue_code: "venue code", + base_path: "/usyd-edu-timetable-exp-api-v1/v1/", + polling_period: 30, + time_zone: "Australia/Sydney", + ssl_key: "private key", + ssl_cert: "certificate", + ssl_auth_enabled: true, + username: "basic auth username", + password: "basic auth password", + basic_auth_enabled: true, + running_a_spec: false, + }) + + @username : String = "" + @password : String = "" + @base_path : String = "" + @context : OpenSSL::SSL::Context::Client = OpenSSL::SSL::Context::Client.new + @host : String = "" + @venue_code : String = "" + @bookings : Array(Booking) = [] of Booking + @time_zone : Time::Location = Time::Location.load("Australia/Sydney") + @ssl_auth_enabled : Bool = false + @basic_auth_enabled : Bool = false + @runing_a_spec : Bool = false + + def on_load + on_update + end + + def on_update + schedule.clear + @running_a_spec = !!setting(Bool, :running_a_spec) + + @username = setting(String, :username) + @password = setting(String, :password) + @basic_auth_enabled = !!setting?(Bool, :basic_auth_enabled) + logger.debug { "basic_auth_enabled is #{@basic_auth_enabled}" } + + @base_path = setting(String, :base_path) + @venue_code = setting(String, :venue_code) + + @host = URI.parse(config.uri.not_nil!).host.not_nil! + + time_zone = setting?(String, :calendar_time_zone).presence + @time_zone = Time::Location.load(time_zone) if time_zone + + @ssl_auth_enabled = !!setting?(Bool, :ssl_auth_enabled) + save_ssl_credentials if @ssl_auth_enabled + logger.debug { "ssl_auth_enabled is #{@ssl_auth_enabled}" } + + schedule.in(Random.rand(60).seconds + Random.rand(1000).milliseconds) { poll_bookings } + + polling_period = (setting?(UInt32, :polling_period) || 5_u32).minutes + polling_period += Random.rand(30).seconds + Random.rand(1000).milliseconds + schedule.every(polling_period) { poll_bookings } + end + + def poll_bookings + now = Time.local @time_zone + from = now - 1.week + to = now + 1.week + + logger.debug { "polling bookings #{@venue_code}, from #{from}, to #{to}, in #{@time_zone.name}" } + + query_bookings(@venue_code, from, to) + + check_current_booking + end + + def check_current_booking + now = Time.utc.to_unix + previous_booking = nil + current_booking = nil + next_booking = Int32::MAX + + @bookings.each_with_index do |event, index| + starting = event.event_start + + # All meetings are in the future + if starting > now + next_booking = index + previous_booking = index - 1 if index > 0 + break + end + + # Calculate event end time + ending_unix = event.event_end + + # Event ended in the past + next if ending_unix < now + + # We've found the current event + if starting <= now && ending_unix > now + current_booking = index + previous_booking = index - 1 if index > 0 + next_booking = index + 1 + break + end + end + + if next_booking >= (@bookings.size - 1) + next_booking = nil + end + + self[:previous_booking] = previous_booking ? @bookings[previous_booking].to_placeos : nil + self[:current_booking] = current_booking ? @bookings[current_booking].to_placeos : nil + self[:next_booking] = next_booking ? @bookings[next_booking].to_placeos : nil + end + + def query_bookings(venue_code : String, starts_at : Time = Time.local.at_beginning_of_day, ends_at : Time = Time.local.at_end_of_day) + client = HTTP::Client.new(host: @host, tls: (@ssl_auth_enabled ? @context : nil)) + + params = { + "startDateTime" => starts_at.to_s("%FT%T"), + "endDateTime" => ends_at.to_s("%FT%T"), + }.map { |k, v| "#{k}=#{v}" }.join("&") + + headers = HTTP::Headers{ + "Content-Type" => "application/json", + "Accept" => "application/json", + } + + if @basic_auth_enabled + headers.add("Authorization", "Basic #{Base64.strict_encode("#{@username}:#{@password}")}") + end + + if @running_a_spec + response = get("#{@base_path}/venues/#{venue_code}/bookings?#{params}", headers: headers) + else + response = client.get("#{@base_path}/venues/#{venue_code}/bookings?#{params}", headers: headers) + end + + raise "request failed with #{response.status_code}: #{response.body}" unless (200...300).includes?(response.status_code) + + # when there's no results, it seems to return just an empty response rather than an empty array? + if response.body.presence != nil + results = BookingResults.from_json(response.body) + + self[:venue_code] = results.venue_code + self[:venue_name] = results.venue_name + + @bookings = results.bookings.sort { |a, b| a.event_start <=> b.event_start } + self[:bookings] = @bookings.map(&.to_placeos) + else + self[:venue_code] = nil + self[:venue_name] = nil + self[:bookings] = nil + end + end + + def query_bookings_epoch(venue_code : String, starts_at : Int32, ends_at : Int32) + query_bookings(venue_code, Time.unix(starts_at), Time.unix(ends_at)) + end + + protected def save_ssl_credentials + [:ssl_key, :ssl_cert].each do |key| + raise "Required setting #{key} left blank" unless setting(String, key).presence + + File.open("./pkey-#{module_id}.#{key}", "w") do |cert| + cert.puts setting(String, key) + end + end + + @context.private_key = "./pkey-#{module_id}.ssl_key" + @context.certificate_chain = "./pkey-#{module_id}.ssl_cert" + end +end diff --git a/drivers/mulesoft/booking_api_spec.cr b/drivers/mulesoft/booking_api_spec.cr new file mode 100644 index 00000000000..b8890217861 --- /dev/null +++ b/drivers/mulesoft/booking_api_spec.cr @@ -0,0 +1,50 @@ +DriverSpecs.mock_driver "MuleSoft::API" do + settings({ + venue_code: "venue code", + base_path: "/usyd-edu-timetable-exp-api-v1/v1/", + polling_period: 5, + time_zone: "Australia/Sydney", + ssl_key: "private key", + ssl_cert: "certificate", + ssl_auth_enabled: false, + username: "basic auth username", + password: "basic auth password", + basic_auth_enabled: false, + running_a_spec: true, + }) + + resp = exec(:query_bookings, "A14.02.K2.05") + + expect_http_request do |request, response| + starts_at = Time.local - 30.minutes + ends_at = starts_at + 1.hour + + response.status_code = 200 + response << <<-RESPONSE + { + "count": 1, + "timeTableBookingsCount": 1, + "casualBookingsCount": 0, + "venueCode": "A14.02.K2.05", + "venueName": "A14.02.K2.05.The Quadrangle.The Quad General Lecture Theatre K2.05", + "bookings": [ + { + "unitCode": "HSTY2630", + "unitName": "Panics and Pandemics", + "activityName": "HSTY2630-S1C-ND-CC/TUT/01", + "activityType": "Tutorial", + "activityDescription": "Tutorial", + "startDateTime": "#{starts_at.to_s("%FT%T")}", + "endDateTime": "#{ends_at.to_s("%FT%T")}", + "location": "Social Sciences Building - SSB Seminar Room 210", + "bookingType": "timeTable" + } + ] + } + RESPONSE + end + + resp.get + + exec(:check_current_booking).get +end diff --git a/drivers/mulesoft/models.cr b/drivers/mulesoft/models.cr new file mode 100644 index 00000000000..e1b515635c5 --- /dev/null +++ b/drivers/mulesoft/models.cr @@ -0,0 +1,61 @@ +module MuleSoft + class Booking + include JSON::Serializable + + @[JSON::Field(key: "unitName")] + property title : String? + + @[JSON::Field(key: "activityType")] + property body : String + + @[JSON::Field(key: "unitCode")] + property recurring_master_id : String? + + @[JSON::Field(key: "startDateTime", converter: MuleSoft::DateTimeConvertor)] + property event_start : Int64 + + @[JSON::Field(key: "endDateTime", converter: MuleSoft::DateTimeConvertor)] + property event_end : Int64 + + property location : String + + # we need this method to create an intermediary hash + # otherwise when to_json is called all the field names revert to the MuleSoft ones + def to_placeos + value = { + "title" => @title, + "body" => @body, + "recurring_master_id" => @recurring_master_id, + "event_start" => @event_start, + "event_end" => @event_end, + "location" => @location, + } + end + end + + class BookingResults + include JSON::Serializable + + property count : Int64 + + @[JSON::Field(key: "venueCode")] + property venue_code : String + + @[JSON::Field(key: "venueName")] + property venue_name : String + + property bookings : Array(Booking) + end + + module DateTimeConvertor + extend self + + def to_json(value, json : JSON::Builder) + json.string(Time.unix(value).to_local.to_s("%FT%T")) + end + + def from_json(pull : JSON::PullParser) + Time.parse(pull.read_string, "%FT%T", Time::Location.local).to_unix + end + end +end diff --git a/drivers/nec/display.cr b/drivers/nec/display.cr new file mode 100644 index 00000000000..33ad26e7f16 --- /dev/null +++ b/drivers/nec/display.cr @@ -0,0 +1,282 @@ +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/muteable" +require "placeos-driver/interface/switchable" + +class Nec::Display < PlaceOS::Driver + include Interface::Powerable + include Interface::AudioMuteable + + enum Input + Vga = 1 + Rgbhv = 2 + Dvi = 3 + HdmiSet = 4 + Video1 = 5 + Video2 = 6 + Svideo = 7 + Tuner = 9 + Tv = 10 + Dvd1 = 12 + Option = 13 + Dvd2 = 14 + DisplayPort = 15 + Hdmi = 17 + Hdmi2 = 18 + Hdmi3 = 130 + Usb = 135 + end + include PlaceOS::Driver::Interface::InputSelection(Input) + + # Discovery Information + tcp_port 7142 + descriptive_name "NEC Display" + generic_name :Display + + DELIMITER = 0x0D_u8 + + def on_load + # Communication settings + queue.delay = 120.milliseconds + queue.timeout = 5.seconds + transport.tokenizer = Tokenizer.new(Bytes[DELIMITER]) + end + + def connected + schedule.every(50.seconds, true) do + do_poll + end + end + + def disconnected + schedule.clear + end + + def power(state : Bool) + # Do nothing if already in desired state + return if self[:power]? == state + + if state + logger.debug { "requested to power on" } + # 1 = Power On + data = MsgType::Command.build(Command::SetPower, 1) + send(data, name: "power", delay: 5.seconds) + else + logger.debug { "requested to power off" } + # 4 = Power Off + data = MsgType::Command.build(Command::SetPower, 4) + send(data, name: "power", delay: 10.seconds, timeout: 10.seconds) + end + end + + def power?(**options) : Bool + data = MsgType::Command.build(Command::PowerQuery) + send(data, **options, name: "power?").get + self[:power].as_bool + end + + def switch_to(input : Input) + logger.debug { "requested to switch to: #{input}" } + data = MsgType::SetParameter.build(Command::VideoInput, input.value) + send(data, name: "input", delay: 6.seconds) + end + + enum Audio + Audio1 = 1 + Audio2 = 2 + Audio3 = 3 + Hdmi = 4 + Tv = 6 + DisplayPort = 7 + end + + def switch_audio(input : Audio) + logger.debug { "requested to switch audio to: #{input}" } + data = MsgType::SetParameter.build(Command::AudioInput, input.value) + send(data, name: "audio") + end + + def auto_adjust + data = MsgType::SetParameter.build(Command::AutoSetup, 1) + send(data, name: "auto_adjust") + end + + def brightness(val : Int32) + data = MsgType::SetParameter.build(Command::BrightnessStatus, val.clamp(0, 100)) + send(data, name: "brightness") + send(MsgType::Command.build(Command::Save), name: "save", priority: 0) + end + + def contrast(val : Int32) + data = MsgType::SetParameter.build(Command::ContrastStatus, val.clamp(0, 100)) + send(data, name: "contrast") + send(MsgType::Command.build(Command::Save), name: "save", priority: 0) + end + + def volume(val : Int32) + data = MsgType::SetParameter.build(Command::VolumeStatus, val.clamp(0, 100)) + send(data, name: "volume") + send(MsgType::Command.build(Command::Save), name: "save", priority: 0) + end + + def mute_audio(state : Bool = true, index : Int32 | String = 0) + logger.debug { "requested to update mute to #{state}" } + data = MsgType::SetParameter.build(Command::MuteStatus, state ? 1 : 0) + send(data, name: "mute_audio") + end + + def do_poll + current_power = power?(priority: 0) + logger.debug { "Polling, power = #{current_power}" } + + if current_power + mute_status + volume_status + video_input + audio_input + end + end + + def received(data, task) + header = data[0..6] + message = data[7..-3] + checksum = data[-2] + + unless checksum == data[1..-3].reduce { |a, b| a ^ b } + return task.try &.retry("invalid checksum in device response") + end + + begin + case MsgType.from_value header[4] + when .command_reply? + parse_command_reply message + when .get_parameter_reply?, .set_parameter_reply? + parse_response message + else + raise "unknown message type" + end + rescue e + task.try &.abort e.message + else + task.try &.success + end + end + + # Command replies each use a different packet structure + private def parse_command_reply(message : Bytes) + # Don't do any processing if this is the response for the save command + return if (string = String.new(message[1..-2])) == "00C" + response = string.hexbytes + + if response[1..3] == Bytes[0xC2, 0x03, 0xD6] # Set power + result_code = response[0] + raise "unsupported operation" unless result_code == 0 + self[:power] = response[5] == 1 + elsif response[2..3] == Bytes[0xD6, 0x00] # Power query + result_code = response[1] + raise "unsupported operation" unless result_code == 0 + self[:power] = response[7] == 1 + else + logger.warn { "unhandled command reply: #{message}" } + end + end + + # Get and set parameter replies share common structure + private def parse_response(message : Bytes) + response = String.new(message[1..-2]).hexbytes + + result_code = response[0] + raise "unsupported operation" unless result_code == 0 + + op_code = response[1].to_u16 << 8 | response[2] + value = response[6].to_u16 << 8 | response[7] + + case Command.from_value op_code + when .video_input? + self[:input] = Input.from_value(value) + when .audio_input? + self[:audio] = Audio.from_value(value) + when .volume_status? + self[:volume] = value + self[:audio_mute] = value == 0 + when .brightness_status? + self[:brightness] = value + when .contrast_status? + self[:contrast] = value + when .mute_status? + self[:audio_mute] = value == 1 + self[:volume] = 0 if value == 1 + when .auto_setup? + # auto_setup + # nothing needed to do here (we are delaying the next command by 4 seconds) + else + logger.warn { "unhandled device response: #{message}" } + end + end + + enum Command + VideoInput = 0x0060 + AudioInput = 0x022E + VolumeStatus = 0x0062 + MuteStatus = 0x008D + PowerOnDelay = 0x02D8 + ContrastStatus = 0x0012 + BrightnessStatus = 0x0010 + AutoSetup = 0x001E + PowerQuery = 0x01D6 + Save = 0x0C + SetPower = 0xC203D6 + + def to_s : String + case self + when .save? + length = 2 + when .set_power? + length = 6 + else + length = 4 + end + value.to_s(16, true).rjust(length, '0') + end + end + + {% for name in Command.constants %} + @[Security(Level::Administrator)] + def {{name.id.underscore}}(priority : Int32 = 0) + send(MsgType::GetParameter.build(Command::{{name.id}}), priority: priority, name: {{name.id.underscore.stringify}}) + end + {% end %} + + # Types of messages sent to and from the LCD + enum MsgType : UInt8 + Command = 0x41 # 'A' + CommandReply = 0x42 # 'B' + GetParameter = 0x43 # 'C' + GetParameterReply = 0x44 # 'D' + SetParameter = 0x45 # 'E' + SetParameterReply = 0x46 # 'F' + + def build(command : Nec::Display::Command, data : Int? = nil) + command = command.to_s + + message = String.build do |str| + str << "0*0" + str.write_byte self.value # Type + + message_length = command.size + 2 + message_length += 4 if data # If there is data, add 4 to the message length + str << message_length.to_s(16, true).rjust(2, '0') # Message length + str.write_byte 0x02 # Start of messsage + str << command # Message + str << data.to_s(16, true).rjust(4, '0') if data # Data if required + str.write_byte 0x03 # End of message + end + + String.build do |str| + str.write_byte 0x01 # SOH + str << message # Message + str.write_byte message.each_byte.reduce { |a, b| a ^ b } # Checksum + str.write_byte DELIMITER # Delimiter + end + end + end +end diff --git a/drivers/nec/display_spec.cr b/drivers/nec/display_spec.cr new file mode 100644 index 00000000000..39a60a8c63f --- /dev/null +++ b/drivers/nec/display_spec.cr @@ -0,0 +1,70 @@ +DriverSpecs.mock_driver "Nec::Display" do + # do_poll + # power? + should_send("\x010*0A06\x0201D6\x03\x1F\x0D") + responds("\x0100*B12\x020200D60000040001\x03\x1F\x0D") + status[:power].should eq(true) + # mute_status + should_send("\x010*0C06\x02008D\x03\x12\x0D") + responds("\x0100*D12\x0200008D0000000002\x03\x12\x0D") + status[:audio_mute].should eq(false) + # volume_status + should_send("\x010*0C06\x020062\x03\x6A\x0D") + responds("\x0100*D12\x020000620000000032\x03\x69\x0D") + status[:volume].should eq(50) + # video_input + should_send("\x010*0C06\x020060\x03\x68\x0D") + responds("\x0100*D12\x020000600000000011\x03\x6A\x0D") + status[:input].should eq("Hdmi") + # audio_input + should_send("\x010*0C06\x02022E\x03\x1B\x0D") + responds("\x0100*D12\x0200022E0000000001\x03\x18\x0D") + status[:audio].should eq("Audio1") + + exec(:mute_audio) + should_send("\x010*0E0A\x02008D0001\x03\x62\x0D") + responds("\x0100*F12\x0200008D0000000001\x03\x13\x0D") + status[:audio_mute].should eq(true) + status[:volume].should eq(0) + + exec(:unmute_audio) + should_send("\x010*0E0A\x02008D0000\x03\x63\x0D") + responds("\x0100*F12\x0200008D0000000000\x03\x12\x0D") + status[:audio_mute].should eq(false) + + exec(:volume, 25) + should_send("\x010*0E0A\x0200620019\x03\x13\x0D") + responds("\x0100*F12\x020000620000640019\x03\x60\x0D") + should_send("\x010*0A04\x020C\x03\x1D\x0D") + responds("\x0100*B06\x0200C\x03\x2C\x0D") + status[:audio_mute].should eq(false) + status[:volume].should eq(25) + + exec(:brightness_status) + should_send("\x010*0C06\x020010\x03\x6F\x0D") + responds("\x0100*D12\x020000100000000000\x03\x6D\x0D") + status[:brightness].should eq(0) + + exec(:brightness, 100) + should_send("\x010*0E0A\x0200100064\x03\x1C\x0D") + responds("\x0100*F12\x020000100000640064\x03\x6F\x0D") + should_send("\x010*0A04\x020C\x03\x1D\x0D") + responds("\x0100*B06\x0200C\x03\x2C\x0D") + status[:brightness].should eq(100) + + exec(:switch_to, "tv") + should_send("\x010*0E0A\x020060000A\x03\x68\x0D") + responds("\x0100*F12\x02000060000000000A\x03\x19\x0D") + status[:input].should eq("Tv") + + exec(:switch_audio, "audio_2") + sleep 6 # since switch_to has 6 seconds of delay + should_send("\x010*0E0A\x02022E0002\x03\x68\x0D") + responds("\x0100*F12\x0200022E0000000002\x03\x19\x0D") + status[:audio].should eq("Audio2") + + exec(:power, false) + should_send("\x010*0A0C\x02C203D60004\x03\x1D\x0D") + responds("\x0100*B0E\x0200C203D60004\x03\x18\x0D") + status[:power].should eq(false) +end diff --git a/drivers/nec/np_series.cr b/drivers/nec/np_series.cr new file mode 100644 index 00000000000..a4cee3ed85c --- /dev/null +++ b/drivers/nec/np_series.cr @@ -0,0 +1,465 @@ +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/muteable" +require "placeos-driver/interface/switchable" + +class Nec::Projector < PlaceOS::Driver + include Interface::Powerable + include Interface::Muteable + + enum Input + VGA = 0x01 + RGBHV = 0x02 + Composite = 0x06 + SVideo = 0x0B + Component = 0x10 + Component2 = 0x11 + HDMI = 0x1A + HDMI2 = 0x1B + DisplayPort = 0xA6 + LAN = 0x20 + Viewer = 0x1F + end + + include Interface::InputSelection(Input) + + # Discovery Information + tcp_port 7142 + descriptive_name "NEC Projector" + generic_name :Display + + default_settings({ + volume_min: 0, + volume_max: 63, + }) + + @power_target : Bool? = nil + @input_target : Input? = nil + @volume_min : Int32 = 0 + @volume_max : Int32 = 63 + + def on_load + # Communication settings + queue.delay = 100.milliseconds + self[:error] = [] of String + on_update + end + + def on_update + @power_target = nil + @input_target = nil + @volume_min = setting(Int32, :volume_min) + @volume_max = setting(Int32, :volume_max) + end + + def connected + schedule.every(50.seconds, true) { do_poll } + end + + def disconnected + schedule.clear + # Disconnect often occurs on power off + # We may have not received a status response before the disconnect occurs + self[:power] = false + end + + # Command Listing + # Second byte used to detect command type + COMMAND = { + # Mute controls + mute_picture: Bytes[0x02, 0x10, 0x00, 0x00, 0x00, 0x12], + unmute_picture: Bytes[0x02, 0x11, 0x00, 0x00, 0x00, 0x13], + mute_audio_cmd: Bytes[0x02, 0x12, 0x00, 0x00, 0x00, 0x14], + unmute_audio_cmd: Bytes[0x02, 0x13, 0x00, 0x00, 0x00, 0x15], + mute_onscreen: Bytes[0x02, 0x14, 0x00, 0x00, 0x00, 0x16], + unmute_onscreen: Bytes[0x02, 0x15, 0x00, 0x00, 0x00, 0x17], + + freeze_picture: Bytes[0x01, 0x98, 0x00, 0x00, 0x01, 0x01], + unfreeze_picture: Bytes[0x01, 0x98, 0x00, 0x00, 0x01, 0x02], + + lamp?: Bytes[0x00, 0x81, 0x00, 0x00, 0x00, 0x81], # Running sense (ret 81) + input?: Bytes[0x00, 0x85, 0x00, 0x00, 0x01, 0x02], # Input status (ret 85) + mute?: Bytes[0x00, 0x85, 0x00, 0x00, 0x01, 0x03], # MUTE STATUS REQUEST (Check 10H on byte 5) + error?: Bytes[0x00, 0x88, 0x00, 0x00, 0x00, 0x88], # ERROR STATUS REQUEST (ret 88) + model?: Bytes[0x00, 0x85, 0x00, 0x00, 0x01, 0x04], # Request model name (both of these are related) + + # lamp hours / remaining info + lamp_info: Bytes[0x03, 0x8A, 0x00, 0x00, 0x00, 0x8D], # LAMP INFORMATION REQUEST + filter_info: Bytes[0x03, 0x8A, 0x00, 0x00, 0x00, 0x8D], + projector_info: Bytes[0x03, 0x8A, 0x00, 0x00, 0x00, 0x8D], + + # TODO: figure out where these are in the docs as they conflict with audio_switch + background_black: Bytes[0x03, 0xB1, 0x00, 0x00, 0x02, 0x0B, 0x01], # set mute to be a black screen + background_blue: Bytes[0x03, 0xB1, 0x00, 0x00, 0x02, 0x0B, 0x00], # set mute to be a blue screen + background_logo: Bytes[0x03, 0xB1, 0x00, 0x00, 0x02, 0x0B, 0x02], # set mute to be the company logo + } + + {% for name, data in COMMAND %} + def {{name.id}}(**options) + do_send(COMMAND[{{name.id.stringify}}], **options, name: {{name.id.stringify}}) + end + {% end %} + + def volume(vol : Int32) + vol = vol.clamp(@volume_min, @volume_max) + # volume base command D1 D2 D3 D4 D5 + command = Bytes[0x03, 0x10, 0x00, 0x00, 0x05, 0x05, 0x00, 0x00, vol, 0x00] + # D3 = 00 (absolute vol) or 01 (relative vol) + # D4 = value (lower bits 0 to 63) + # D5 = value (higher bits always 00h) + + do_send(command) + end + + # Mutes both audio/video + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + if layer.video? || layer.audio_video? + if state + mute_picture + mute_onscreen + else + unmute_picture + end + end + + if layer.audio? || layer.audio_video? + state ? mute_audio_cmd : unmute_audio_cmd + end + end + + def switch_to(input : Input) + logger.debug { "-- NEC projector, requested to switch to: #{input}" } + @input_target = input + command = Bytes[0x02, 0x03, 0x00, 0x00, 0x02, 0x01, input.value] + do_send(command, name: "input") + end + + enum Audio + HDMI + VGA # Computer in docs + end + + def switch_audio(input : Audio) + # C0 == HDMI Audio + command = Bytes[0x03, 0xB1, 0x00, 0x00, 0x02, 0xC0, input.value] + do_send(command, name: "switch_audio") + end + + def power(state : Bool) + @power_target = state + + if state + command = Bytes[0x02, 0x00, 0x00, 0x00, 0x00] + do_send(command, name: "power", timeout: 15.seconds, delay: 1.second) + else + command = Bytes[0x02, 0x01, 0x00, 0x00, 0x00] + # Jump ahead of any other queued commands as they are no longer important + do_send( + command, + name: "power", + timeout: 60.seconds, # don't want retries occuring very fast + delay: 30.seconds, + clear_queue: true, + priority: 100, + ) + end + end + + def power?(**options) : Bool + do_send(COMMAND[:lamp?], **options, name: "power?").get + !!self[:power]?.try(&.as_bool) + end + + def switch_to(input : Input) + @input_target = input + command = Bytes[0x02, 0x03, 0x00, 0x00, 0x02, 0x01, input.value] + do_send(command, name: "input") + end + + def do_poll + if power?(priority: 0) + mute?(priority: 0) + background_black(priority: 0) + lamp_info(priority: 0) + end + end + + private def checksum_valid?(data : Bytes) + checksum = data[0..-2].sum(0) & 0xFF + logger.debug { "Error: checksum should be 0x#{checksum.to_s(16, true)}" } unless result = checksum == data[-1] + result + end + + private def do_send(command : Bytes, **options) + req = Bytes.new(command.size + 1) + req.copy_from(command) + req[-1] = (command.sum(0) & 0xFF).to_u8 + logger.debug { "Nec proj sending 0x#{req.hexstring}" } + send(req, **options) { |data, task| process_response(data, task, req) } + end + + # TODO: add responses for freeze commands if we need to process them + enum Response : UInt16 + Power = 8321 # [0x20,0x81] + InputOrMuteQuery = 8325 # [0x20,0x85] + Error = 8328 # [0x20,0x88] + InputSwitch = 8707 # [0x22,0x03] + Lamp = 8704 # [0x22,0x00] + Lamp2 = 8705 # [0x22,0x01] + PictureMuteOn = 8720 # [0x22,0x10] + PictureMuteOff = 8721 # [0x22,0x11] + AudioMuteOn = 8722 # [0x22,0x12] + AudioMuteOff = 8723 # [0x22,0x13] + OnscreenMuteOn = 8724 # [0x22,0x14] + OnscreenMuteOff = 8725 # [0x22,0x15] + VolumeOrImageAdjust = 8976 # [0x23,0x10] + Info = 9098 # [0x23,0x8A] + AudioSwitch = 9137 # [0x23,0xB1] + + def self.from_bytes?(response) + value = IO::Memory.new(response[0..1]).read_bytes(UInt16, IO::ByteFormat::BigEndian) + Response.from_value?(value) + end + end + + private def process_response(data, task, req = nil) + logger.debug { "NEC projector sent: 0x#{data.hexstring}" } + + # Command failed + if (data[0] & 0xA0) == 0xA0 + # We were changing power state at time of failure we should keep trying + if req && (0..1).includes?(req[1]) + # command[:delay_on_receive] = 6000 + power? + return task.try(&.success) + end + return task.try(&.abort("-- NEC projector, sent fail code for command: 0x#{req.try(&.hexstring) || "unknown"}")) + end + + # Verify checksum + unless checksum_valid?(data) + return task.try(&.abort("-- NEC projector, checksum failed for command: 0x#{req.try(&.hexstring) || "unknown"}")) + end + + # Only process response if successful + # Otherwise return success to prevent retries on commands we were not expecting + unless resp = Response.from_bytes?(data) + return task.try(&.success("-- NEC projector, no status updates defined for response for command: 0x#{req.try(&.hexstring) || "unknown"}")) + end + + case resp + when .power? + process_power_status(data) + when .input_or_mute_query? + # Return if we can't work out what was requested initially + return task.try(&.success) unless req && (2..3).includes?(req[-2]) + process_input_state(data) if req[-2] == 2 + process_mute_state(data) if req[-2] == 3 + when .error? + process_error_status(data) + when .input_switch? + return process_input_switch(data, task, req) + when .lamp?, .lamp2? + process_lamp_command(data, req) + when .picture_mute_on?, .picture_mute_off? + self[:mute] = self[:picture_mute] = resp.picture_mute_on? + when .audio_mute_on?, .audio_mute_off? + self[:audio_mute] = resp.audio_mute_on? + when .onscreen_mute_on?, .onscreen_mute_off? + self[:onscreen_mute] = resp.onscreen_mute_on? + when .volume_or_image_adjust? + self[:volume] = req[-3] if req && data[-3] == 5 && data[-2] == 0 + # We don't care about image adjust + when .info? + process_projector_info(data) + when .audio_switch? # TODO: also seems to the seem as setting background response + self[:audio_input] = Audio.from_value(data[-2]) if data[-3] == 0xC0 + end + + task.try(&.success) + end + + def received(data, task) + process_response(data, task) + end + + # Process the lamp status response + # Intimately entwined with the power power command + # (as we need to control ensure we are in the correct target state) + private def process_power_status(data) + logger.debug { "-- NEC projector sent a response to a power status command" } + + self[:power] = (data[-2] & 0b10) > 0 + + # Projector cooling || power on off processing + if (data[-2] & 0b100000) > 0 || (data[-2] & 0b10000000) > 0 + if @power_target + self[:cooling] = false + self[:warming] = true + logger.debug { "power warming..." } + else + self[:warming] = false + self[:cooling] = true + logger.debug { "power cooling..." } + end + + schedule.in(3.seconds) { power? } + # Signal processing + elsif (data[-2] & 0b1000000) > 0 + schedule.in(3.seconds) { power? } + else # We are in a stable state! + if power_target = @power_target + if self[:power] == power_target + @power_target = nil + else # We are in an undesirable state and will try to correct it + logger.debug { "NEC projector in an undesirable power state... (Correcting)" } + power(power_target) + end + else + logger.debug { "NEC projector is in a good power state..." } + self[:warming] = self[:cooling] = false + # Ensure the input is in the correct state if power/lamp is on + input? if self[:power].as_bool # Calls status mute + end + end + + logger.debug { "Current state {power: #{self[:power]}, warming: #{self[:warming]}, cooling: #{self[:cooling]}}" } + end + + # NEC has different values for the input status when compared to input selection + INPUT_MAP = { + 0x01 => { + 0x01 => Input::VGA, + 0x02 => Input::Composite, + 0x03 => Input::SVideo, + 0x06 => Input::HDMI, + 0x07 => Input::Viewer, + 0x21 => Input::HDMI, + 0x22 => Input::DisplayPort, + }, + 0x02 => { + 0x01 => Input::RGBHV, + 0x04 => Input::Component2, + 0x06 => Input::HDMI2, + 0x07 => Input::LAN, + 0x21 => Input::HDMI2, + }, + 0x03 => { + 0x04 => Input::Component, + }, + } + + private def process_input_state(data) + return unless self[:power]?.try(&.as_bool) && (first = INPUT_MAP[data[-15]]) + + logger.debug { "-- NEC projector sent a response to an input state command" } + + self[:input] = current_input = first[data[-14]] || "unknown" + if data[-17] == 0x01 + # TODO: figure out how to write in crystal and if needed + # command[:delay_on_receive] = 3000 # still processing signal + input? + else # TODO: figure out if this is needed from old ruby driver + # mute? # get mute status one signal has settled + end + + logger.debug { "The input selected was: #{current_input}" } + + # Notify of bad input selection for debugging + # We ensure at the very least power state and input are always correct + if (input_target = @input_target) + # If we have reached the input_target, clear @input_target so input can be set again + if current_input == input_target + @input_target = nil + else + logger.debug { "-- NEC input state may not be correct, desired: #{input_target} current: #{current_input}" } + switch_to(input_target) + end + end + end + + private def process_mute_state(data) + logger.debug { "-- NEC projector responded to mute state command" } + self[:mute] = self[:picture_mute] = data[-17] == 0x01 + self[:audio_mute] = data[-16] == 0x01 + self[:onscreen_mute] = data[-15] == 0x01 + end + + private def process_input_switch(data, task, req) + logger.debug { "-- NEC projector responded to switch input command" } + if data[-2] != 0xFF + input? # Double check with a status update + return task.try(&.success) + end + task.try(&.retry("-- NEC projector failed to switch input with command: #{req.try(&.hexstring) || "unknown"}")) + end + + private def process_lamp_command(data, req) + logger.debug { "-- NEC projector sent a response to a power command" } + # Ensure a change of power state was the last command sent + if req && (0..1).includes?(req[1]) + power? # Queues the status power command + end + end + + # Provide all the error info required + ERROR_CODES = [{ + 0b1 => "Lamp cover error", + 0b10 => "Temperature error (Bimetal)", + # 0b100 => not used + 0b1000 => "Fan Error", + 0b10000 => "Fan Error", + 0b100000 => "Power Error", + 0b1000000 => "Lamp Error", + 0b10000000 => "Lamp has reached its end of life", + }, { + 0b1 => "Lamp has been used beyond its limit", + 0b10 => "Formatter error", + 0b100 => "Lamp no.2 Error", + }, { + # 0b1 => "not used" + 0b10 => "FPGA error", + 0b100 => "Temperature error (Sensor)", + 0b1000 => "Lamp housing error", + 0b10000 => "Lamp data error", + 0b100000 => "Mirror cover error", + 0b1000000 => "Lamp no.2 has reached its end of life", + 0b10000000 => "Lamp no.2 has been used beyond its limit", + }, { + 0b1 => "Lamp no.2 housing error", + 0b10 => "Lamp no.2 data error", + 0b100 => "High temperature due to dust pile-up", + 0b1000 => "A foreign object sensor error", + }] + + private def process_error_status(data) + logger.debug { "-- NEC projector sent a response to an error status command" } + errors = [] of String + # Run through each byte + data[5..8].each_with_index do |byte, byte_no| + # If there is an error + if byte > 0 + # Go through each individual bit + ERROR_CODES[byte_no].each_key do |bit_check| + # Add the error if the bit corresponding to it is set + errors.push(ERROR_CODES[byte_no][bit_check]) if (bit_check & byte) > 0 + end + end + end + self[:error] = errors + end + + private def process_projector_info(data) + logger.debug { "-- NEC projector sent a response to a projector info command" } + # Calculate lamp/filter usage in seconds + lamp = data[87..90].each_with_index.sum { |byte, index| byte.to_i << (index * 8) } + filter = data[91..94].each_with_index.sum { |byte, index| byte.to_i << (index * 8) } + # Convert seconds to hours + self[:lamp_usage] = lamp / 3600 + self[:filter_usage] = filter / 3600 + logger.debug { "lamp usage is #{self[:lamp_usage]} hours, filter usage is #{self[:filter_usage]} hours" } + end +end diff --git a/drivers/nec/np_series_spec.cr b/drivers/nec/np_series_spec.cr new file mode 100644 index 00000000000..b06ff72d165 --- /dev/null +++ b/drivers/nec/np_series_spec.cr @@ -0,0 +1,83 @@ +# NOTES +# (*1) Projector ID +# (*2) Model code: "xxH" inscription +# (*3) Checksum: "CKS" inscription +# (*4) Response error number +# (*5) Term “RGB” and “COMPUTER” +# (*6) Term “DVI” and “COMPUTER” + +DriverSpecs.mock_driver "Nec::Projector" do + p_id = 0x00_u8 # Projector ID + mdlc = 0x10_u8 # Model code + + # do_poll + # power? + should_send(Bytes[0x00, 0x81, 0x00, 0x00, 0x00, 0x81, 0x02]) + responds(Bytes[0x20, 0x81, p_id, mdlc, 0x10, 0b_0000_0010, 0xC3]) + status[:power].should eq(true) + # input? + should_send(Bytes[0x00, 0x85, 0x00, 0x00, 0x01, 0x02, 0x88]) + responds(Bytes[0x20, 0x85, p_id, mdlc, 0x10, + # Data, simplified for sanity + # We only care about the ones with 0x + # -17 -15 -14 + 0x00, 2, 0x01, 0x06, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 0x4C]) # Checksum + status[:input].should eq("HDMI") + # mute? + should_send(Bytes[0x00, 0x85, 0x00, 0x00, 0x01, 0x03, 0x89]) + responds(Bytes[0x20, 0x85, p_id, mdlc, 0x10, + # -17 -16 -15 + 0x00, 0x00, 0x00, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 0x47]) # Checksum + status[:mute].should eq(false) + status[:picture_mute].should eq(false) + status[:audio_mute].should eq(false) + status[:onscreen_mute].should eq(false) + # background_black + should_send(Bytes[0x03, 0xB1, 0x00, 0x00, 0x02, 0x0B, 0x01, 0xC2]) + responds(Bytes[0x23, 0xB1, p_id, mdlc, 0x02, 0x0B, 0xF1]) + # lamp_info + should_send(Bytes[0x03, 0x8A, 0x00, 0x00, 0x00, 0x8D, 0x1A]) + # 5 for header, 1 for checksum and 98 for data + response = Bytes.new(104) + response.copy_from(Bytes[0x23, 0x8A, p_id, mdlc, 0x62, 0x0B]) # header + # data + # lamp usage + response[87] = 0xC0 + response[88] = 0x65 + response[89] = 0x52 + # filter usage + response[92] = 0xE4 + response[93] = 0x57 + # checksum + response[-1] = 0xDC + responds(response) + status[:lamp_usage].should eq(1500) + status[:filter_usage].should eq(1600) + + exec(:volume, 100) + should_send(Bytes[0x03, 0x10, 0x00, 0x00, 0x05, 0x05, 0x00, 0x00, 0x3F, 0x00, 0x5C]) + responds(Bytes[0x23, 0x10, p_id, mdlc, 0x05, 0x00, 0x48]) + status[:volume].should eq(63) + + exec(:mute) + # mute_picture + should_send(Bytes[0x02, 0x10, 0x00, 0x00, 0x00, 0x12, 0x24]) + responds(Bytes[0x22, 0x10, p_id, mdlc, 0x32, 0x00, 0x74]) + status[:mute] = true + status[:picture_mute] = true + # mute_onscreen + should_send(Bytes[0x02, 0x14, 0x00, 0x00, 0x00, 0x16, 0x2C]) + responds(Bytes[0x22, 0x14, p_id, mdlc, 0x00, 0x46]) + status[:onscreen_mute] = true + # mute_audio + should_send(Bytes[0x02, 0x12, 0x00, 0x00, 0x00, 0x14, 0x28]) + responds(Bytes[0x22, 0x12, p_id, mdlc, 0x00, 0x44]) + status[:audio_mute] = true + + exec(:switch_audio, "VGA") + should_send(Bytes[0x03, 0xB1, 0x00, 0x00, 0x02, 0xC0, 0x01, 0x77]) + responds(Bytes[0x23, 0xB1, p_id, mdlc, 0xC0, 0x01, 0xA5]) + status[:audio_input].should eq("VGA") +end diff --git a/drivers/office_rnd/models.cr b/drivers/office_rnd/models.cr new file mode 100644 index 00000000000..f6943dffc4d --- /dev/null +++ b/drivers/office_rnd/models.cr @@ -0,0 +1,202 @@ +require "json" + +# OfficeRnD Data Models +module OfficeRnd + abstract struct Data + include JSON::Serializable + end + + struct TokenResponse < Data + include JSON::Serializable + property access_token : String + property token_type : String + property expires_in : Int32 + property scope : String + end + + struct Office < Data + @[JSON::Field(key: "_id")] + getter id : String + getter name : String + getter country : String? + getter state : String? + getter city : String? + getter address : String? + getter timezone : String? + getter image : String? + @[JSON::Field(key: "isOpen")] + getter is_open : Bool? + end + + struct BookingTime < Data + @[JSON::Field(key: "dateTime")] + getter time : Time + + def initialize(@time : Time); end + end + + struct Fee < Data + getter name : String + getter price : Int32 + getter quantity : Int32 = 1 + getter date : Time + @[JSON::Field(key: "team")] + getter team_id : String? + @[JSON::Field(key: "office")] + getter office_id : String + @[JSON::Field(key: "member")] + getter member_id : String? + @[JSON::Field(key: "plan")] + getter plan_id : String? + getter refundable : Bool? + @[JSON::Field(key: "billInAdvance")] + getter bill_in_advance : Bool? + @[JSON::Field(key: "isPersonal")] + getter is_personal : Bool? + end + + struct BookingFee < Data + getter date : Time + getter fee : Fee? + @[JSON::Field(key: "extraFees")] + getter extra_fees : Array(JSON::Any?) + getter credits : Array(Credit) + end + + struct Booking < Data + @[JSON::Field(key: "start")] + getter booking_start : BookingTime + @[JSON::Field(key: "end")] + getter booking_end : BookingTime + getter timezone : String = "Australia/Sydney" + getter source : String? + getter summary : String? + @[JSON::Field(key: "resourceId")] + getter resource_id : String + @[JSON::Field(key: "plan")] + getter plan_id : String = "" + @[JSON::Field(key: "team")] + getter team_id : String? + @[JSON::Field(key: "member")] + getter member_id : String? + getter description : String? + getter tentative : Bool? + getter free : Bool? + getter fees : Array(::OfficeRnd::BookingFee) = [] of ::OfficeRnd::BookingFee + getter extras : JSON::Any = JSON::Any.new("") + + def initialize( + @resource_id : String, + booking_start : Time, + booking_end : Time, + @summary : String? = nil, + @team_id : String? = nil, + @member_id : String? = nil, + @description : String? = nil, + @tentative : Bool? = nil, + @free : Bool? = nil + ) + unless @member_id || @team_id + raise "Booking requires at least one of team_id or member_id" + end + @booking_start = BookingTime.new(booking_start) + @booking_end = BookingTime.new(booking_end) + end + + def overlaps?(time_span : Range(Time, Time)) + starting, ending = booking_start.time, booking_end.time + within = time_span.includes?(starting) || time_span.includes?(ending) + covers = starting < time_span.begin && ending > time_span.end + + within || covers + end + end + + struct Credit < Data + getter count : Int32 + getter credit : String + end + + struct Rate < Data + @[JSON::Field(key: "_id")] + getter id : String + getter name : String + getter price : Int32 + @[JSON::Field(key: "cancellationPolicy")] + getter cancellation_policy : CancellationPolicy + getter extras : Array(Extra) + @[JSON::Field(key: "maxDuration")] + getter max_duration : Int32 + + struct CancellationPolicy < Data + @[JSON::Field(key: "minimumPeriod")] + property minimum_period : Int32 + end + + struct Extra < Data + @[JSON::Field(key: "_id")] + getter id : String + getter name : String + getter price : Int32 + end + end + + struct Resource < Data + getter name : String + @[JSON::Field(key: "rate")] + getter rate_id : String? + @[JSON::Field(key: "office")] + getter office_id : String + @[JSON::Field(key: "room")] + getter floor_id : String + getter type : Type + + MAPPING = { + Type::MeetingRoom => "meeting_room", + Type::PrivateOffices => "team_room", + Type::PrivateOfficeDesk => "desk_tr", + Type::DedicatedDesks => "desk", + Type::HotDesks => "hotdesk", + } + + enum Type + MeetingRoom + PrivateOffices + PrivateOfficeDesk + DedicatedDesks + HotDesks + + def to_s + Resource::MAPPING[self] + end + + def to_json(json : JSON::Builder) + json.string(self.to_s) + end + + def self.parse(type : String) + parsed = Resource::MAPPING.key_for?(type) + raise ArgumentError.new("Unrecognised Resource::Type '#{type}'") unless parsed + parsed + end + + def self.valid?(type : String) + !!(Resource::MAPPING.key_for?(type)) + end + end + end + + struct Floor < Data + @[JSON::Field(key: "_id")] + getter id : String + getter floor : String? + getter name : String + @[JSON::Field(key: "office")] + getter office_id : String + getter area : Int32? + @[JSON::Field(key: "isOpen")] + getter is_open : Bool? + @[JSON::Field(key: "targetRevenue")] + getter target_revenue : Int32? + end +end diff --git a/drivers/office_rnd/office_rnd_api.cr b/drivers/office_rnd/office_rnd_api.cr new file mode 100644 index 00000000000..e5ab9c2d5e1 --- /dev/null +++ b/drivers/office_rnd/office_rnd_api.cr @@ -0,0 +1,268 @@ +require "uri" +require "uuid" + +require "./models" + +module OfficeRnd + class OfficeRndAPI < PlaceOS::Driver + # Discovery Information + generic_name :OfficeRnd + descriptive_name "OfficeRnD REST API" + + default_settings({ + client_id: "10000000", + client_secret: "c5a6adc6-UUID-46e8-b72d-91395bce9565", + scopes: ["officernd.api.read", "officernd.api.write"], + test_auth: true, + }) + + @client_id : String = "" + @client_secret : String = "" + @scopes : Array(String) = [] of String + + @test_auth : Bool = false + @auth_token : String = "" + @auth_expiry : Time = 1.minute.ago + + def on_load + on_update + @test_auth = setting(Bool, :test_auth) + end + + def on_update + @client_id = setting(String, :client_id) + @client_secret = setting(String, :client_secret) + @scopes = setting(Array(String), :scopes) + end + + def expire_token! + @auth_expiry = 1.minute.ago + end + + def token_expired? + @auth_expiry < Time.utc + end + + def get_token + return @auth_token unless token_expired? + auth_route = @test_auth ? "http://localhost:17839/oauth/token" : "https://identity.officernd.com/oauth/token" + params = HTTP::Params.encode({ + "client_id" => @client_id, + "client_secret" => @client_secret, + "grant_type" => "client_credentials", + "scope" => @scopes.join(' '), + }) + headers = HTTP::Headers{ + "Content-Type" => "application/x-www-form-urlencoded", + "Accept" => "application/json", + } + response = HTTP::Client.post( + url: auth_route, + headers: headers, + body: params, + ) + body = response.body + logger.debug { "received login response: #{body}" } + + if response.success? + resp = TokenResponse.from_json(body) + @auth_expiry = Time.utc + (resp.expires_in - 5).seconds + @auth_token = "Bearer #{resp.access_token}" + else + logger.error { "authentication failed with HTTP #{response.status_code}" } + raise "failed to obtain access token" + end + end + + # Floor + ########################################################################### + + # Get a floor + # + def floor(floor_id : String) + get_request("/floors/#{floor_id}", Floor) + end + + # Get floors + # + def floors(office_id : String?, name : String?) + params = HTTP::Params.new + params["office"] = office_id if office_id + params["name"] = name if name + query_string = params.to_s + path = query_string.empty? ? "/floors" : "/floors?#{query_string}" + get_request(path, Array(Floor)) + end + + # Booking + ########################################################################### + + # Get bookings for a resource for a given time span + # + def resource_bookings( + resource_id : String, + range_start : Time = Time.utc - 5.minutes, + range_end : Time = Time.utc + 24.hours, + office_id : String? = nil, + member_id : String? = nil, + team_id : String? = nil + ) : Array(Booking) + time_span = (range_start..range_end) + bookings( + office_id: office_id, + member_id: member_id, + team_id: team_id, + ).select! do |booking| + booking.resource_id == resource_id && booking.overlaps?(time_span) + end + end + + # Get a booking + # + def booking(booking_id : String) + get_request("/bookings/#{booking_id}", Booking) + end + + # Get bookings + # + def bookings( + office_id : String? = nil, + member_id : String? = nil, + team_id : String? = nil + ) + params = HTTP::Params.new + params["office"] = office_id if office_id + params["member"] = member_id if member_id + params["team"] = team_id if team_id + query_string = params.to_s + path = query_string.empty? ? "/bookings" : "/bookings?#{query_string}" + get_request(path, Array(Booking)) + end + + # Delete a booking + # + def delete_booking(booking_id : String) + !!(delete_request("/bookings/#{booking_id}")) + end + + # Make a booking + # + def create_bookings(bookings : Array(Booking)) + response = post("/bookings", body: bookings.to_json, headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => get_token, + }) + unless response.success? + expire_token! if response.status_code == 401 + raise "unexpected response #{response.status_code}\n#{response.body}" + end + end + + # Create a booking + # + def create_booking( + resource_id : String, + booking_start : Time, + booking_end : Time, + summary : String? = nil, + team_id : String? = nil, + member_id : String? = nil, + description : String? = nil, + tentative : Bool? = nil, + free : Bool? = nil + ) + create_bookings [Booking.new( + resource_id: resource_id, + booking_start: booking_start, + booking_end: booking_end, + summary: summary, + team_id: team_id, + member_id: member_id, + description: description, + tentative: tentative, + free: free, + )] + end + + alias BookingArgument = NamedTuple( + resource_id: String, + booking_start: Time, + booking_end: Time, + summary: String?, + team_id: String?, + member_id: String?, + description: String?, + tentative: Bool?, + free: Bool?, + ) + + def create_bookings(bookings : Array(BookingArgument)) + create_bookings(bookings.map { |booking| Booking.new(**booking) }) + end + + # Office + ########################################################################### + + # List offices + # + def offices + path = "/offices" + get_request(path, Array(Office)) + end + + # Retrieve office + # + def office(name : String) + path = "/offices/#{name}" + get_request(path, Office) + end + + # Resource + ########################################################################### + + # Get available rooms (resources) by + # - type + # - date range (available_from, available_to) + # - office (office_id) + # - resource name (name) + def resources( + type : (Resource::Type | String)? = nil, + name : String? = nil, + office_id : String? = nil, + available_from : Time? = nil, + available_to : Time? = nil + ) + type = Resource::Type.parse(type) if type.is_a?(String) + params = HTTP::Params.new + params["type"] = type.to_s if type + params["name"] = name if name + params["office"] = office_id if office_id + params["availableFrom"] = available_from.to_s if available_from + params["availableTo"] = available_to.to_s if available_to + query_string = params.to_s + path = query_string.empty? ? "/resources" : "/resources?#{query_string}" + get_request(path, Array(Resource)) + end + + # Internal Helpers + ############################################################################# + + private macro get_request(path, result_type) + begin + %token = get_token + %response = get({{path}}, headers: { + "Accept" => "application/json", + "Authorization" => %token + }) + + if %response.success? + {{result_type}}.from_json(%response.body) + else + expire_token! if %response.status_code == 401 + raise "unexpected response #{%response.status_code}\n#{%response.body}" + end + end + end + end +end diff --git a/drivers/office_rnd/office_rnd_api_spec.cr b/drivers/office_rnd/office_rnd_api_spec.cr new file mode 100644 index 00000000000..7041b457d02 --- /dev/null +++ b/drivers/office_rnd/office_rnd_api_spec.cr @@ -0,0 +1,39 @@ +DriverSpecs.mock_driver "OfficeRnd::OfficeRndApi" do + # Send the request + retval = exec(:get_token) + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJSRUFEIiwiV1JJVEUiXSwiZXhwIjoxNTc0MjMzNjEyLCJhdXRob3JpdGllcyI6WyJST0xFX1RSVVNURURfQ0xJRU5UIl0sImp0aSI6IjM1ZjkxYjlkLTVmZmMtNDJkYy05YWZkLTJiZTE0YjI1MmE1NCIsImNsaWVudF9pZCI6IjEwMDAwMjEzIn0.Wzrsaey5z3ShAFYKOaWmgfoRZNsk-PclSK9IRtYf4b8" + + expect_http_request do |request, response| + case request.path + when "/oauth/token" + data = request.body + .try(&.gets_to_end) + .try(&->HTTP::Params.parse(String)) + # The request is param encoded + if data && data["grant_type"] == "client_credentials" && data["client_secret"] == "c5a6adc6-UUID-46e8-b72d-91395bce9565" + response.status_code = 200 + response.output.puts %({ + "access_token": "#{token}", + "token_type": "Bearer", + "expires_in": 3599, + "scope": "officernd.api.read officernd.api.write" + }) + else + response.status_code = 401 + response.output.puts "" + end + when .starts_with?("/bookings") + case request.method + when "DELETE" + # TODO: Delete booking mock response + when "GET" + # TODO: Get bookings mock response + when "POST" + # TODO: Create bookings mock response + end + end + end + + # What the function should return (for use in making further requests) + retval.get.should eq("Bearer #{token}") +end diff --git a/drivers/panasonic/display/protocol2.cr b/drivers/panasonic/display/protocol2.cr new file mode 100644 index 00000000000..099254f288f --- /dev/null +++ b/drivers/panasonic/display/protocol2.cr @@ -0,0 +1,269 @@ +require "digest/md5" +require "placeos-driver/interface/muteable" +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/switchable" + +# Based off the very similar https://github.com/PlaceOS/drivers/blob/master/drivers/panasonic/display/nt_control.cr + +# How the display expects you interact with it: +# =============================================== +# 1. New connection required for each command sent (hence makebreak!) +# 2. On connect, the display sends you a string of characters to use as a password salt +# 3. Encode your message using the salt and send it to the display +# 4. Display responds with a value +# 5. You have to disconnect explicitly, display won't close the connection + +class Panasonic::Display::Protocol2 < PlaceOS::Driver + include Interface::Powerable + include Interface::Muteable + + enum Inputs + HDMI + HDMI2 + VGA + DVI + end + + include Interface::InputSelection(Inputs) + + # Discovery Information + tcp_port 1024 + descriptive_name "Panasonic Display Protocol 2" + generic_name :Display + + default_settings({ + username: "admin1", + password: "panasonic", + }) + + makebreak! + + def on_load + # Communication settings + transport.tokenizer = Tokenizer.new("\r") + + schedule.every(60.seconds) { do_poll } + + on_update + end + + def disconnected + @channel.close unless @channel.closed? + end + + @username : String = "admin1" + @password : String = "panasonic" + + # used to coordinate the display password hash + @channel : Channel(String) = Channel(String).new + @power_target : Bool? = nil + + def on_update + @username = setting?(String, :username) || "dispadmin" + @password = setting?(String, :password) || "@Panasonic" + end + + COMMANDS = { + power_on: "PON", + power_off: "POF", + power_query: "QPW", + input: "IMS", + volume: "AVL", + volume_query: "QAV", + audio_mute: "AMT", + } + RESPONSES = COMMANDS.to_h.invert + + def power(state : Bool) + @power_target = state + + if state + logger.debug { "requested to power on" } + do_send(:power_on, retries: 10, name: :power, delay: 8.seconds) + else + logger.debug { "requested to power off" } + do_send(:power_off, retries: 10, name: :power, delay: 8.seconds) + end + power? + end + + def power?(**options) : Bool + do_send(:power_query, **options).get + !!self[:power]?.try(&.as_bool) + end + + INPUTS = { + Inputs::HDMI => "HM1", + Inputs::HDMI2 => "HM2", + Inputs::VGA => "PC1", + Inputs::DVI => "DVI", + } + INPUT_LOOKUP = INPUTS.invert + + def switch_to(input : Inputs) + logger.debug { "requested to switch to: #{input}" } + do_send(:input, INPUTS[input], delay: 2.seconds) + self[:input] = input # for a responsive UI + end + + # There is no input query command + def input? + self[:input]? + end + + # There is no video mute command so this only mutes audio + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + if layer == MuteLayer::Video + logger.warn { "requested to mute video which is unsupported" } + else + logger.debug { "requested audio mute state: #{state}" } + do_send(:audio_mute, state ? 1 : 0) + end + end + + def mute? : Bool + do_send(:audio_mute).get + !!self[:audio_mute]?.try(&.as_bool) + end + + def volume(val : Int32) + # Unable to query current volume + do_send(:volume, val.to_s.rjust(3, '0')).get + self[:volume] = val + end + + def volume? : Int32? + do_send(:volume_query).get + self[:volume]?.try(&.as_i) + end + + def do_poll + if power?(priority: 0) + mute? + volume? + end + end + + ERRORS = { + "ERR1" => "1: Undefined control command", + "ERR2" => "2: Out of parameter range", + "ERR3" => "3: Busy state or no-acceptable period", + "ERR4" => "4: Timeout or no-acceptable period", + "ERR5" => "5: Wrong data length", + "ERRA" => "A: Password mismatch", + "ER401" => "401: Command cannot be executed", + "ER402" => "402: Invalid parameter is sent", + } + + def received(data, task) + data = String.new(data).strip + logger.debug { "Panasonic display sent: #{data} for #{task.try(&.name) || "unknown"}" } + + # This is sent by the display on initial connection + # the channel is used to send the hash salt to the task sending a command + if data.starts_with?("NTCONTROL") + # check for protected mode + if @channel && !@channel.closed? + # 1 == protected mode + @channel.send(data[10] == '1' ? data[12..-1] : "") + else + transport.disconnect + end + return + end + + # we no longer need the connection to be open , the display expects + # us to close it and a new connection is required per-command + transport.disconnect + + # remove the leading 00 + data = data[2..-1] + + # Check for error response + if data[0] == 'E' + self[:last_error] = error_msg = ERRORS[data] + + if {"ERR3", "ERR4"}.includes?(data) + logger.info { "display busy: #{error_msg} (#{data})" } + task.try(&.retry) + else + logger.error { "display error: #{error_msg} (#{data})" } + task.try(&.abort(error_msg)) + end + return + end + + # We can't interpret this message without a task reference + # This also makes sure it is no longer nil + return unless task + + # Process the response + resp = data.split(':') + cmd = RESPONSES[resp[0]]? + val = resp[1]? + + case cmd + when :power_on, :power_off, :power_query + self[:power] = cmd == :power_on if cmd == :power_on || cmd == :power_off + self[:power] = val.not_nil!.to_i == 1 if cmd == :power_query + + # Ensure selected power state is achieved + if power_target = @power_target + if self[:power] == power_target + @power_target = nil + else + power(power_target) + end + end + when :input + self[:input] = INPUT_LOOKUP[val] + when :volume, :volume_query + self[:volume] = val.not_nil!.to_i + when :audio_mute + self[:audio_mute] = val.not_nil!.to_i == 1 + end + + task.success + end + + protected def do_send(command, param = nil, **options) + # prepare the command + cmd = if param.nil? + "00#{COMMANDS[command]}\r" + else + "00#{COMMANDS[command]}:#{param}\r" + end + + logger.debug { "queuing #{command}: #{cmd}" } + + # queue the request + queue(**({ + name: command, + }.merge(options))) do + # prepare channel and connect to the display (which will then send the random key) + @channel = Channel(String).new + transport.connect + # wait for the random key to arrive + random_key = @channel.receive + # build the password hash + password_hash = if random_key.empty? + # An empty key indicates unauthenticated mode + "" + else + Digest::MD5.hexdigest("#{@username}:#{@password}:#{random_key}") + end + + message = "#{password_hash}#{cmd}" + logger.debug { "Sending: #{message}" } + + # send the request + # NOTE:: the built in `send` function has implicit queuing, but we are + # in a task callback here so should be calling transport send directly + transport.send(message) + end + end +end diff --git a/drivers/panasonic/display/protocol2_spec.cr b/drivers/panasonic/display/protocol2_spec.cr new file mode 100644 index 00000000000..4ee496bcc42 --- /dev/null +++ b/drivers/panasonic/display/protocol2_spec.cr @@ -0,0 +1,73 @@ +DriverSpecs.mock_driver "Panasonic::Display::Protocol2" do + password = "d4a58eaea919558fb54a33a2effa8b94" + + # Execute a command (triggers the connection) + exec(:power?) + # Once connected the projector will send a password salt + expect_reconnect + responds("NTCONTROL 1 09b075be\r") + # Check the request was sent with the correct password + should_send("#{password}00QPW\r") + # Respond with the status then check the state updated + responds("00QPW:0\r") + status[:power].should eq(false) + + exec(:power, true) + expect_reconnect + responds("NTCONTROL 1 09b075be\r") + should_send("#{password}00PON\r") + responds("00PON\r") + sleep 8.seconds + expect_reconnect + responds("NTCONTROL 1 09b075be\r") + should_send("#{password}00QPW\r") + responds("00QPW:1\r") + status[:power].should eq(true) + + exec(:switch_to, "hdmi") + expect_reconnect + responds("NTCONTROL 1 09b075be\r") + should_send("#{password}00IMS:HM1\r") + responds("00IMS:HM1\r") + status[:input].should eq("HDMI") + + exec(:mute?) + expect_reconnect + responds("NTCONTROL 1 09b075be\r") + should_send("#{password}00AMT\r") + responds("00AMT:0\r") + status[:audio_mute].should eq(false) + + exec(:mute) + expect_reconnect + responds("NTCONTROL 1 09b075be\r") + should_send("#{password}00AMT:1\r") + responds("00AMT:1\r") + status[:audio_mute].should eq(true) + + exec(:volume?) + expect_reconnect + responds("NTCONTROL 1 09b075be\r") + should_send("#{password}00QAV\r") + responds("00QAV:100\r") + status[:volume].should eq(100) + + exec(:volume, 20) + expect_reconnect + responds("NTCONTROL 1 09b075be\r") + should_send("#{password}00AVL:020\r") + responds("00AVL:020\r") + status[:volume].should eq(20) + + exec(:power, false) + expect_reconnect + responds("NTCONTROL 1 09b075be\r") + should_send("#{password}00POF\r") + responds("00POF\r") + sleep 8.seconds + expect_reconnect + responds("NTCONTROL 1 09b075be\r") + should_send("#{password}00QPW\r") + responds("00QPW:0\r") + status[:power].should eq(false) +end diff --git a/drivers/panasonic/projector/nt_control.cr b/drivers/panasonic/projector/nt_control.cr new file mode 100644 index 00000000000..cd0fdd707e1 --- /dev/null +++ b/drivers/panasonic/projector/nt_control.cr @@ -0,0 +1,283 @@ +require "digest/md5" +require "placeos-driver/interface/muteable" +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/switchable" + +module Panasonic; end + +module Panasonic::Projector; end + +# Documentation: https://aca.im/driver_docs/Panasonic/panasonic_pt-vw535n_manual.pdf +# also https://aca.im/driver_docs/Panasonic/pt-ez580_en.pdf + +# How the projector expects you interact with it: +# =============================================== +# 1. New connection required for each command sent (hence makebreak!) +# 2. On connect, the projector sends you a string of characters to use as a password salt +# 3. Encode your message using the salt and send it to the projector +# 4. Projector responds with a value +# 5. You have to disconnect explicitly, projector won't close the connection + +class Panasonic::Projector::NTControl < PlaceOS::Driver + include Interface::Powerable + include Interface::Muteable + + enum Inputs + HDMI + HDMI2 + VGA + VGA2 + Miracast + DVI + DisplayPort + HDBaseT + Composite + end + + include PlaceOS::Driver::Interface::InputSelection(Inputs) + + # Discovery Information + tcp_port 1024 + descriptive_name "Panasonic Projector" + generic_name :Display + default_settings({username: "admin1", password: "panasonic"}) + makebreak! + + def on_load + # Communication settings + transport.tokenizer = Tokenizer.new("\r") + + schedule.every(40.seconds) do + power?(priority: 0) + lamp_hours?(priority: 0) + end + + on_update + end + + def disconnected + @channel.close unless @channel.closed? + end + + @username : String = "admin1" + @password : String = "panasonic" + + # used to coordinate the projector password hash + @channel : Channel(String) = Channel(String).new + @stable_power : Bool = true + + def on_update + @username = setting?(String, :username) || "admin1" + @password = setting?(String, :password) || "panasonic" + end + + COMMANDS = { + power_on: "PON", + power_off: "POF", + power_query: "QPW", + freeze: "OFZ", + input: "IIS", + mute: "OSH", + lamp: "Q$S", + lamp_hours: "Q$L", + } + RESPONSES = COMMANDS.to_h.invert + + def power(state : Bool) + self[:stable_power] = @stable_power = false + self[:power_target] = state + + if state + logger.debug { "requested to power on" } + do_send(:power_on, retries: 10, name: :power, delay: 8.seconds) + do_send(:lamp) + else + logger.debug { "requested to power off" } + do_send(:power_off, retries: 10, name: :power, delay: 8.seconds).get + + # Schedule this after we have a result for the power function + # As the projector does not even update to cooling for awhile + schedule.in(10.seconds) { do_send(:lamp) } + end + end + + def power?(**options) + do_send(:lamp, **options) + end + + def lamp_hours?(**options) + do_send(:lamp_hours, 1, **options) + end + + INPUTS = { + Inputs::HDMI => "HD1", + Inputs::HDMI2 => "HD2", + Inputs::VGA => "RG1", + Inputs::VGA2 => "RG2", + Inputs::Miracast => "MC1", + Inputs::DVI => "DVI", + Inputs::DisplayPort => "DP1", + Inputs::HDBaseT => "DL1", + Inputs::Composite => "VID", + } + INPUT_LOOKUP = INPUTS.invert + + def switch_to(input : Inputs) + # Projector doesn't automatically unmute + unmute if self[:mute] + + do_send(:input, INPUTS[input], delay: 2.seconds) + logger.debug { "requested to switch to: #{input}" } + + self[:input] = input # for a responsive UI + end + + # Mutes audio + video + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + logger.debug { "requested mute state: #{state}" } + + # TODO:: remote seems to support audio mute and AV mute + # but I can't find an audio mute command + + actual = state ? 1 : 0 + do_send(:mute, actual) + end + + ERRORS = { + "ERR1" => "1: Undefined control command", + "ERR2" => "2: Out of parameter range", + "ERR3" => "3: Busy state or no-acceptable period", + "ERR4" => "4: Timeout or no-acceptable period", + "ERR5" => "5: Wrong data length", + "ERRA" => "A: Password mismatch", + "ER401" => "401: Command cannot be executed", + "ER402" => "402: Invalid parameter is sent", + } + + def received(data, task) + data = String.new(data).strip + logger.debug { "Panasonic sent: #{data}" } + + # This is sent by the projector on initial connection + # the channel is used to send the hash salt to the task sending a command + if data.starts_with? "NTCONTROL" + # check for protected mode + if @channel && !@channel.closed? + # 1 == protected mode + @channel.send(data[10] == '1' ? data[12..-1] : "") + else + transport.disconnect + end + return + end + + # we no longer need the connection to be open , the projector expects + # us to close it and a new connection is required per-command + transport.disconnect + + # Check for error response + if data[0] == 'E' + self[:last_error] = error_msg = ERRORS[data] + + if {"ERR3", "ERR4"}.includes? data + logger.info { "projector busy: #{error_msg} (#{data})" } + task.try &.retry + else + logger.error { "projector error: #{error_msg} (#{data})" } + task.try &.abort(error_msg) + end + return + end + + # We can't interpret this message without a task reference + # This also makes sure it is no longer nil + return unless task + + # Process the response + data = data[2..-1] + resp = data.split(':') + cmd = RESPONSES[resp[0]]? + val = resp[1]? + + case cmd + when :power_on + self[:power] = true + when :power_off + self[:power] = false + when :power_query + self[:power] = val.not_nil!.to_i == 1 + when :freeze + self[:frozen] = val.not_nil!.to_i == 1 + when :input + self[:input] = INPUT_LOOKUP[val] + when :mute + state = self[:mute] = val.not_nil!.to_i == 1 + self[:mute0] = state + self[:mute0_video] = state + self[:mute0_audio] = state + else + case task.name + when "lamp" + ival = resp[0].to_i + self[:power] = {1, 2}.includes?(ival) + self[:warming] = ival == 1 + self[:cooling] = ival == 3 + + # check target states here + if !@stable_power + if self[:power] == self[:power_target] + self[:stable_power] = @stable_power = true + else + power self[:power_target].as_bool + end + end + when "lamp_hours" + # Resp looks like: "001682" + self[:lamp_usage] = data.to_i + end + end + + task.success + end + + protected def do_send(command, param = nil, **options) + # prepare the command + cmd = if param.nil? + "00#{COMMANDS[command]}\r" + else + "00#{COMMANDS[command]}:#{param}\r" + end + + logger.debug { "queuing #{command}: #{cmd}" } + + # queue the request + queue(**({ + name: command, + }.merge(options))) do + # prepare channel and connect to the projector (which will then send the random key) + @channel = Channel(String).new + transport.connect + # wait for the random key to arrive + random_key = @channel.receive + # build the password hash + password_hash = if random_key.empty? + # An empty key indicates unauthenticated mode + "" + else + Digest::MD5.hexdigest("#{@username}:#{@password}:#{random_key}") + end + + message = "#{password_hash}#{cmd}" + logger.debug { "Sending: #{message}" } + + # send the request + # NOTE:: the built in `send` function has implicit queuing, but we are + # in a task callback here so should be calling transport send directly + transport.send(message) + end + end +end diff --git a/drivers/panasonic/projector/nt_control_spec.cr b/drivers/panasonic/projector/nt_control_spec.cr new file mode 100644 index 00000000000..dc26608fc1a --- /dev/null +++ b/drivers/panasonic/projector/nt_control_spec.cr @@ -0,0 +1,23 @@ +DriverSpecs.mock_driver "Panasonic::Projector::NTControl" do + # Execute a command (triggers the connection) + exec(:power?) + + # Once connected the projector will send a password salt + expect_reconnect + transmit "NTCONTROL 1 09b075be\r" + + # Check the request was sent with the correct password + password = "d4a58eaea919558fb54a33a2effa8b94" + should_send("#{password}00Q$S\r") + + # Respond with the status then check the state updated + transmit("00PON\r") + status[:power].should be_true + + exec(:lamp_hours?) + expect_reconnect + transmit "NTCONTROL 1 09b075be\r" + should_send("#{password}00Q$L:1\r") + transmit("001682\r") + status[:lamp_usage].should eq(1682) +end diff --git a/drivers/place/area_config.cr b/drivers/place/area_config.cr new file mode 100644 index 00000000000..e2fedc891ed --- /dev/null +++ b/drivers/place/area_config.cr @@ -0,0 +1,67 @@ +require "json" +require "./area_polygon" + +module Place + class Geometry + include JSON::Serializable + + def initialize(@coordinates, @geo_type = "Polygon") + end + + @[JSON::Field(key: "type")] + property geo_type : String + property coordinates : Array(Tuple(Float64, Float64)) + end + + class AreaConfig + include JSON::Serializable + + def initialize(@id, name, coordinates, building_id = nil, @area_type = "Feature", @feature_type = "section") + @geometry = Geometry.new(coordinates) + @properties = Hash(String, JSON::Any::Type | Hash(String, JSON::Any)).new + @properties["name"] = name + @properties["building_id"] = building_id if building_id + end + + @[JSON::Field(ignore: true)] + @polygon : Polygon? = nil + + property id : String + + @[JSON::Field(key: "type")] + property area_type : String + property feature_type : String + + property geometry : Geometry + property properties : Hash(String, JSON::Any::Type) + + @[JSON::Field(ignore: true)] + @adjusted_coords : Array(Tuple(Float64, Float64))? = nil + + def name : String + self.properties["name"].as(String) + end + + def building : String? + if id = self.properties["building_id"]? + id.as(String) + end + end + + def coordinates + if coords = @adjusted_coords + coords + else + self.geometry.coordinates + end + end + + def coordinates(map_width : Float64, map_height : Float64) + @adjusted_coords = self.geometry.coordinates.map { |(x, y)| {x * map_width, y * map_height} } + end + + def polygon : Polygon + @polygon ||= Polygon.new(coordinates.map { |coords| Point.new(*coords) }) + end + end +end diff --git a/drivers/place/area_management.cr b/drivers/place/area_management.cr new file mode 100644 index 00000000000..79deb61c50e --- /dev/null +++ b/drivers/place/area_management.cr @@ -0,0 +1,410 @@ +module Place; end + +require "set" +require "placeos" +require "./area_config" +require "./area_polygon" + +class Place::AreaManagement < PlaceOS::Driver + descriptive_name "PlaceOS Area Management" + generic_name :AreaManagement + description %(counts trackable objects, such as laptops, in building areas) + + accessor staff_api : StaffAPI_1 + + default_settings({ + building: "zone-12345", + + # time in seconds + poll_rate: 60, + + # How many wireless devices should we ignore + duplication_factor: 0.8, + + # Driver to query + location_service: "LocationServices", + + areas: { + "zone-1234" => [ + { + id: "lobby1", + name: "George St Lobby", + building: "building-zone-id", + coordinates: [{3, 5}, {5, 6}, {6, 1}], + }, + ], + }, + }) + + alias AreaSetting = NamedTuple( + id: String, + name: String, + building: String?, + coordinates: Array(Tuple(Float64, Float64))) + + alias AreaDetails = NamedTuple( + area_id: String, + name: String, + count: Int32, + ) + + alias LevelCapacity = NamedTuple( + total_desks: Int32, + total_capacity: Int32, + desk_ids: Array(String), + ) + + alias RawLevelDetails = NamedTuple( + wireless_devices: Int32, + desk_usage: Int32, + capacity: LevelCapacity, + ) + + # zone_id => areas + @level_areas : Hash(String, Array(AreaConfig)) = {} of String => Array(AreaConfig) + # area_id => area + @areas : Hash(String, AreaConfig) = {} of String => AreaConfig + + # zone_id => desk_ids + @duplication_factor : Float64 = 0.8 + @level_details : Hash(String, LevelCapacity) = {} of String => LevelCapacity + + # PlaceOS client config + @building_id : String = "" + + @poll_rate : Time::Span = 60.seconds + @location_service : String = "LocationServices" + + @rate_limit : Channel(Nil) = Channel(Nil).new + @update_lock : Mutex = Mutex.new + @terminated = false + + def on_load + spawn { rate_limiter } + spawn(same_thread: true) { update_scheduler } + + on_update + end + + def on_unload + @terminated = true + @rate_limit.close + end + + def on_update + @building_id = setting(String, :building) + + @poll_rate = (setting?(Int32, :poll_rate) || 60).seconds + @location_service = setting?(String, :location_service).presence || "LocationServices" + @duplication_factor = setting?(Float64, :duplication_factor) || 0.8 + + # Areas are defined in metadata, this is mainly here so we can write specs + if building_areas = setting?(Hash(String, Array(AreaSetting)), :areas) + @level_areas.clear + building_areas.each do |zone_id, areas| + @level_areas[zone_id] = areas.map do |area| + config = AreaConfig.new(area[:id], area[:name], area[:coordinates], area[:building]) + @areas[config.id] = config + config + end + end + end + + schedule.clear + schedule.every(@poll_rate) { synchronize_all_levels } + end + + # The location services provider + protected def location_service + system[@location_service] + end + + # Updates a single zone, syncing the metadata + protected def update_level_details(level_details, zone, metadata) + return unless zone.tags.includes?("level") + + if desks = metadata["desks"]? + ids = desks.details.as_a.map { |desk| desk["id"].as_s } + level_details[zone.id] = { + total_desks: ids.size, + total_capacity: zone.capacity, + desk_ids: ids, + } + else + level_details[zone.id] = { + total_desks: zone.count, + total_capacity: zone.capacity, + desk_ids: [] of String, + } + end + + if regions = metadata["map_regions"]? + area_data = Array(AreaConfig).from_json(regions.details["areas"].to_json) + @level_areas[zone.id] = area_data + area_data.each { |area| @areas[area.id] = area } + else + @level_areas.delete(zone.id) + end + end + + alias Zone = PlaceOS::Client::API::Models::Zone + alias Metadata = Hash(String, PlaceOS::Client::API::Models::Metadata) + alias ChildMetadata = Array(NamedTuple(zone: Zone, metadata: Metadata)) + + # Grabs all the level zones in the building and syncs the metadata + protected def sync_level_details + # Attempt to obtain the latest version of the metadata + response = ChildMetadata.from_json(staff_api.metadata_children(@building_id).get.to_json) + + level_details = {} of String => LevelCapacity + response.each do |meta| + update_level_details(level_details, meta[:zone], meta[:metadata]) + end + @level_details = level_details + rescue error + logger.error(exception: error) { "obtaining level metadata" } + end + + protected def update_level_locations(level_counts, level_id, details) + areas = @level_areas[level_id]? || [] of AreaConfig + + # Provide the frontend with the list of all known desk ids on a level + self["#{level_id}:desk_ids"] = details[:desk_ids] + + # Get location data for the level + locations = location_service.device_locations(level_id).get.as_a + + # Provide to the frontend + self[level_id] = { + value: locations, + ts_hint: "complex", + ts_map: { + x: "xloc", + y: "yloc", + }, + ts_tag_keys: {"s2_cell_id"}, + ts_fields: { + pos_level: level_id, + }, + ts_tags: { + pos_building: @building_id, + }, + } + + # Grab the x,y locations + wireless_count = 0 + desk_count = 0 + xy_locs = locations.select do |loc| + case loc["location"].as_s + when "wireless" + wireless_count += 1 + + # Keep if x, y coords are present + !loc["x"].raw.nil? + when "desk" + desk_count += 1 + false + else + false + end + end + + # build the level overview + level_counts[level_id] = { + wireless_devices: wireless_count, + desk_usage: desk_count, + capacity: details, + } + + # we need to know the map dimensions to be able to count people in areas + map_width = -1.0 + map_height = -1.0 + + if tmp_loc = xy_locs[0]? + # ensure map width and height are known + map_width_raw = tmp_loc["map_width"]?.try(&.raw) + case map_width_raw + when Int64, Float64 + map_width = map_width_raw.to_f + end + + map_height_raw = tmp_loc["map_height"]?.try(&.raw) + case map_height_raw + when Int64, Float64 + map_height = map_height_raw.to_f + end + end + + # Calculate the device counts for each area + area_counts = [] of AreaDetails + if map_width && map_height + areas.each do |area| + count = 0 + + # Ensure the area is configured + area.coordinates(map_width, map_height) + polygon = area.polygon + + # Calculate counts, our config uses browser coordinate systems, + # so need to adjust any x,y values being received for this + xy_locs.each do |loc| + case loc["coordinates_from"]?.try(&.raw) + when "bottom-left" + count += 1 if polygon.contains(loc["x"].as_f, map_height - loc["y"].as_f) + else + count += 1 if polygon.contains(loc["x"].as_f, loc["y"].as_f) + end + end + + area_counts << { + area_id: area.id, + name: area.name, + count: count, + } + end + end + + # Provide the frontend the area details + self["#{level_id}:areas"] = { + value: area_counts, + ts_hint: "complex", + ts_fields: { + pos_level: level_id, + }, + ts_tags: { + pos_building: @building_id, + }, + } + rescue error + log_location_parsing(error, level_id) + sleep 200.milliseconds + end + + @level_counts : Hash(String, RawLevelDetails) = {} of String => RawLevelDetails + + def request_locations + @update_lock.synchronize do + sync_level_details + + # level => user count + level_counts = {} of String => RawLevelDetails + @level_details.each do |level_id, details| + update_level_locations(level_counts, level_id, details) + end + @level_counts = level_counts + update_overview + end + end + + def request_level_locations(level_id : String) : Nil + @update_lock.synchronize do + zone = Zone.from_json(staff_api.zone(level_id).get.to_json) + if !zone.tags.includes?("level") + logger.warn { "attempted to update location for #{zone.name} (#{level_id}) which is not tagged as a level" } + return + end + metadata = Metadata.from_json(staff_api.metadata(level_id).get.to_json) + + update_level_details @level_details, zone, metadata + update_level_locations @level_counts, level_id, @level_details[level_id] + update_overview + end + end + + protected def update_overview + self[:overview] = @level_counts.transform_values { |details| build_level_stats(**details) } + end + + protected def log_location_parsing(error, level_id) + logger.debug(exception: error) { "while parsing #{level_id}" } + rescue + end + + def is_inside?(x : Float64, y : Float64, area_id : String) + area = @areas[area_id] + area.polygon.contains(x, y) + end + + protected def build_level_stats(wireless_devices, desk_usage, capacity) + # raw data + total_desks = capacity[:total_desks] + total_capacity = capacity[:total_capacity] + + # normalised data + adjusted_devices = wireless_devices * @duplication_factor + + if total_capacity <= 0 + percentage_use = 100.0 + individual_impact = 100.0 + else + percentage_use = (adjusted_devices / total_capacity) * 100.0 + individual_impact = 100.0 / total_capacity + end + remaining_capacity = total_capacity - adjusted_devices + recommendation = remaining_capacity + remaining_capacity * individual_impact + + { + desk_count: total_desks, + desk_usage: desk_usage, + device_capacity: total_capacity, + device_count: wireless_devices, + estimated_people: adjusted_devices.to_i, + percentage_use: percentage_use, + + # higher the number, better the recommendation + recommendation: recommendation, + } + end + + # This is to limit the number of "real-time" updates + # batching operations to provide fast updates that don't waste CPU cycles + protected def rate_limiter + sleep 3 + + loop do + begin + break if @rate_limit.closed? + @rate_limit.send(nil) + rescue error + logger.error(exception: error) { "issue with rate limiter" } + ensure + sleep 3 + end + end + rescue + # Possible error with logging exception, restart rate limiter silently + spawn { rate_limiter } unless @terminated + end + + @update_levels : Set(String) = Set.new([] of String) + @update_all : Bool = true + @schedule_lock : Mutex = Mutex.new + + def update_available(level_ids : Array(String)) + @schedule_lock.synchronize { @update_levels.concat level_ids } + end + + def synchronize_all_levels + @schedule_lock.synchronize { @update_all = true } + end + + protected def update_scheduler + loop do + @rate_limit.receive + @schedule_lock.synchronize do + begin + if @update_all + request_locations + else + @update_levels.each { |level_id| request_level_locations level_id } + end + rescue error + logger.error(exception: error) { "error updating floors" } + ensure + @update_levels.clear + @update_all = false + end + end + end + end +end diff --git a/drivers/place/area_management_spec.cr b/drivers/place/area_management_spec.cr new file mode 100644 index 00000000000..5b38a1457c1 --- /dev/null +++ b/drivers/place/area_management_spec.cr @@ -0,0 +1,16 @@ +DriverSpecs.mock_driver "Place::AreaCount" do + # Used this tool to work out coordinates: https://www.mathsisfun.com/geometry/polygons-interactive.html + exec(:is_inside?, 4, 5, "lobby1").get.should eq(true) + exec(:is_inside?, 4, 4, "lobby1").get.should eq(true) + exec(:is_inside?, 5, 5, "lobby1").get.should eq(true) + exec(:is_inside?, 5, 4, "lobby1").get.should eq(true) + exec(:is_inside?, 5, 3, "lobby1").get.should eq(true) + exec(:is_inside?, 3.1, 5, "lobby1").get.should eq(true) + + exec(:is_inside?, 3, 6, "lobby1").get.should eq(false) + exec(:is_inside?, 4, 6, "lobby1").get.should eq(false) + exec(:is_inside?, 4.6, 5.9, "lobby1").get.should eq(false) + exec(:is_inside?, 5.2, 5.4, "lobby1").get.should eq(false) + exec(:is_inside?, 5.5, 1.5, "lobby1").get.should eq(false) + exec(:is_inside?, 5.9, 2, "lobby1").get.should eq(false) +end diff --git a/drivers/place/area_polygon.cr b/drivers/place/area_polygon.cr new file mode 100644 index 00000000000..8bdfe7c349d --- /dev/null +++ b/drivers/place/area_polygon.cr @@ -0,0 +1,60 @@ +# Crystal lang point in a polygon, based on +# https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html + +# 1. The polygon may contain multiple separate components, and/or holes, which may be concave, provided that you separate the components and holes with a (0,0) vertex, as follows. +# First, include a (0,0) vertex. +# Then include the first component' vertices, repeating its first vertex after the last vertex. +# Include another (0,0) vertex. +# Include another component or hole, repeating its first vertex after the last vertex. +# Repeat the above two steps for each component and hole. +# Include a final (0,0) vertex. +# 2. For example, let three components' vertices be A1, A2, A3, B1, B2, B3, and C1, C2, C3. Let two holes be H1, H2, H3, and I1, I2, I3. Let O be the point (0,0). List the vertices thus: +# O, A1, A2, A3, A1, O, B1, B2, B3, B1, O, C1, C2, C3, C1, O, H1, H2, H3, H1, O, I1, I2, I3, I1, O. +# 3. Each component or hole's vertices may be listed either clockwise or counter-clockwise. +# 4. If there is only one connected component, then it is optional to repeat the first vertex at the end. It's also optional to surround the component with zero vertices. + +struct Point + def initialize(@x : Float64, @y : Float64) + end + + property x : Float64 + property y : Float64 +end + +class Polygon + def initialize(@points : Array(Point)) + @xmax = @xmin = @points[0].x + @ymax = @ymin = @points[0].y + + @points[1..-1].each do |point| + @xmax = point.x if point.x > @xmax + @ymax = point.y if point.y > @ymax + @xmin = point.x if point.x < @xmin + @ymin = point.y if point.y < @ymin + end + end + + getter points : Array(Point) + getter xmin : Float64 + getter ymin : Float64 + getter xmax : Float64 + getter ymax : Float64 + + def contains(testx : Float64, testy : Float64) + # definitely not within the polygon, quick check + return false if testx < @xmin || testx > @xmax || testy < @ymin || testy > @ymax + + inside = false + previous_index = @points.size - 1 + + @points.each_with_index do |point, index| + previous = @points[previous_index] + if ((point.y > testy) != (previous.y > testy)) && (testx < (previous.x - point.x) * (testy - point.y) / (previous.y - point.y) + point.x) + inside = !inside + end + previous_index = index + end + + inside + end +end diff --git a/drivers/place/bookings.cr b/drivers/place/bookings.cr new file mode 100644 index 00000000000..a8565f6de8e --- /dev/null +++ b/drivers/place/bookings.cr @@ -0,0 +1,279 @@ +module Place; end + +require "place_calendar" + +class Place::Bookings < PlaceOS::Driver + descriptive_name "PlaceOS Bookings" + generic_name :Bookings + + default_settings({ + calendar_id: nil, + calendar_time_zone: "Australia/Sydney", + book_now_default_title: "Ad Hoc booking", + disable_book_now: false, + disable_end_meeting: false, + pending_period: 5, + pending_from: 5, + cache_polling_period: 2, + + control_ui: "https://if.panel/to_be_used_for_control", + catering_ui: "https://if.panel/to_be_used_for_catering", + }) + + accessor calendar : Calendar_1 + + @calendar_id : String = "" + @time_zone : Time::Location = Time::Location.load("Australia/Sydney") + @default_title : String = "Ad Hoc booking" + @disable_book_now : Bool = false + @disable_end_meeting : Bool = false + @pending_period : Time::Span = 5.minutes + @pending_before : Time::Span = 5.minutes + @bookings : Array(JSON::Any) = [] of JSON::Any + + def on_load + monitor("staff/event/changed") { |_subscription, payload| check_change(payload) } + + on_update + end + + def on_update + schedule.clear + @calendar_id = setting?(String, :calendar_id).presence || system.email.not_nil! + + schedule.in(Random.rand(60).seconds + Random.rand(1000).milliseconds) { poll_events } + + cache_polling_period = (setting?(UInt32, :cache_polling_period) || 2_u32).minutes + cache_polling_period += Random.rand(30).seconds + Random.rand(1000).milliseconds + schedule.every(cache_polling_period) { poll_events } + + time_zone = setting?(String, :calendar_time_zone).presence + @time_zone = Time::Location.load(time_zone) if time_zone + + @default_title = setting?(String, :book_now_default_title).presence || "Ad Hoc booking" + + book_now = setting?(Bool, :disable_book_now) + @disable_book_now = book_now.nil? ? !system.bookable : !!book_now + @disable_end_meeting = !!setting?(Bool, :disable_end_meeting) + + pending_period = setting?(UInt32, :pending_period) || 5_u32 + @pending_period = pending_period.minutes + + pending_before = setting?(UInt32, :pending_before) || 5_u32 + @pending_before = pending_before.minutes + + @last_booking_started = setting?(Int64, :last_booking_started) || 0_i64 + + # Write to redis last on the off chance there is a connection issue + self[:default_title] = @default_title + self[:disable_book_now] = @disable_book_now + self[:disable_end_meeting] = @disable_end_meeting + self[:pending_period] = pending_period + self[:pending_before] = pending_before + self[:control_ui] = setting?(String, :control_ui) + self[:catering_ui] = setting?(String, :catering_ui) + end + + # This is how we check the rooms status + @last_booking_started : Int64 = 0_i64 + + def start_meeting(meeting_start_time : Int64) : Nil + logger.debug { "starting meeting #{meeting_start_time}" } + @last_booking_started = meeting_start_time + define_setting(:last_booking_started, meeting_start_time) + check_current_booking + end + + # End either the current meeting early, or the pending meeting + def end_meeting(meeting_start_time : Int64) : Nil + cmeeting = current + result = if cmeeting && cmeeting.event_start.to_unix == meeting_start_time + logger.debug { "deleting event #{cmeeting.title}, from #{@calendar_id}" } + calendar.delete_event(@calendar_id, cmeeting.id) + else + nmeeting = upcoming + if nmeeting && nmeeting.event_start.to_unix == meeting_start_time + logger.debug { "deleting event #{nmeeting.title}, from #{@calendar_id}" } + calendar.delete_event(@calendar_id, nmeeting.id) + else + raise "only the current or pending meeting can be cancelled" + end + end + result.get + + # Update the display + poll_events + check_current_booking + end + + def book_now(period_in_seconds : Int64, title : String? = nil, owner : String? = nil) + title ||= @default_title + starting = Time.utc.to_unix + ending = starting + period_in_seconds + + logger.debug { "booking event #{title}, from #{starting}, to #{ending}, in #{@time_zone.name}, on #{@calendar_id}" } + + host_calendar = owner.presence || @calendar_id + room_email = system.email.not_nil! + room_is_organizer = host_calendar == room_email + event = calendar.create_event( + title, + starting, + ending, + "", + [PlaceCalendar::Event::Attendee.new(room_email, room_email, "accepted", true, room_is_organizer)], + @time_zone.name, + nil, + host_calendar + ) + # Update booking info after creating event + poll_events + event + end + + def poll_events : Nil + now = Time.local @time_zone + start_of_week = now.at_beginning_of_week.to_unix + four_weeks_time = start_of_week + 30.days.to_i + + logger.debug { "polling events #{@calendar_id}, from #{start_of_week}, to #{four_weeks_time}, in #{@time_zone.name}" } + + events = calendar.list_events( + @calendar_id, + start_of_week, + four_weeks_time, + @time_zone.name + ).get + + @bookings = events.as_a.sort { |a, b| a["event_start"].as_i64 <=> b["event_start"].as_i64 } + self[:bookings] = @bookings + + check_current_booking + end + + protected def check_current_booking : Nil + now = Time.utc.to_unix + previous_booking = nil + current_booking = nil + next_booking = Int32::MAX + + @bookings.each_with_index do |event, index| + starting = event["event_start"].as_i64 + + # All meetings are in the future + if starting > now + next_booking = index + previous_booking = index - 1 if index > 0 + break + end + + # Calculate event end time + ending_unix = if ending = event["event_end"]? + ending.as_i64 + else + starting + 24.hours.to_i + end + + # Event ended in the past + next if ending_unix < now + + # We've found the current event + if starting <= now && ending_unix > now + current_booking = index + previous_booking = index - 1 if index > 0 + next_booking = index + 1 + break + end + end + + self[:previous_booking] = previous_booking ? @bookings[previous_booking] : nil + + # Configure room status (free, pending, in-use) + current_pending = false + next_pending = false + booked = false + + if current_booking + booking = @bookings[current_booking] + start_time = booking["event_start"].as_i64 + + booked = true + # Up to the frontend to delete pending bookings that have past their start time + if !@disable_end_meeting + current_pending = true if start_time > @last_booking_started + elsif @pending_period.to_i > 0_i64 + pending_limit = (Time.unix(start_time) + @pending_period).to_unix + current_pending = true if start_time < pending_limit + end + + self[:current_booking] = booking + else + self[:current_booking] = nil + end + + self[:booked] = booked + + # We haven't checked the index of `next_booking` exists, hence the `[]?` + if booking = @bookings[next_booking]? + start_time = booking["event_start"].as_i64 + next_pending = true if start_time <= @pending_before.from_now.to_unix + self[:next_booking] = booking + else + self[:next_booking] = nil + end + + # Check if pending is enabled + if @pending_period.to_i > 0_i64 + self[:current_pending] = current_pending + self[:next_pending] = next_pending + self[:pending] = current_pending || next_pending + + self[:in_use] = booked && !current_pending + else + self[:current_pending] = nil + self[:next_pending] = nil + self[:pending] = nil + + self[:in_use] = booked + end + + # TODO:: set video_conference_url if found in the event details + + self[:status] = (current_pending || next_pending) ? "pending" : (booked ? "busy" : "free") + end + + protected def current : PlaceCalendar::Event? + status?(PlaceCalendar::Event, :current_booking) + end + + protected def upcoming : PlaceCalendar::Event? + status?(PlaceCalendar::Event, :next_booking) + end + + class StaffEventChange + include JSON::Serializable + + property action : String # create, update, cancelled + property system_id : String # primary calendar effected + property event_id : String + property resource : String # the resource email that is effected + end + + # This is called when bookings are modified via the staff app + # it allows us to update the cache faster than via polling alone + protected def check_change(payload : String) + event = StaffEventChange.from_json(payload) + if event.system_id == system.id + poll_events + check_current_booking + else + matching = @bookings.select { |b| b["id"] == event.event_id } + if matching + poll_events + check_current_booking + end + end + rescue error + logger.error { "processing change event: #{error.inspect_with_backtrace}" } + end +end diff --git a/drivers/place/bookings_spec.cr b/drivers/place/bookings_spec.cr new file mode 100644 index 00000000000..b26efae3788 --- /dev/null +++ b/drivers/place/bookings_spec.cr @@ -0,0 +1,232 @@ +DriverSpecs.mock_driver "Place::Bookings" do + system({ + Calendar: {Calendar}, + }) + + # Check it calculates state properly + exec(:poll_events).get + bookings = status[:bookings].as_a + bookings.size.should eq(4) + status[:booked].should eq(true) + status[:in_use].should eq(false) + status[:pending].should eq(true) + status[:current_pending].should eq(true) + status[:next_pending].should eq(false) + status[:status].should eq("pending") + + current_start = bookings[0]["event_start"] + + # Start a meeting + exec(:start_meeting, current_start).get + bookings = status[:bookings].as_a + bookings.size.should eq(4) + status[:booked].should eq(true) + status[:in_use].should eq(true) + status[:pending].should eq(false) + status[:current_pending].should eq(false) + status[:next_pending].should eq(false) + status[:status].should eq("busy") + + # End a meeting + exec(:end_meeting, current_start).get + bookings = status[:bookings].as_a + bookings.size.should eq(3) + + status[:booked].should eq(false) + status[:in_use].should eq(false) + status[:pending].should eq(false) + status[:current_pending].should eq(false) + status[:next_pending].should eq(false) + status[:status].should eq("free") +end + +class Calendar < DriverSpecs::MockDriver + def on_load + self[:checked_calendar] = nil + self[:deleted_event] = nil + end + + def delete_event(calendar_id : String, event_id : String, user_id : String? = nil) + self[:deleted_event] = {calendar_id, event_id} + @events = @events.reject { |event| event["id"] == event_id } + nil + end + + def list_events( + calendar_id : String, + period_start : Int64, + period_end : Int64, + time_zone : String? = nil, + user_id : String? = nil + ) + self[:checked_calendar] = calendar_id + @events + end + + @events : Array(Hash(String, Array(Hash(String, String)) | Bool | Int64 | String | Array(Nil))) = [ + { + "event_start" => 10.minutes.ago.to_unix, + "event_end" => 20.minutes.from_now.to_unix, + "id" => "2hg6c13j9ko8hiugmuj8n3jtap_20200804T000000Z", + "host" => "jeremy@place.nology", + "title" => "A Standup", + "description" => "", + "attendees" => [ + { + "name" => "alexandre@place.nology", + "email" => "alexandre@place.nology", + }, + { + "name" => "candy@place.nology", + "email" => "candy@place.nology", + }, + { + "name" => "viv@place.nology", + "email" => "viv@place.nology", + }, + { + "name" => "steve@place.nology", + "email" => "steve@place.nology", + }, + { + "name" => "jeremy@place.nology", + "email" => "jeremy@place.nology", + }, + ], + "private" => false, + "recurring" => false, + "all_day" => false, + "timezone" => "UTC", + "attachments" => [] of Nil, + }, + { + "event_start" => 40.minutes.from_now.to_unix, + "event_end" => 1.hour.from_now.to_unix, + "id" => "2hg6c13j9ko8hiugmuj8n3jtap_20200806T000000Z", + "host" => "jeremy@place.nology", + "title" => "A Standup", + "description" => "", + "attendees" => [ + { + "name" => "alexandre@place.nology", + "email" => "alexandre@place.nology", + }, + { + "name" => "candy@place.nology", + "email" => "candy@place.nology", + }, + { + "name" => "viv@place.nology", + "email" => "viv@place.nology", + }, + { + "name" => "steve@place.nology", + "email" => "steve@place.nology", + }, + { + "name" => "jeremy@place.nology", + "email" => "jeremy@place.nology", + }, + ], + "private" => false, + "recurring" => false, + "all_day" => false, + "timezone" => "UTC", + "attachments" => [] of Nil, + }, + { + "event_start" => 4.hour.from_now.to_unix, + "event_end" => 5.hour.from_now.to_unix, + "id" => "0e1f5n6a898n85eo9gsj169kh1_20200806T010000Z", + "host" => "shreya@external.com", + "title" => "Place weekly catchup", + "description" => "", + "attendees" => [ + { + "name" => "Michael", + "email" => "michael@external.com", + }, + { + "name" => "Glenn", + "email" => "glenn@external.com", + }, + { + "name" => "Shreya", + "email" => "shreya@external.com", + }, + { + "name" => "jeremy@place.nology", + "email" => "jeremy@place.nology", + }, + { + "name" => "Lisa", + "email" => "lisa@external.com", + }, + { + "name" => "Sheshank", + "email" => "sheshank@external.com", + }, + { + "name" => "steve@place.nology", + "email" => "steve@place.nology", + }, + { + "name" => "Zinoca", + "email" => "zain@external.com", + }, + { + "name" => "Aymie", + "email" => "aymie@external.com", + }, + ], + "private" => false, + "recurring" => false, + "all_day" => false, + "timezone" => "UTC", + "attachments" => [] of Nil, + }, + { + "event_start" => 10.hours.from_now.to_unix, + "event_end" => 11.hours.from_now.to_unix, + "id" => "d8n8u5a5u8j45jgm5248ir49qs_20200806T010000Z", + "host" => "jeremy@place.nology", + "title" => "PlaceOS Standup", + "description" => "Regular Standup to discuss Engine2 Development and Product Requirements.", + "attendees" => [ + { + "name" => "caspian@place.nology", + "email" => "caspian@place.nology", + }, + { + "name" => "viv@place.nology", + "email" => "viv@place.nology", + }, + { + "name" => "Kim", + "email" => "kim@place.nology", + }, + { + "name" => "William", + "email" => "w.le@place.nology", + }, + { + "name" => "jeremy@place.nology", + "email" => "jeremy@place.nology", + }, + { + "name" => "Shane", + "email" => "shane@place.nology", + }, + { + "name" => "steve@place.nology", + "email" => "steve@place.nology", + }, + ], + "private" => false, + "recurring" => false, + "all_day" => false, + "timezone" => "UTC", + "attachments" => [] of Nil, + }, + ] +end diff --git a/drivers/place/calendar.cr b/drivers/place/calendar.cr new file mode 100644 index 00000000000..06b533c2f74 --- /dev/null +++ b/drivers/place/calendar.cr @@ -0,0 +1,292 @@ +module Place; end + +require "place_calendar" +require "placeos-driver/interface/mailer" +require "qr-code" +require "qr-code/export/png" + +class Place::Calendar < PlaceOS::Driver + include PlaceOS::Driver::Interface::Mailer + + descriptive_name "PlaceOS Calendar" + generic_name :Calendar + + uri_base "https://staff.app.api.com" + + default_settings({ + calendar_service_account: "service_account@email.address", + calendar_config: { + scopes: ["https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/admin.directory.user.readonly"], + domain: "primary.domain.com", + sub: "default.service.account@google.com", + issuer: "placeos@organisation.iam.gserviceaccount.com", + signing_key: "PEM encoded private key", + }, + calendar_config_office: { + _note_: "rename to 'calendar_config' for use", + tenant: "", + client_id: "", + client_secret: "", + }, + rate_limit: 5, + + # defaults to calendar_service_account if not configured + mailer_from: "email_or_office_userPrincipalName", + email_templates: {visitor: {checkin: { + subject: "%{name} has arrived", + text: "for your meeting at %{time}", + }}}, + }) + + alias GoogleParams = NamedTuple( + scopes: String | Array(String), + domain: String, + sub: String, + issuer: String, + signing_key: String, + ) + + alias OfficeParams = NamedTuple( + tenant: String, + client_id: String, + client_secret: String, + ) + + @client : PlaceCalendar::Client? = nil + @service_account : String? = nil + @client_lock : Mutex = Mutex.new + @rate_limit : Int32 = 3 + @channel : Channel(Nil) = Channel(Nil).new(3) + + @queue_lock : Mutex = Mutex.new + @queue_size = 0 + @wait_time : Time::Span = 300.milliseconds + + @mailer_from : String? = nil + + def on_load + @channel = Channel(Nil).new(2) + spawn { rate_limiter } + on_update + end + + def on_update + @service_account = setting?(String, :calendar_service_account).presence + @rate_limit = setting?(Int32, :rate_limit) || 3 + @wait_time = 1.second / @rate_limit + + @mailer_from = setting?(String, :mailer_from).presence || @service_account + @templates = setting?(Templates, :email_templates) || Templates.new + + @client_lock.synchronize do + # Work around crystal limitation of splatting a union + @client = begin + config = setting(GoogleParams, :calendar_config) + PlaceCalendar::Client.new(**config) + rescue + config = setting(OfficeParams, :calendar_config) + PlaceCalendar::Client.new(**config) + end + end + end + + protected def client + if (@wait_time * @queue_size) > 10.seconds + raise "wait time would be exceeded for API request, #{@queue_size} requests already queued" + end + @queue_lock.synchronize { @queue_size += 1 } + @client_lock.synchronize do + @channel.receive + @queue_lock.synchronize { @queue_size -= 1 } + yield @client.not_nil! + end + end + + def queue_size + @queue_size + end + + def generate_svg_qrcode(text : String) : String + QRCode.new(text).as_svg + end + + def generate_png_qrcode(text : String, size : Int32 = 128) : String + Base64.strict_encode QRCode.new(text).as_png(size: size) + end + + @[Security(Level::Support)] + def send_mail( + to : String | Array(String), + subject : String, + message_plaintext : String? = nil, + message_html : String? = nil, + resource_attachments : Array(ResourceAttachment) = [] of ResourceAttachment, + attachments : Array(Attachment) = [] of Attachment, + cc : String | Array(String) = [] of String, + bcc : String | Array(String) = [] of String, + from : String | Array(String) | Nil = nil + ) + sender = case from + in String + from + in Array(String) + from.first? || @mailer_from.not_nil! + in Nil + @mailer_from.not_nil! + end + + logger.debug { "an email was sent from: #{sender}, to: #{to}" } + + client &.calendar.send_mail( + sender, + to, + subject, + message_plaintext, + message_html, + resource_attachments, + attachments, + cc, + bcc + ) + end + + @[Security(Level::Administrator)] + def access_token(user_id : String? = nil) + logger.info { "access token requested #{user_id}" } + client &.access_token(user_id) + end + + @[Security(Level::Support)] + def get_groups(user_id : String) + logger.debug { "getting group membership for user: #{user_id}" } + client &.get_groups(user_id) + end + + @[Security(Level::Support)] + def get_members(group_id : String) + logger.debug { "listing members of group: #{group_id}" } + client &.get_members(group_id) + end + + @[Security(Level::Support)] + def list_users(query : String? = nil, limit : Int32? = nil) + logger.debug { "listing user details, query #{query}" } + client &.list_users(query, limit) + end + + @[Security(Level::Support)] + def get_user(user_id : String) + logger.debug { "getting user details for #{user_id}" } + client &.get_user_by_email(user_id) + end + + @[Security(Level::Support)] + def list_calendars(user_id : String) + logger.debug { "listing calendars for #{user_id}" } + client &.list_calendars(user_id) + end + + # NOTE:: GraphAPI Only! + @[Security(Level::Support)] + def get_user_manager(user_id : String) + logger.debug { "getting manager details for #{user_id}, note: graphAPI only" } + client do |_client| + if _client.client_id == :office365 + _client.calendar.as(PlaceCalendar::Office365).client.get_user_manager(user_id).to_place_calendar + end + end + end + + # NOTE:: GraphAPI Only! - here for use with configuration + @[Security(Level::Support)] + def list_groups(query : String?) + logger.debug { "listing groups, filtering by #{query}, note: graphAPI only" } + client do |_client| + if _client.client_id == :office365 + _client.calendar.as(PlaceCalendar::Office365).client.list_groups(query) + end + end + end + + # NOTE:: GraphAPI Only! + @[Security(Level::Support)] + def get_group(group_id : String) + logger.debug { "getting group #{group_id}, note: graphAPI only" } + client do |_client| + if _client.client_id == :office365 + _client.calendar.as(PlaceCalendar::Office365).client.get_group(group_id) + end + end + end + + @[Security(Level::Support)] + def list_events(calendar_id : String, period_start : Int64, period_end : Int64, time_zone : String? = nil, user_id : String? = nil) + location = time_zone ? Time::Location.load(time_zone) : Time::Location.local + period_start = Time.unix(period_start).in location + period_end = Time.unix(period_end).in location + user_id = user_id || @service_account.presence || calendar_id + + logger.debug { "listing events for #{calendar_id}" } + + client &.list_events(user_id, calendar_id, + period_start: period_start, + period_end: period_end + ) + end + + @[Security(Level::Support)] + def delete_event(calendar_id : String, event_id : String, user_id : String? = nil) + user_id = user_id || @service_account.presence || calendar_id + + logger.debug { "deleting event #{event_id} on #{calendar_id}" } + + client &.delete_event(user_id, event_id, calendar_id: calendar_id) + end + + @[Security(Level::Support)] + def create_event( + title : String, + event_start : Int64, + event_end : Int64? = nil, + description : String = "", + attendees : Array(PlaceCalendar::Event::Attendee) = [] of PlaceCalendar::Event::Attendee, + timezone : String? = nil, + user_id : String? = nil, + calendar_id : String? = nil + ) + user_id = (user_id || @service_account.presence || calendar_id).not_nil! + calendar_id = calendar_id || user_id + + logger.debug { "creating event on #{calendar_id}" } + + event = PlaceCalendar::Event.new + event.host = calendar_id + event.title = title + event.body = description + event.timezone = timezone + event.attendees = attendees + event.event_start = Time.unix(event_start) + if event_end + event.event_end = Time.unix(event_end) + else + event.all_day = true + end + + client &.create_event(user_id, event, calendar_id) + end + + protected def rate_limiter + loop do + begin + @channel.send(nil) + rescue error + logger.error(exception: error) { "issue with rate limiter" } + ensure + sleep @wait_time + end + end + rescue + # Possible error with logging exception, restart rate limiter silently + spawn { rate_limiter } + end +end diff --git a/drivers/place/desk_booking_webhook.cr b/drivers/place/desk_booking_webhook.cr new file mode 100644 index 00000000000..c2cb29eff29 --- /dev/null +++ b/drivers/place/desk_booking_webhook.cr @@ -0,0 +1,70 @@ +module Place; end + +require "http/client" + +class Place::DeskBookingWebhook < PlaceOS::Driver + descriptive_name "Desk Booking Webhook" + generic_name :DeskBookingWebhook + description %(sends a webhook with booking information as it changes) + + accessor staff_api : StaffAPI_1 + + default_settings({ + post_uri: "https://remote-server/path", + building: "zone-id", + + custom_headers: { + "API_KEY" => "123456", + }, + + # how many days from now do we want to send + days_from_now: 14, + + booking_category: "desk", + + debug: false, + }) + + def on_load + monitor("staff/booking/changed") do |_subscription, payload| + logger.debug { "received booking changed event #{payload}" } + fetch_and_post + end + schedule.every(24.hours) { fetch_and_post } + on_update + end + + @time_period : Time::Span = 14.days + @booking_category : String = "desk" + @custom_headers = {} of String => String + @building = "" + @post_uri = "" + @debug : Bool = false + + def on_update + @post_uri = setting(String, :post_uri) + @building = setting(String, :building) + @custom_headers = setting(Hash(String, String), :custom_headers) + @time_period = setting(Int32, :days_from_now).days + @booking_category = setting(String, :booking_category) + @debug = setting(Bool, :debug) + + fetch_and_post + end + + def fetch_and_post + period_start = Time.utc.to_unix + period_end = @time_period.from_now.to_unix + zones = [@building] + payload = staff_api.query_bookings(@booking_category, period_start, period_end, zones).get.to_json + + headers = HTTP::Headers.new + @custom_headers.each { |key, value| headers[key] = value } + headers["Content-Type"] = "application/json; charset=UTF-8" + + logger.debug { "Posting: #{payload} \n with Headers: #{headers}" } if @debug + response = HTTP::Client.post @post_uri, headers, body: payload + raise "Request failed with #{response.status_code}: #{response.body}" unless response.status_code < 300 + "#{response.status_code}: #{response.body}" + end +end diff --git a/drivers/place/desk_bookings_locations.cr b/drivers/place/desk_bookings_locations.cr new file mode 100644 index 00000000000..a4de0f65448 --- /dev/null +++ b/drivers/place/desk_bookings_locations.cr @@ -0,0 +1,230 @@ +module Place; end + +require "json" +require "placeos-driver/interface/locatable" + +class Place::DeskBookingsLocations < PlaceOS::Driver + include Interface::Locatable + + descriptive_name "PlaceOS Desk Bookings Locations" + generic_name :DeskBookings + description %(collects desk booking data from the staff API for visualising on a map) + + accessor area_manager : AreaManagement_1 + accessor staff_api : StaffAPI_1 + + default_settings({ + zone_filter: ["placeos-zone-id"], + + # time in seconds + poll_rate: 60, + booking_type: "desk", + }) + + @zone_filter : Array(String) = [] of String + @poll_rate : Time::Span = 60.seconds + @booking_type : String = "desk" + + def on_load + monitor("staff/booking/changed") do |_subscription, payload| + logger.debug { "received booking changed event #{payload}" } + booking_changed(Booking.from_json(payload)) + end + on_update + end + + def on_update + @zone_filter = setting?(Array(String), :zone_filter) || [] of String + @poll_rate = (setting?(Int32, :poll_rate) || 60).seconds + + @booking_type = setting?(String, :booking_type).presence || "desk" + + map_zones + schedule.clear + schedule.every(@poll_rate) { query_desk_bookings } + schedule.in(5.seconds) { query_desk_bookings } + end + + # =================================== + # Monitoring desk bookings + # =================================== + protected def booking_changed(event) + return unless event.booking_type == @booking_type + matching_zones = @zone_filter & event.zones + return if matching_zones.empty? + + logger.debug { "booking event is in a matching zone" } + + case event.action + when "create" + return unless event.in_progress? + # Check if this event is happening now + logger.debug { "adding new booking" } + @bookings[event.user_email] << event + when "cancelled", "rejected" + # delete the booking from the levels + found = false + @bookings[event.user_email].reject! { |booking| found = true if booking.id == event.id } + return unless found + when "check_in" + return unless event.in_progress? + @bookings[event.user_email].each { |booking| booking.checked_in = true if booking.id == event.id } + when "changed" + # Check if this booking is for today and update as required + @bookings[event.user_email].reject! { |booking| booking.id == event.id } + @bookings[event.user_email] << event if event.in_progress? + else + # ignore the update (approve) + logger.debug { "booking event was ignored" } + return + end + + area_manager.update_available(matching_zones) + end + + # =================================== + # Locatable Interface functions + # =================================== + def locate_user(email : String? = nil, username : String? = nil) + logger.debug { "searching for #{email}, #{username}" } + bookings = @bookings[email]? || [] of Booking + map_bookings(bookings) + end + + def macs_assigned_to(email : String? = nil, username : String? = nil) : Array(String) + logger.debug { "listing MAC addresses assigned to #{email}, #{username}" } + found = [] of String + @known_users.each { |user_id, (user_email, _name)| + found << user_id if email == user_email + } + found + end + + def check_ownership_of(mac_address : String) : OwnershipMAC? + logger.debug { "searching for owner of #{mac_address}" } + if user_details = @known_users[mac_address]? + email, _name = user_details + { + location: "booking", + assigned_to: email, + mac_address: mac_address, + } + end + end + + def device_locations(zone_id : String, location : String? = nil) + logger.debug { "searching devices in zone #{zone_id}" } + bookings = [] of Booking + @bookings.each_value(&.each { |booking| + next unless zone_id.in?(booking.zones) + bookings << booking + }) + map_bookings(bookings) + end + + protected def map_bookings(bookings) + bookings.map do |booking| + level = nil + building = nil + booking.zones.each do |zone_id| + tags = @zone_mappings[zone_id] + level = zone_id if tags.includes? "level" + building = zone_id if tags.includes? "building" + break if level && building + end + + { + location: :booking, + type: @booking_type, + checked_in: booking.checked_in, + asset_id: booking.asset_id, + booking_id: booking.id, + building: building, + level: level, + ends_at: booking.booking_end, + mac: booking.user_id, + staff_email: booking.user_email, + staff_name: booking.user_name, + } + end + end + + # =================================== + # DESK AND ZONE QUERIES + # =================================== + # zone id => tags + @zone_mappings = {} of String => Array(String) + + class ZoneDetails + include JSON::Serializable + property tags : Array(String) + end + + protected def map_zones + @zone_mappings = Hash(String, Array(String)).new do |hash, zone_id| + # Map zones_ids to tags (level, building etc) + hash[zone_id] = staff_api.zone(zone_id).get["tags"].as_a.map(&.as_s) + end + end + + class Booking + include JSON::Serializable + + # This is to support events + property action : String? + + property id : Int64 + property booking_type : String + property booking_start : Int64 + property booking_end : Int64 + property timezone : String? + + # events use resource_id instead of asset_id + property asset_id : String? + property resource_id : String? + + def asset_id : String + (@asset_id || @resource_id).not_nil! + end + + property user_id : String + property user_email : String + property user_name : String + + property zones : Array(String) + + property checked_in : Bool? + property rejected : Bool? + + def in_progress? + now = Time.utc.to_unix + now >= @booking_start && now < @booking_end + end + end + + # Email => Array of bookings + @bookings : Hash(String, Array(Booking)) = Hash(String, Array(Booking)).new + + # UserID => {Email, Name} + @known_users : Hash(String, Tuple(String, String)) = Hash(String, Tuple(String, String)).new + + def query_desk_bookings : Nil + bookings = [] of JSON::Any + @zone_filter.each { |zone| bookings.concat staff_api.query_bookings(type: @booking_type, zones: {zone}).get.as_a } + bookings = bookings.map { |booking| Booking.from_json(booking.to_json) } + + logger.debug { "queried desk bookings, found #{bookings.size}" } + + new_bookings = Hash(String, Array(Booking)).new do |hash, key| + hash[key] = [] of Booking + end + + bookings.each do |booking| + next if booking.rejected + new_bookings[booking.user_email] << booking + @known_users[booking.user_id] = {booking.user_email, booking.user_name} + end + + @bookings = new_bookings + end +end diff --git a/drivers/place/desk_bookings_locations_spec.cr b/drivers/place/desk_bookings_locations_spec.cr new file mode 100644 index 00000000000..f99d1459de2 --- /dev/null +++ b/drivers/place/desk_bookings_locations_spec.cr @@ -0,0 +1,77 @@ +DriverSpecs.mock_driver "Place::DeskBookingsLocations" do + system({ + StaffAPI: {StaffAPI}, + AreaManagement: {AreaManagement}, + }) + + now = Time.local + start = now.at_beginning_of_day.to_unix + ending = now.at_end_of_day.to_unix + + exec(:query_desk_bookings).get + resp = exec(:device_locations, "placeos-zone-id").get + puts resp + resp.should eq([ + {"location" => "booking", "checked_in" => true, "asset_id" => "desk-123", "booking_id" => 1, "building" => "zone-building", "level" => "placeos-zone-id", "ends_at" => 1610110799, "mac" => "user-1234", "staff_email" => "user1234@org.com", "staff_name" => "Bob Jane"}, + {"location" => "booking", "checked_in" => false, "asset_id" => "desk-456", "booking_id" => 2, "building" => "zone-building", "level" => "placeos-zone-id", "ends_at" => 1610110799, "mac" => "user-456", "staff_email" => "zdoo@org.com", "staff_name" => "Zee Doo"}, + ]) +end + +class StaffAPI < DriverSpecs::MockDriver + def query_bookings(type : String, zones : Array(String)) + logger.debug { "Querying desk bookings!" } + + now = Time.local + start = now.at_beginning_of_day.to_unix + ending = now.at_end_of_day.to_unix + [{ + id: 1, + booking_type: type, + booking_start: start, + booking_end: ending, + asset_id: "desk-123", + user_id: "user-1234", + user_email: "user1234@org.com", + user_name: "Bob Jane", + zones: zones + ["zone-building"], + checked_in: true, + rejected: false, + }, + { + id: 2, + booking_type: type, + booking_start: start, + booking_end: ending, + asset_id: "desk-456", + user_id: "user-456", + user_email: "zdoo@org.com", + user_name: "Zee Doo", + zones: zones + ["zone-building"], + checked_in: false, + rejected: false, + }] + end + + def zone(zone_id : String) + logger.info { "requesting zone #{zone_id}" } + + if zone_id == "placeos-zone-id" + { + id: zone_id, + tags: ["level"], + } + else + { + id: zone_id, + tags: ["building"], + } + end + end +end + +class AreaManagement < DriverSpecs::MockDriver + def update_available(zones : Array(String)) + logger.info { "requested update to #{zones}" } + nil + end +end diff --git a/drivers/place/event_scrape.cr b/drivers/place/event_scrape.cr new file mode 100644 index 00000000000..957174518bf --- /dev/null +++ b/drivers/place/event_scrape.cr @@ -0,0 +1,84 @@ +require "place_calendar" + +class Place::EventScrape < PlaceOS::Driver + descriptive_name "PlaceOS Event Scrape" + generic_name :EventScrape + + default_settings({ + zone_ids: ["placeos-zone-id"], + internal_domains: ["PlaceOS.com"] + }) + + accessor staff_api : StaffAPI_1 + + @zone_ids = [] of String + @internal_domains = [] of String + + alias Event = PlaceCalendar::Event + + struct SystemWithEvents + include JSON::Serializable + + def initialize(@name : String, @zones : Array(String), @events : Array(Event)) + end + end + + def on_load + on_update + end + + def on_update + @zone_ids = setting?(Array(String), :zone_ids) || [] of String + @internal_domains = setting?(Array(String), :internal_domains) || [] of String + end + + def todays_bookings + response = { + internal_domains: @internal_domains, + systems: {} of String => SystemWithEvents + } + + now = Time.local + start_epoch = now.at_beginning_of_day.to_unix + end_epoch = now.at_end_of_day.to_unix + + @zone_ids.each do |z_id| + staff_api.systems(zone_id: z_id).get.as_a.each do |sys| + sys_id = sys["id"].as_s + # In case the same system is in multiple zones + next if response[:systems][sys_id]? + + response[:systems][sys_id] = SystemWithEvents.new( + name: sys["name"].as_s, + zones: Array(String).from_json(sys["zones"].to_json), + events: get_system_bookings(sys_id, start_epoch, end_epoch) + ) + end + end + + response + end + + def get_system_bookings(sys_id : String, start_epoch : Int64?, end_epoch : Int64?) : Array(Event) + booking_module = staff_api.modules_from_system(sys_id).get.as_a.find { |mod| mod["name"] == "Bookings" } + # If the system has a booking module with bookings + if booking_module && (bookings = staff_api.get_module_state(booking_module["id"].as_s).get["bookings"]?) + bookings = Array(Event).from_json(bookings.as_s) + + # If both start_epoch and end_epoch are passed + if start_epoch && end_epoch + # Convert start/end_epoch to Time object as Event.event_start.class == Time + start_time = Time.unix(start_epoch) + end_time = Time.unix(end_epoch) + range = (start_time..end_time) + # Only select bookings within start_epoch and end_epoch + bookings.select! { |b| range.includes?(b.event_start) } + bookings + end + + bookings + else + [] of Event + end + end +end diff --git a/drivers/place/event_scrape_spec.cr b/drivers/place/event_scrape_spec.cr new file mode 100644 index 00000000000..c59793aeb34 --- /dev/null +++ b/drivers/place/event_scrape_spec.cr @@ -0,0 +1,68 @@ +DriverSpecs.mock_driver "Place::EventScrape" do + system({ + StaffAPI: {StaffAPI} + }) + + exec(:get_bookings) +end + +class StaffAPI < DriverSpecs::MockDriver + def systems(zone_id : String) + logger.info { "requesting zone #{zone_id}" } + + sys_1 = { + id: "sys-1", + name: "Room 1", + zones: ["placeos-zone-id"] + } + + if zone_id == "placeos-zone-id" + [sys_1] + else + [ + sys_1, + { + id: "sys-2", + name: "Room 2", + zones: ["zone-1"] + } + ] + end + end + + def modules_from_system(system_id : String) + [ + { + id: "mod-1", + control_system_id: system_id, + name: "Calendar" + }, + { + id: "mod-2", + control_system_id: system_id, + name: "Bookings" + }, + { + id: "mod-3", + control_system_id: system_id, + name: "Bookings" + } + ] + end + + def get_module_state(module_id : String, lookup : String? = nil) + now = Time.local + start = now.at_beginning_of_day.to_unix + ending = now.at_end_of_day.to_unix + + { + bookings: [{ + event_start: start, + event_end: ending, + id: "booking-1", + host: "testroom1@booking.demo.acaengine.com", + title: "Test in #{module_id}" + }].to_json + } + end +end diff --git a/drivers/place/location_services.cr b/drivers/place/location_services.cr new file mode 100644 index 00000000000..fc99672a431 --- /dev/null +++ b/drivers/place/location_services.cr @@ -0,0 +1,152 @@ +module Place; end + +require "json" +require "placeos-driver/interface/locatable" + +class Place::LocationServices < PlaceOS::Driver + descriptive_name "PlaceOS Location Services" + generic_name :LocationServices + description %(collects location data from compatible services and combines the data) + + default_settings({ + debug_webhook: false, + + # various groups of people one might be interested in contacting + emergency_contacts: { + "Fire Wardens" => "5542c9f-eaa7-4e74", + "First Aid" => "ed9f7608-488f-aeef", + }, + }) + + def on_load + on_update + end + + @debug_webhook : Bool = false + @emergency_contacts : Hash(String, String) = {} of String => String + + def on_update + @debug_webhook = setting?(Bool, :debug_webhook) || false + @emergency_contacts = setting?(Hash(String, String), :emergency_contacts) || Hash(String, String).new + + if !@emergency_contacts.empty? + schedule.clear + schedule.every(6.hours, immediate: true) { update_contacts_list } + end + end + + # Runs through all the services that support the Locatable interface + # requests location information on the identifier for all of them + # concatenates the results and returns them as a single array + def locate_user(email : String? = nil, username : String? = nil) + logger.debug { "searching for #{email}, #{username}" } + located = [] of JSON::Any + system.implementing(Interface::Locatable).locate_user(email, username).get.each do |locations| + located.concat locations.as_a + end + located + end + + # Will return an array of MAC address strings + # lowercase with no seperation characters abcdeffd1234 etc + def macs_assigned_to(email : String? = nil, username : String? = nil) + logger.debug { "listing MAC addresses assigned to #{email}, #{username}" } + macs = [] of String + system.implementing(Interface::Locatable).macs_assigned_to(email, username).get.each do |found| + macs.concat found.as_a.map(&.as_s) + end + macs + end + + # Will return `nil` or `{"location": "wireless", "assigned_to": "bob123", "mac_address": "abcd"}` + def check_ownership_of(mac_address : String) + logger.debug { "searching for owner of #{mac_address}" } + owner = nil + system.implementing(Interface::Locatable).check_ownership_of(mac_address).get.each do |result| + if result != nil + owner = result + break + end + end + owner + end + + # Will return an array of devices and their x, y coordinates + def device_locations(zone_id : String, location : String? = nil) + logger.debug { "searching devices in zone #{zone_id}" } + located = [] of JSON::Any + system.implementing(Interface::Locatable).device_locations(zone_id, location).get.each do |locations| + located.concat locations.as_a + end + located + end + + # =============================== + # IP ADDRESS => MAC ADDRESS + # =============================== + SUCCESS_RESPONSE = {HTTP::Status::OK, {} of String => String, nil} + + # Webhook handler for accepting IP address to username mappings + # This data is typically obtained via domain controller logs + def ip_mappings(method : String, headers : Hash(String, Array(String)), body : String) + logger.debug { "IP mappings webhook received: #{method},\nheaders #{headers},\nbody size #{body.size}" } + logger.debug { body } if @debug_webhook + + # ip, username, domain, hostname + ip_map = Array(Tuple(String, String, String, String?)).from_json(body) + system.implementing(Interface::Locatable).ip_username_mappings(ip_map) + + SUCCESS_RESPONSE + end + + def mac_address_mappings(method : String, headers : Hash(String, Array(String)), body : String) + logger.debug { "MAC mappings webhook received: #{method},\nheaders #{headers},\nbody size #{body.size}" } + logger.debug { body } if @debug_webhook + + # username, macs, domain + username, macs, domain = Tuple(String, Array(String), String?).from_json(body) + username = username.strip + macs = macs.compact_map do |mac| + mac = mac.strip.gsub(/(0x|[^0-9A-Fa-f])*/, "").downcase + mac if mac.size == 12 + end + return {HTTP::Status::NOT_ACCEPTABLE, {} of String => String, nil} if username.empty? || macs.empty? + + system.implementing(Interface::Locatable).mac_address_mappings(username, macs, domain) + + SUCCESS_RESPONSE + end + + @[Security(Level::Support)] + def update_contacts_list + if @emergency_contacts.empty? + self[:emergency_contacts] = nil + return + end + + if !system.exists?(:Calendar) + logger.warn { "contacts requested however no directory service available" } + return + end + + directory = system[:Calendar] + self[:emergency_contacts] = @emergency_contacts.transform_values { |id| + directory.get_members(id).get.as(JSON::Any) + } + end + + # locates all the of the emergency contacts + def locate_contacts(list_name : String) + contacts = status(Hash(String, Array(NamedTuple( + email: String, + username: String))), :emergency_contacts) + + list = contacts[list_name] + results = {} of String => Array(JSON::Any) + list.each do |person| + email = person[:email] + results[email] = locate_user(email, person[:username]) + end + results + end +end diff --git a/drivers/place/location_services_spec.cr b/drivers/place/location_services_spec.cr new file mode 100644 index 00000000000..fe323d2cfcb --- /dev/null +++ b/drivers/place/location_services_spec.cr @@ -0,0 +1,85 @@ +module Place; end + +require "placeos-driver/interface/locatable" + +WIRELESS_LOC = { + "location" => "wireless", + "coordinates_from" => "bottom-left", + "x" => 16.764784482481577, + "y" => 25.435735950388988, + "lng" => 55.274935030154325, + "lat" => 25.201036346211698, + "variance" => 7.944837533996209, + "last_seen" => 1601526474, + "building" => "zone_1234", + "level" => "zone_1234", +} + +DESK_LOC = { + "location" => "desk", + "at_location" => true, + "map_id" => "desk-4-1006", + "building" => "zone_1234", + "level" => "zone_1234", +} + +DriverSpecs.mock_driver "Place::LocationServices" do + system({ + Dashboard: {WirelessLocation}, + DeskManagement: {DeskLocation}, + }) + + exec(:locate_user, "Steve").get.should eq([WIRELESS_LOC, DESK_LOC]) +end + +class WirelessLocation < DriverSpecs::MockDriver + include PlaceOS::Driver::Interface::Locatable + + def locate_user(email : String? = nil, username : String? = nil) + [WIRELESS_LOC] + end + + def macs_assigned_to(email : String? = nil, username : String? = nil) : Array(String) + [] of String + end + + alias OwnershipMAC = NamedTuple( + location: String, + assigned_to: String, + mac_address: String, + ) + + def check_ownership_of(mac_address : String) : OwnershipMAC? + nil + end + + def device_locations(zone_id : String, location : String? = nil) + nil + end +end + +class DeskLocation < DriverSpecs::MockDriver + include PlaceOS::Driver::Interface::Locatable + + def locate_user(email : String? = nil, username : String? = nil) + [DESK_LOC] + end + + def macs_assigned_to(email : String? = nil, username : String? = nil) : Array(String) + [] of String + end + + alias OwnershipMAC = NamedTuple( + location: String, + assigned_to: String, + mac_address: String, + ) + + def check_ownership_of(mac_address : String) : OwnershipMAC? + nil + end + + def device_locations(zone_id : String, location : String? = nil) + nil + end +end diff --git a/drivers/place/logic_example.cr b/drivers/place/logic_example.cr new file mode 100644 index 00000000000..62eadaebcb8 --- /dev/null +++ b/drivers/place/logic_example.cr @@ -0,0 +1,24 @@ +module Place; end + +class Place::LogicExample < PlaceOS::Driver + descriptive_name "Example Logic" + generic_name :ExampleLogic + + accessor main_lcd : Display_1, implementing: Powerable + + def on_update + logger.info { "woot! an update #{setting?(String, :name)}" } + end + + def power_state? + main_lcd[:power] + end + + def power(state : Bool) + main_lcd.power(state) + end + + def display_count + system.count(:Display) + end +end diff --git a/drivers/place/logic_example_spec.cr b/drivers/place/logic_example_spec.cr new file mode 100644 index 00000000000..0f855966b3b --- /dev/null +++ b/drivers/place/logic_example_spec.cr @@ -0,0 +1,90 @@ +require "placeos-driver/interface/muteable" +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/switchable" + +class Display < DriverSpecs::MockDriver + include PlaceOS::Driver::Interface::Powerable + include PlaceOS::Driver::Interface::Muteable + + enum Inputs + HDMI + HDMI2 + VGA + VGA2 + Miracast + DVI + DisplayPort + HDBaseT + Composite + end + + include PlaceOS::Driver::Interface::InputSelection(Inputs) + + # Configure initial state in on_load + def on_load + self[:power] = false + self[:input] = Inputs::HDMI + end + + # implement the abstract methods required by the interfaces + def power(state : Bool) + self[:power] = state + end + + def switch_to(input : Inputs) + mute(false) + self[:input] = input + end + + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + self[:mute] = state + self[:mute0] = state + end +end + +class Switcher < DriverSpecs::MockDriver + include PlaceOS::Driver::Interface::InputSelection(Int32) + + def switch_to(input : Input) + self[:output] = input + end +end + +DriverSpecs.mock_driver "Place::LogicExample" do + system({ + Display: {Display, Display}, + Switcher: {Switcher}, + }) + + exec(:power_state?).get.should eq(false) + + # Should allow updating of settings + settings({ + name: "Steve", + }) + + # Updating emulated module state + system(:Display_1)[:power] = true + exec(:power_state?).get.should eq(true) + + # Expecting a function call + exec(:power, false) + exec(:power_state?).get.should eq(false) + system(:Display_1)[:power].should eq(false) + + # Expecting a function call to return a result + exec(:power, true).get.should eq(true) + + exec(:display_count).get.should eq(2) + + system({ + Display: {Display}, + Switcher: {Switcher}, + }) + + exec(:display_count).get.should eq(1) +end diff --git a/drivers/place/pinger.cr b/drivers/place/pinger.cr new file mode 100644 index 00000000000..32f14202a00 --- /dev/null +++ b/drivers/place/pinger.cr @@ -0,0 +1,43 @@ +module Place; end + +require "pinger" + +class Place::Pinger < PlaceOS::Driver + descriptive_name "Device Pinger" + generic_name :Ping + + # Discard port + udp_port 9 + description %(periodically pings a device) + + default_settings({ + ping_every: 60, + }) + + def on_load + on_update + end + + def on_update + # Use quite a large random value to spread load + period = setting?(Int32, :ping_every) || 60 + period = period * 1000 + rand(1000) + + schedule.clear + schedule.every(period.milliseconds) { ping } + end + + def ping + hostname = config.ip.not_nil! + pinger = ::Pinger.new(hostname, count: 3) + pinger.ping + + pingable = pinger.pingable + if !pingable + self[:last_error] = pinger.exception || pinger.warning || "unknown error" + end + + set_connected_state pingable + self[:pingable] = pingable + end +end diff --git a/drivers/place/pinger_spec.cr b/drivers/place/pinger_spec.cr new file mode 100644 index 00000000000..afb2984d37e --- /dev/null +++ b/drivers/place/pinger_spec.cr @@ -0,0 +1,4 @@ +DriverSpecs.mock_driver "Place::Pinger" do + exec(:ping).get.should eq(true) + status[:pingable].should eq(true) +end diff --git a/drivers/place/smtp.cr b/drivers/place/smtp.cr new file mode 100644 index 00000000000..568995b30e4 --- /dev/null +++ b/drivers/place/smtp.cr @@ -0,0 +1,156 @@ +require "qr-code" +require "qr-code/export/png" +require "base64" +require "email" +require "uri" + +require "placeos-driver/interface/mailer" + +class Place::Smtp < PlaceOS::Driver + include PlaceOS::Driver::Interface::Mailer + + descriptive_name "SMTP Mailer" + generic_name :Mailer + uri_base "https://smtp.host.com" + description %(sends emails via SMTP) + + default_settings({ + sender: "support@place.tech", + # host: "smtp.host", + # port: 587, + tls_mode: EMail::Client::TLSMode::STARTTLS.to_s, + username: "", # Username/Password for SMTP servers with basic authorization + password: "", + + email_templates: {visitor: {checkin: { + subject: "%{name} has arrived", + text: "for your meeting at %{time}", + }}}, + }) + + private def smtp_client : EMail::Client + @smtp_client ||= new_smtp_client + end + + @smtp_client : EMail::Client? + + @sender : String = "support@place.tech" + @username : String = "" + @password : String = "" + @host : String = "smtp.host" + @port : Int32 = 587 + @tls_mode : EMail::Client::TLSMode = EMail::Client::TLSMode::STARTTLS + @send_lock : Mutex = Mutex.new + + def on_load + on_update + end + + def on_update + defaults = URI.parse(config.uri.not_nil!) + tls_mode = if scheme = defaults.scheme + scheme.ends_with?('s') ? EMail::Client::TLSMode::SMTPS : EMail::Client::TLSMode::STARTTLS + else + EMail::Client::TLSMode::STARTTLS + end + port = defaults.port || 587 + host = defaults.host || "smtp.host" + + @username = setting?(String, :username) || "" + @password = setting?(String, :password) || "" + @sender = setting?(String, :sender) || "support@place.tech" + @host = setting?(String, :host) || host + @port = setting?(Int32, :port) || port + @tls_mode = setting?(EMail::Client::TLSMode, :tls_mode) || tls_mode + + @smtp_client = new_smtp_client + + @templates = setting?(Templates, :email_templates) || Templates.new + end + + # Create and configure an SMTP client + private def new_smtp_client + email_config = EMail::Client::Config.new(@host, @port) + email_config.log = logger + email_config.client_name = "PlaceOS" + + unless @username.empty? || @password.empty? + email_config.use_auth(@username, @password) + end + + email_config.use_tls(@tls_mode) + + EMail::Client.new(email_config) + end + + def generate_svg_qrcode(text : String) : String + QRCode.new(text).as_svg + end + + def generate_png_qrcode(text : String, size : Int32 = 128) : String + Base64.strict_encode QRCode.new(text).as_png(size: size) + end + + def send_mail( + to : String | Array(String), + subject : String, + message_plaintext : String? = nil, + message_html : String? = nil, + resource_attachments : Array(ResourceAttachment) = [] of ResourceAttachment, + attachments : Array(Attachment) = [] of Attachment, + cc : String | Array(String) = [] of String, + bcc : String | Array(String) = [] of String, + from : String | Array(String) | Nil = nil + ) : Bool + to = {to} unless to.is_a?(Array) + + from = {from} unless from.nil? || from.is_a?(Array) + cc = {cc} unless cc.nil? || cc.is_a?(Array) + bcc = {bcc} unless bcc.nil? || bcc.is_a?(Array) + + message = EMail::Message.new + + message.subject(subject) + + message.sender(@sender) + + if from.nil? || from.empty? + message.from(@sender) + else + from.each { |_from| message.from(_from) } + end + + to.each { |_to| message.to(_to) } + bcc.each { |_bcc| message.bcc(_bcc) } + cc.each { |_cc| message.cc(_cc) } + + message.message(message_plaintext.as(String)) unless message_plaintext.presence.nil? + message.message_html(message_html.as(String)) unless message_html.presence.nil? + + # Traverse all attachments + {resource_attachments, attachments}.map(&.each).each.flatten.each do |attachment| + # Base64 decode to memory, then attach to email + attachment_io = IO::Memory.new + Base64.decode(attachment[:content], attachment_io) + attachment_io.rewind + + case attachment + in Attachment + message.attach(io: attachment_io, file_name: attachment[:file_name]) + in ResourceAttachment + message.message_resource(io: attachment_io, file_name: attachment[:file_name], cid: attachment[:content_id]) + end + end + + sent = false + + # Ensure only a single send at a time + @send_lock.synchronize do + smtp_client.start do + sent = send(message) + end + end + + sent + end +end diff --git a/drivers/place/smtp_spec.cr b/drivers/place/smtp_spec.cr new file mode 100644 index 00000000000..2d471eb074f --- /dev/null +++ b/drivers/place/smtp_spec.cr @@ -0,0 +1,40 @@ +require "email" + +# for local testing use: http://nilhcem.com/FakeSMTP/download.html + +DriverSpecs.mock_driver "Place::Smtp" do + settings({ + sender: "support@place.tech", + host: ENV["PLACE_SMTP_HOST"]? || "localhost", + port: ENV["PLACE_SMTP_PORT"]?.try(&.to_i) || 25, + username: ENV["PLACE_SMTP_USER"]? || "", # Username/Password for SMTP servers with basic authorization + password: ENV["PLACE_SMTP_PASS"]? || "", + tls_mode: ENV["PLACE_SMTP_MODE"]? || "none", + + email_templates: {visitor: {checkin: { + subject: "%{name} has arrived", + text: "for your meeting at %{time}", + }}}, + }) + + response = exec( + :send_mail, + subject: "Test Email", + to: ENV["PLACE_TEST_EMAIL"]? || "support@place.tech", + message_plaintext: "Hello!", + ).get + + response.should be_true + + response = exec( + :send_template, + to: "steve@place.tech", + template: {"visitor", "checkin"}, + args: { + name: "Bob", + time: "1:30pm", + } + ).get + + response.should be_true +end diff --git a/drivers/place/spec_helper.cr b/drivers/place/spec_helper.cr new file mode 100644 index 00000000000..113fcb6b303 --- /dev/null +++ b/drivers/place/spec_helper.cr @@ -0,0 +1,7 @@ +module Place; end + +class Place::SpecHelper < PlaceOS::Driver + def implemented_in_driver + "woot!" + end +end diff --git a/drivers/place/staff_api.cr b/drivers/place/staff_api.cr new file mode 100644 index 00000000000..8206b940866 --- /dev/null +++ b/drivers/place/staff_api.cr @@ -0,0 +1,432 @@ +module Place; end + +require "json" +require "oauth2" +require "placeos" + +class Place::StaffAPI < PlaceOS::Driver + descriptive_name "PlaceOS Staff API" + generic_name :StaffAPI + description %(helpers for requesting data held in the staff API) + + # The PlaceOS API + uri_base "https://staff.app.api.com" + + default_settings({ + # PlaceOS API creds, so we can query the zone metadata + username: "", + password: "", + client_id: "", + redirect_uri: "", + }) + + @place_domain : URI = URI.parse("https://staff.app.api.com") + @username : String = "" + @password : String = "" + @client_id : String = "" + @redirect_uri : String = "" + + @running_a_spec : Bool = false + + def on_load + on_update + end + + def on_update + # we use the Place Client to query the desk booking data + @username = setting(String, :username) + @password = setting(String, :password) + @client_id = setting(String, :client_id) + @redirect_uri = setting(String, :redirect_uri) + @place_domain = URI.parse(config.uri.not_nil!) + + @running_a_spec = setting?(Bool, :running_a_spec) || false + end + + def get_system(id : String) + response = get("/api/engine/v2/systems/#{id}", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + + raise "unexpected response for system id #{id}: #{response.status_code}\n#{response.body}" unless response.success? + + begin + JSON.parse(response.body) + rescue error + logger.debug { "issue parsing system #{id}:\n#{response.body.inspect}" } + raise error + end + end + + def systems(q : String? = nil, + limit : Int32 = 1000, + offset : Int32 = 0, + zone_id : String? = nil, + module_id : String? = nil, + features : String? = nil, + capacity : Int32? = nil, + bookable : Bool? = nil) + placeos_client.systems.search( + q: q, + limit: limit, + offset: offset, + zone_id: zone_id, + module_id: module_id, + features: features, + capacity: capacity, + bookable: bookable + ) + end + + # Staff details returns the information from AD + def staff_details(email : String) + response = get("/api/staff/v1/people/#{email}", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + + raise "unexpected response for stafff #{email}: #{response.status_code}\n#{response.body}" unless response.success? + + begin + JSON.parse(response.body) + rescue error + logger.debug { "issue parsing staff #{email}:\n#{response.body.inspect}" } + raise error + end + end + + # =================================== + # User details + # =================================== + def user(id : String) + placeos_client.users.fetch(id) + end + + @[Security(Level::Support)] + def update_user(id : String, body_json : String) : Nil + response = patch("/api/engine/v2/users/#{id}", body: body_json, headers: { + "Accept" => "application/json", + "Content-Type" => "application/json", + "Authorization" => "Bearer #{token}", + }) + + raise "failed to update groups for #{id}: #{response.status_code}" unless response.success? + end + + @[Security(Level::Support)] + def resource_token + response = post("/api/engine/v2/users/resource_token", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + + raise "unexpected response #{response.status_code}\n#{response.body}" unless response.success? + + begin + JSON.parse(response.body) + rescue error + logger.debug { "issue parsing:\n#{response.body.inspect}" } + raise error + end + end + + # =================================== + # Guest details + # =================================== + @[Security(Level::Support)] + def guest_details(guest_id : String) + response = get("/api/staff/v1/guests/#{guest_id}", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + + raise "unexpected response #{response.status_code}\n#{response.body}" unless response.success? + + begin + JSON.parse(response.body) + rescue error + logger.debug { "issue parsing:\n#{response.body.inspect}" } + raise error + end + end + + # =================================== + # ZONE METADATA + # =================================== + def metadata(id : String, key : String? = nil) + placeos_client.metadata.fetch(id, key) + end + + def metadata_children(id : String, key : String? = nil) + placeos_client.metadata.children(id, key) + end + + # =================================== + # ZONE INFORMATION + # =================================== + def zone(zone_id : String) + placeos_client.zones.fetch zone_id + end + + def zones(q : String? = nil, + limit : Int32 = 1000, + offset : Int32 = 0, + parent : String? = nil, + tags : Array(String) | String? = nil) + placeos_client.zones.search( + q: q, + limit: limit, + offset: offset, + parent: parent, + tags: tags + ) + end + + # =================================== + # MODULE INFORMATION + # =================================== + def module(module_id : String) + response = get("/api/engine/v2/modules/#{module_id}", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + + raise "unexpected response for module id #{module_id}: #{response.status_code}\n#{response.body}" unless response.success? + + begin + JSON.parse(response.body) + rescue error + logger.debug { "issue parsing module #{module_id}:\n#{response.body.inspect}" } + raise error + end + end + + def modules_from_system(system_id : String) + response = get("/api/engine/v2/modules?control_system_id=#{system_id}", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + + raise "unexpected response for modules for #{system_id}: #{response.status_code}\n#{response.body}" unless response.success? + + begin + JSON.parse(response.body) + rescue error + logger.debug { "issue getting modules for #{system_id}:\n#{response.body.inspect}" } + raise error + end + end + + def get_module_state(module_id : String, lookup : String? = nil) + placeos_client.modules.state(module_id, lookup) + end + + # TODO: figure out why these 2 methods don't work + # def module(module_id : String) + # placeos_client.modules.fetch module_id + # end + + # def modules(q : String? = nil, + # limit : Int32 = 20, + # offset : Int32 = 0, + # control_system_id : String? = nil, + # driver_id : String? = nil) + # placeos_client.modules.search( + # q: q, + # limit: limit, + # offset: offset, + # control_system_id: control_system_id, + # driver_id: driver_id + # ) + # end + + # =================================== + # BOOKINGS ACTIONS + # =================================== + @[Security(Level::Support)] + def reject(booking_id : String | Int64) + logger.debug { "rejecting booking #{booking_id}" } + response = post("/api/staff/v1/bookings/#{booking_id}/reject", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + raise "issue rejecting booking #{booking_id}: #{response.status_code}" unless response.success? + true + end + + @[Security(Level::Support)] + def approve(booking_id : String | Int64) + logger.debug { "approving booking #{booking_id}" } + response = post("/api/staff/v1/bookings/#{booking_id}/approve", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + raise "issue approving booking #{booking_id}: #{response.status_code}" unless response.success? + true + end + + @[Security(Level::Support)] + def booking_state(booking_id : String | Int64, state : String) + logger.debug { "updating booking #{booking_id} state to: #{state}" } + response = post("/api/staff/v1/bookings/#{booking_id}/update_state?state=#{state}", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + raise "issue updating booking state #{booking_id}: #{response.status_code}" unless response.success? + true + end + + @[Security(Level::Support)] + def booking_check_in(booking_id : String | Int64, state : Bool = true) + logger.debug { "checking in booking #{booking_id} to: #{state}" } + response = post("/api/staff/v1/bookings/#{booking_id}/check_in?state=#{state}", headers: { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + raise "issue checking in booking #{booking_id}: #{response.status_code}" unless response.success? + true + end + + # =================================== + # BOOKINGS QUERY + # =================================== + class Booking + include JSON::Serializable + + property id : Int64 + + property user_id : String + property user_email : String + property user_name : String + property asset_id : String + property zones : Array(String) + property booking_type : String + + property booking_start : Int64 + property booking_end : Int64 + + property timezone : String? + property title : String? + property description : String? + + property checked_in : Bool + property rejected : Bool + property approved : Bool + + property approver_id : String? + property approver_email : String? + property approver_name : String? + + property booked_by_id : String + property booked_by_email : String + property booked_by_name : String + + property process_state : String? + property last_changed : Int64? + property created : Int64? + end + + def query_bookings( + type : String, + period_start : Int64? = nil, + period_end : Int64? = nil, + zones : Array(String) = [] of String, + user : String? = nil, + email : String? = nil, + state : String? = nil, + created_before : Int64? = nil, + created_after : Int64? = nil, + approved : Bool? = nil, + rejected : Bool? = nil + ) + # Assumes occuring now + period_start ||= Time.utc.to_unix + period_end ||= 30.minutes.from_now.to_unix + + params = { + "period_start" => period_start.to_s, + "period_end" => period_end.to_s, + "type" => type, + } + params["zones"] = zones.join(",") unless zones.empty? + params["user"] = user if user && !user.empty? + params["email"] = email if email && !email.empty? + params["state"] = state if state && !state.empty? + params["created_before"] = created_before.to_s if created_before + params["created_after"] = created_after.to_s if created_after + params["approved"] = approved.to_s unless approved.nil? + params["rejected"] = rejected.to_s unless rejected.nil? + + # Get the existing bookings from the API to check if there is space + response = get("/api/staff/v1/bookings", params, { + "Accept" => "application/json", + "Authorization" => "Bearer #{token}", + }) + raise "issue loading list of bookings (zones #{zones}): #{response.status_code}" unless response.success? + + # Just parse it here instead of using the Bookings object + # it will be parsed into an object on the far end + JSON.parse(response.body) + end + + # For accessing PlaceOS APIs + protected def placeos_client : PlaceOS::Client + PlaceOS::Client.new( + @place_domain, + token: OAuth2::AccessToken::Bearer.new(token, nil) + ) + end + + # =================================== + # PLACEOS AUTHENTICATION: + # =================================== + @access_token : String = "" + @access_expires : Time = Time.unix(0) + + protected def authenticate : String + uri = @place_domain + host = uri.port ? "#{uri.host}:#{uri.port}" : uri.host.not_nil! + origin = "#{uri.scheme}://#{host}" + + # Create oauth client, optionally pass custom URIs if needed, + # if the authorize or token URIs are not the standard ones + # (they can also be absolute URLs) + oauth2_client = OAuth2::Client.new(host, @client_id, "", + redirect_uri: @redirect_uri, + authorize_uri: "#{origin}/auth/oauth/authorize", + token_uri: "#{origin}/auth/oauth/token") + + access_token = oauth2_client.get_access_token_using_resource_owner_credentials( + @username, + @password, + "public" + ).as(OAuth2::AccessToken::Bearer) + + @access_expires = (access_token.expires_in.not_nil! - 300).seconds.from_now + @access_token = access_token.access_token + end + + protected def token : String + # Don't perform OAuth if we are testing the driver + return "spec-test" if @running_a_spec + return @access_token if valid_token? + authenticate + end + + protected def valid_token? + Time.utc < @access_expires + end +end + +# Deal with bad SSL certificate +class OpenSSL::SSL::Context::Client + def initialize(method : LibSSL::SSLMethod = Context.default_method) + super(method) + + self.verify_mode = OpenSSL::SSL::VerifyMode::NONE + {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.2") >= 0 %} + self.default_verify_param = "ssl_server" + {% end %} + end +end diff --git a/drivers/place/staff_api_spec.cr b/drivers/place/staff_api_spec.cr new file mode 100644 index 00000000000..7a5930f3f67 --- /dev/null +++ b/drivers/place/staff_api_spec.cr @@ -0,0 +1,53 @@ +DriverSpecs.mock_driver "Place::StaffAPI" do + settings({ + # PlaceOS API creds, so we can query the zone metadata + username: "", + password: "", + client_id: "", + redirect_uri: "", + + running_a_spec: true, + }) + + resp = exec(:query_bookings, "desk") + + expect_http_request do |request, response| + headers = request.headers + if headers["Authorization"]? == "Bearer spec-test" + response.status_code = 200 + response << %([{ + "id": 1234, + "user_id": "user-12345", + "user_email": "steve@place.tech", + "user_name": "Steve T", + "asset_id": "desk-2-12", + "zones": ["zone-build1", "zone-level2"], + "booking_type": "Steve T", + "booking_start": 123456, + "booking_end": 12345678, + "timezone": "Australia/Sydney", + "checked_in": true, + "rejected": false, + "approved": false + }]) + else + response.status_code = 401 + end + end + + resp.get.should eq(JSON.parse(%([{ + "id": 1234, + "user_id": "user-12345", + "user_email": "steve@place.tech", + "user_name": "Steve T", + "asset_id": "desk-2-12", + "zones": ["zone-build1", "zone-level2"], + "booking_type": "Steve T", + "booking_start": 123456, + "booking_end": 12345678, + "timezone": "Australia/Sydney", + "checked_in": true, + "rejected": false, + "approved": false + }]))) +end diff --git a/drivers/place/user_group_mappings.cr b/drivers/place/user_group_mappings.cr new file mode 100644 index 00000000000..2e110bbb309 --- /dev/null +++ b/drivers/place/user_group_mappings.cr @@ -0,0 +1,112 @@ +module Place; end + +class Place::UserGroupMappings < PlaceOS::Driver + descriptive_name "User Group Mappings" + generic_name :UserGroupMappings + description "monitors user logins and maps relevent groups to the local user profile" + + accessor staff_api : StaffAPI_1 + accessor calendar_api : Calendar_1 + + default_settings({ + # ID => place_name + group_mappings: { + "group_id" => { + place_id: "manager", + description: "managers of the level2 building", + }, + "group2_id" => { + place_id: "boss", + description: "people that can access everything", + }, + }, + + # Group name prefix => group mappings + group_prefix: { + "group_name_prefix_" => { + strip_prefix: false, + place_id: "optional-place-id", + }, + }, + }) + + class UserLogin + include JSON::Serializable + + property user_id : String + property provider : String + end + + def on_load + monitor("auth/login") { |_subscription, payload| new_user_login(payload) } + on_update + end + + alias Mapping = NamedTuple(place_id: String) + alias Prefix = NamedTuple(strip_prefix: Bool?, place_id: String?) + + @group_mappings : Hash(String, Mapping) = {} of String => Mapping + @group_prefixes : Hash(String, Prefix) = {} of String => Prefix + @users_checked : UInt64 = 0_u64 + @error_count : UInt64 = 0_u64 + + def on_update + @group_mappings = setting?(Hash(String, Mapping), :group_mappings) || {} of String => Mapping + @group_prefixes = setting?(Hash(String, Prefix), :group_prefix) || {} of String => Prefix + @group_prefixes = @group_prefixes.transform_keys(&.downcase) + end + + protected def new_user_login(user_json) + user_details = UserLogin.from_json user_json + check_user(user_details.user_id) + + @users_checked += 1 + self[:users_checked] = @users_checked + rescue error + logger.error { error.inspect_with_backtrace } + self[:last_error] = { + error: error.message, + time: Time.local.to_s, + user: user_json, + } + @error_count += 1 + self[:error_count] = @error_count + end + + @[Security(Level::Support)] + def check_user(id : String) : Nil + logger.debug { "checking groups of: #{id}" } + + # Loading the existing user info in PlaceOS (we need the users id) + email = staff_api.user(id).get["email"].as_s + logger.debug { "found placeos user info: #{email}" } + + # Request user details from GraphAPI or Google + users_groups = calendar_api.get_groups(email).get + logger.debug { "found user groups: #{users_groups.to_pretty_json}" } + users_groups = users_groups.as_a + + users_group_ids = users_groups.map { |group| group["id"].as_s } + users_group_names = users_groups.map { |group| group["name"].as_s.downcase } + + # Build the list of placeos groups based on the mappings and update the user model + groups = [] of String + @group_mappings.each { |group_id, place_group| groups << place_group[:place_id] if users_group_ids.includes? group_id } + @group_prefixes.each do |group_prefix, place_group| + users_group_names.each do |name| + if name.starts_with?(group_prefix) + if place_name = place_group[:place_id] + groups << place_name + elsif place_group[:strip_prefix] + groups << name.split(group_prefix, 2)[-1] + else + groups << name + end + end + end + end + staff_api.update_user(id, {groups: groups}.to_json).get + + logger.debug { "checked #{users_groups.size}, found #{groups.size} matching: #{groups}" } + end +end diff --git a/drivers/place/user_group_mappings_spec.cr b/drivers/place/user_group_mappings_spec.cr new file mode 100644 index 00000000000..bea0f16a01f --- /dev/null +++ b/drivers/place/user_group_mappings_spec.cr @@ -0,0 +1,43 @@ +class StaffAPI < DriverSpecs::MockDriver + def user(id : String) + {email: "steve@placeos.tech"} + end + + def update_user(id : String, body_json : String) : Nil + self[id] = body_json + end +end + +class Calendar < DriverSpecs::MockDriver + def get_groups(user_id : String) + [ + { + id: "5f4694-96f3-4209-a432-b04ac06ca7", + name: "Azure-Global-Microsoft Intune Users-Licensed", + description: "Azure-Global-Microsoft Intune Users-Licensed", + }, + { + id: "bb8836-5942-402d-8d67-55b1a642", + name: "All Users", + description: "Auto generated group, do not change", + }, + ] + end +end + +DriverSpecs.mock_driver "Place::LogicExample" do + system({ + StaffAPI: {StaffAPI}, + Calendar: {Calendar}, + }) + + settings({ + group_mappings: { + "5f4694-96f3-4209-a432-b04ac06ca7" => {"place_id" => "intune"}, + "admins" => {"place_id" => "im an admin"}, + }, + }) + + exec(:check_user, "user-1234").get + system(:StaffAPI_1)["user-1234"].should eq({"groups" => ["intune"]}.to_json) +end diff --git a/drivers/place/visitor_mailer.cr b/drivers/place/visitor_mailer.cr new file mode 100644 index 00000000000..328fd4aeacc --- /dev/null +++ b/drivers/place/visitor_mailer.cr @@ -0,0 +1,178 @@ +module Place; end + +require "uuid" +require "oauth2" +require "placeos-driver/interface/mailer" + +class Place::VisitorMailer < PlaceOS::Driver + descriptive_name "PlaceOS Visitor Mailer" + generic_name :VisitorMailer + description %(emails visitors when they are invited and notifies hosts when they check in) + + default_settings({ + timezone: "GMT", + date_time_format: "%c", + time_format: "%l:%M%p", + date_format: "%A, %-d %B", + }) + + accessor mailer : Mailer_1, implementing: PlaceOS::Driver::Interface::Mailer + accessor staff_api : StaffAPI_1 + + def on_load + # Guest has been marked as attending a meeting in person + monitor("staff/guest/attending") { |_subscription, payload| guest_event(payload.gsub(/[^[:print:]]/, "")) } + + # Guest has arrived in the lobby + monitor("staff/guest/checkin") { |_subscription, payload| guest_event(payload.gsub(/[^[:print:]]/, "")) } + + on_update + end + + @uri : URI? = nil + @host : String = "" + @origin : String = "" + @time_zone : Time::Location = Time::Location.load("GMT") + + @users_checked_in : UInt64 = 0_u64 + @error_count : UInt64 = 0_u64 + + @visitor_emails_sent : UInt64 = 0_u64 + @visitor_email_errors : UInt64 = 0_u64 + + # See: https://crystal-lang.org/api/0.35.1/Time/Format.html + @date_time_format : String = "%c" + @time_format : String = "%l:%M%p" + @date_format : String = "%A, %-d %B" + + def on_update + @date_time_format = setting?(String, :date_time_format) || "%c" + @time_format = setting?(String, :time_format) || "%l:%M%p" + @date_format = setting?(String, :date_format) || "%A, %-d %B" + + uri = URI.parse(config.uri.not_nil!) + @host = uri.port ? "#{uri.host}:#{uri.port}" : uri.host.not_nil! + @origin = "#{uri.scheme}://#{@host}" + @uri = uri + + time_zone = setting?(String, :calendar_time_zone).presence || "GMT" + @time_zone = Time::Location.load(time_zone) + end + + class GuestEvent + include JSON::Serializable + + property action : String + property checkin : Bool? + property system_id : String + property event_id : String + property host : String + property resource : String + property event_summary : String + property event_starting : Int64 + property attendee_name : String + property attendee_email : String + property ext_data : Hash(String, JSON::Any)? + end + + protected def guest_event(payload) + logger.debug { "received guest event payload: #{payload}" } + guest_details = GuestEvent.from_json payload + + if guest_details.action == "checkin" + # send_checkedin_email( + # guest_details.host, + # guest_details.attendee_name, + # ) + # self[:users_checked_in] = @users_checked_in += 1 + else + send_visitor_qr_email( + guest_details.attendee_email, + guest_details.attendee_name, + guest_details.host, + guest_details.event_id, + # guest_details.event_title, + guest_details.event_starting, + guest_details.system_id, + ) + end + rescue error + logger.error { error.inspect_with_backtrace } + self[:error_count] = @error_count += 1 + self[:last_error] = { + error: error.message, + time: Time.local.to_s, + user: payload, + } + end + + @[Security(Level::Support)] + def send_visitor_qr_email( + visitor_email : String, + visitor_name : String, + host_email : String, + event_id : String, + # event_title : String, + event_start : Int64, + system_id : String + ) + room = get_room_details(system_id) + + local_start_time = Time.unix(event_start).in(@time_zone) + + qr_png = mailer.generate_png_qrcode(text: "VISIT:#{visitor_email},#{system_id},#{event_id}", size: 256).get.as_s + + mailer.send_template( + visitor_email, + {"visitor_invited", "visitor"}, # Template selection: "visitor_invited" action, "visitor" email + { + visitor_email: visitor_email, + visitor_name: visitor_name, + host_name: get_host_name(host_email), + room_name: room.display_name.presence || room.name, + # event_title: event_title, + event_start: local_start_time.to_s(@time_format), + event_date: local_start_time.to_s(@date_format), + }, + [ + { + file_name: "qr.png", + content: qr_png, + content_id: visitor_email, + }, + ] + ) + end + + # =================================== + # PlaceOS API requests + # =================================== + + class SystemDetails + include JSON::Serializable + + property id : String + property name : String + property display_name : String? + property map_id : String? + end + + protected def get_room_details(system_id : String, retries = 0) + SystemDetails.from_json staff_api.get_system(system_id).get.to_json + rescue error + raise "issue loading system details #{system_id}" if retries > 3 + sleep 1 + get_room_details(system_id, retries + 1) + end + + protected def get_host_name(host_email, retries = 0) + staff_api.staff_details(host_email).get["name"].as_s.split('(')[0] + rescue error + if retries > 3 + logger.error { "issue loading host details #{host_email}" } + return "your host" + end + sleep 1 + get_host_name(host_email, retries + 1) + end +end diff --git a/drivers/point_grab/cogni_point.cr b/drivers/point_grab/cogni_point.cr new file mode 100644 index 00000000000..0f7737bfbd6 --- /dev/null +++ b/drivers/point_grab/cogni_point.cr @@ -0,0 +1,385 @@ +require "uri" +require "uuid" + +module PointGrab; end + +# Documentation: https://aca.im/driver_docs/PointGrab/CogniPointAPI2-1.pdf + +class PointGrab::CogniPoint < PlaceOS::Driver + # Discovery Information + generic_name :CogniPoint + descriptive_name "PointGrab CogniPoint REST API" + + default_settings({ + user_id: "10000000", + app_key: "c5a6adc6-UUID-46e8-b72d-91395bce9565", + }) + + @user_id : String = "" + @app_key : String = "" + @auth_token : String = "" + @auth_expiry : Time = 1.minute.ago + + def on_load + on_update + end + + def on_update + @user_id = setting(String, :user_id) + @app_key = setting(String, :app_key) + end + + class TokenResponse + include JSON::Serializable + + property token : String + property expires_in : Int32 + end + + def expire_token! + @auth_expiry = 1.minute.ago + end + + def token_expired? + @auth_expiry < Time.utc + end + + def get_token + return @auth_token unless token_expired? + + response = post("/be/cp/oauth2/token", body: "grant_type=client_credentials", headers: { + "Content-Type" => "application/x-www-form-urlencoded", + "Accept" => "application/json", + "Authorization" => "Basic #{Base64.strict_encode("#{@user_id}:#{@app_key}")}", + }) + + body = response.body + logger.debug { "received login response: #{body}" } + + if response.success? + resp = TokenResponse.from_json(body.not_nil!) + token = resp.token + @auth_expiry = Time.utc + (resp.expires_in - 5).seconds + @auth_token = "Bearer #{resp.token}" + else + logger.error { "authentication failed with HTTP #{response.status_code}" } + raise "failed to obtain access token" + end + end + + macro get_request(path, result_type) + begin + %token = get_token + %response = get({{path}}, headers: { + "Accept" => "application/json", + "Authorization" => %token + }) + + if %response.success? + {{result_type}}.from_json(%response.body.not_nil!) + else + expire_token! if %response.status_code == 401 + raise "unexpected response #{%response.status_code}\n#{%response.body}" + end + end + end + + class Customer + include JSON::Serializable + + property id : String + property name : String + end + + def customers + customers = get_request("/be/cp/v2/customers", NamedTuple(endCustomers: Array(Customer))) + customers[:endCustomers] + end + + class GeoPosition + include JSON::Serializable + + property latitude : Float64 + property longitude : Float64 + end + + class MetricPositions + include JSON::Serializable + + @[JSON::Field(key: "posX")] + property pos_x : Float64 + + @[JSON::Field(key: "posY")] + property pos_y : Float64 + end + + class Site + include JSON::Serializable + + property id : String + property name : String + + class Location + include JSON::Serializable + + @[JSON::Field(key: "houseNo")] + property house_number : String + property street : String + property city : String + property county : String + property state : String + property country : String + property zip : String + + @[JSON::Field(key: "geoPosition")] + property geo_position : GeoPosition + end + + @[JSON::Field(key: "customerId")] + property customer_id : String + property location : Location + end + + def sites + sites = get_request("/be/cp/v2/sites", NamedTuple(sites: Array(Site))) + sites[:sites] + end + + def site(site_id : String) + get_request("/be/cp/v2/sites/#{site_id}", Site) + end + + class Building + include JSON::Serializable + + property id : String + property name : String + + @[JSON::Field(key: "siteId")] + property site_id : String + + property location : Site::Location + end + + def buildings(site_id : String) + buildings = get_request("/be/cp/v2/sites/#{site_id}/buildings", NamedTuple(buildings: Array(Building))) + buildings[:buildings] + end + + def building(site_id : String, building_id : String) + get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}", Building) + end + + class Floor + include JSON::Serializable + + property id : String + property name : String + + @[JSON::Field(key: "floorNumber")] + property floor_number : String + + @[JSON::Field(key: "floorPlanURL")] + property floor_plan_url : String + + @[JSON::Field(key: "widthDistance")] + property width_distance : Float64 + + @[JSON::Field(key: "lengthDistance")] + property length_distance : Float64 + + # NOTE:: unknown format for referencePoints => Array(?) + end + + def floors(site_id : String, building_id : String) + floors = get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}/floors", NamedTuple(floors: Array(Building))) + floors[:floors] + end + + def floor(site_id : String, building_id : String, floor_id : String) + get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}/floors/#{floor_id}", Floor) + end + + class Area + include JSON::Serializable + + property id : String + property name : String + property length : Float64 + property width : Float64 + + @[JSON::Field(key: "centerX")] + property center_x : Float64 + + @[JSON::Field(key: "centerY")] + property center_y : Float64 + + property rotation : Int32 + property frequency : Int32 + + @[JSON::Field(key: "deviceIDs")] + property device_ids : Array(String) + + class Application + include JSON::Serializable + + @[JSON::Field(key: "areaType")] + property area_type : String + + @[JSON::Field(key: "applicationType")] + property application_type : String + end + + property applications : Array(Application) + + # Area Polygon positions in meters + @[JSON::Field(key: "metricPositions")] + property metric_positions : Array(MetricPositions) + + # Area Polygon Coordinates positions + @[JSON::Field(key: "geoPositions")] + property geo_positions : Array(GeoPosition)? + end + + class FloorAreas + include JSON::Serializable + + @[JSON::Field(key: "floorId")] + property floor_id : String + property areas : Array(Area) + end + + def building_areas(site_id : String, building_id : String) + floors = get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}/areas", NamedTuple( + floorsAreas: FloorAreas)) + floors[:floorsAreas] + end + + def areas(site_id : String, building_id : String, floor_id : String) + areas = get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}/floors/#{floor_id}/areas", NamedTuple( + areas: Array(Area))) + areas[:areas] + end + + def area(site_id : String, building_id : String, floor_id : String, area_id : String) + get_request("/be/cp/v2/sites/#{site_id}/buildings/#{building_id}/floors/#{floor_id}/areas/#{area_id}", Area) + end + + class Handler + include JSON::Serializable + + property id : String + property token : String + + @[JSON::Field(key: "thirdPartyAppID")] + property app_id : UInt32 + + @[JSON::Field(key: "endPoint")] + property end_point : String + end + + def handlers + handlers = get_request("/be/cp/v2/resources/handlers", NamedTuple( + handlers: Array(Handler))) + handlers[:handlers] + end + + class Subscription + include JSON::Serializable + + property id : String + property token : String + property started : Bool + property endpoint : String + property uri : String + + @[JSON::Field(key: "notificationType")] + property notification_type : String + + @[JSON::Field(key: "subscriptionType")] + property subscription_type : String + end + + enum NotificationType + Counting + Traffic + end + + def subscribe(handler_uri : String, auth_token : String = UUID.random.to_s, events : NotificationType = NotificationType::Counting) + # Ensure the handler is a valid URI + URI.parse handler_uri + + token = get_token + response = post( + "/be/cp/v2/telemetry/subscriptions", + body: { + subscriptionType: "PUSH", + notificationType: events.to_s.upcase, + endpoint: handler_uri, + token: auth_token, + }.to_json, + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => token, + } + ) + + body = response.body + logger.debug { "received login response: #{body}" } + + if response.success? + Subscription.from_json(body.not_nil!) + else + logger.error { "authentication failed with HTTP #{response.status_code}" } + raise "failed to obtain access token" + end + end + + def subscriptions + get_request("/be/cp/v2/telemetry/subscriptions", Array(Subscription)) + end + + def delete_subscription(id : String) + token = get_token + delete("/be/cp/v2/telemetry/subscriptions/#{id}", + headers: { + "Accept" => "application/json", + "Authorization" => token, + } + ).success? + end + + def update_subscription(id : String, started : Bool = true) + token = get_token + patch( + "/be/cp/v2/telemetry/subscriptions/#{id}", + body: {started: started}.to_json, + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => token, + } + ).success? + end + + # TODO:: this data is posted to the subscription endpoint + # we need to implement webhooks for this to work properly + class CountUpdate + include JSON::Serializable + + @[JSON::Field(key: "areaId")] + property area_id : String + property devices : Array(String) + + @[JSON::Field(key: "type")] + property event_type : String + property timestamp : UInt64 + property count : Int32 + end + + def update_count(count_json : String) + count = CountUpdate.from_json(count_json) + self["area_#{count.area_id}"] = count.count + end +end diff --git a/drivers/point_grab/cogni_point_spec.cr b/drivers/point_grab/cogni_point_spec.cr new file mode 100644 index 00000000000..8a305ca31f0 --- /dev/null +++ b/drivers/point_grab/cogni_point_spec.cr @@ -0,0 +1,31 @@ +DriverSpecs.mock_driver "PointGrab::CogniPoint" do + # Send the request + retval = exec(:get_token) + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJSRUFEIiwiV1JJVEUiXSwiZXhwIjoxNTc0MjMzNjEyLCJhdXRob3JpdGllcyI6WyJST0xFX1RSVVNURURfQ0xJRU5UIl0sImp0aSI6IjM1ZjkxYjlkLTVmZmMtNDJkYy05YWZkLTJiZTE0YjI1MmE1NCIsImNsaWVudF9pZCI6IjEwMDAwMjEzIn0.Wzrsaey5z3ShAFYKOaWmgfoRZNsk-PclSK9IRtYf4b8" + + # We should request a new token from Floorsense + expect_http_request do |request, response| + if io = request.body + data = io.gets_to_end + + # The request is param encoded + if data == "grant_type=client_credentials" && request.headers["Authorization"] == "Basic #{Base64.strict_encode("10000000:c5a6adc6-UUID-46e8-b72d-91395bce9565")}" + response.status_code = 200 + response.output.puts %({ + "token": "#{token}", + "access_token": "#{token}", + "token_type": "bearer", + "expires_in": 3599 + }) + else + response.status_code = 401 + response.output.puts "" + end + else + raise "expected request to include token type" + end + end + + # What the function should return (for use in making further requests) + retval.get.should eq("Bearer #{token}") +end diff --git a/drivers/qsc/q_sys_control.cr b/drivers/qsc/q_sys_control.cr new file mode 100644 index 00000000000..a42f5d9083b --- /dev/null +++ b/drivers/qsc/q_sys_control.cr @@ -0,0 +1,377 @@ +# Documentation https://q-syshelp.qsc.com/Content/External_Control/Q-SYS_External_Control/007_Q-SYS_External_Control_Protocol.htm + +class Qsc::QSysControl < PlaceOS::Driver + # Discovery Information + tcp_port 1702 + descriptive_name "QSC Audio DSP External Control" + generic_name :Mixer + + alias Group = NamedTuple(id: Int32, controls: Set(String)) + alias Ids = String | Array(String) + alias Val = Int32 | Float64 + + @username : String? = nil + @password : String? = nil + @change_group_id : Int32 = 30 + @em_id : String? = nil + @emergency_subscribe : PlaceOS::Driver::Subscriptions::Subscription? = nil + @history = {} of String => Symbol + @change_groups = {} of Symbol => Group + + def on_load + transport.tokenizer = Tokenizer.new("\r\n") + on_update + end + + def on_update + @username = setting?(String, :username) + @password = setting?(String, :password) + login if @username + + @change_groups.each do |_, group| + logger.debug { "change groups" } + group_id = group[:id] + controls = group[:controls] + + # Re-create change groups and poll every 2 seconds + do_send("cgc #{group_id}\n")#, wait: false) + do_send("cgsna #{group_id} 2000\n")#, wait: false) + controls.each do |id| + do_send("cga #{group_id} #{id}\n")#, wait: false) + end + end + + em_id = setting?(String, :emergency) + + # Emergency ID changed + if (e = @emergency_subscribe) && @em_id != em_id + subscriptions.unsubscribe(e) + end + + # Emergency ID exists + if em_id + group = create_change_group(:emergency) + group_id = group[:id] + controls = group[:controls] + + # Add id to change group as required + unless controls.includes?(em_id) + # subscribe to changes + @em_id = em_id + @emergency_subscribe = subscribe(em_id) do |_, value| + self[:emergency] = value + end + + update_change_group(:emergency, group_id, Set.new([em_id])) + do_send("cga #{group_id} #{em_id}\n")#, wait: false) + end + end + end + + def connected + schedule.every(40.seconds) do + logger.debug { "Maintaining Connection" } + about + end + end + + def disconnected + schedule.clear + end + + def get_status(control_id : String, **options) + do_send("cg #{control_id}\n", **options) + end + + def set_position(control_id : String, position : Int32, ramp_time : Val? = nil) + if ramp_time + do_send("cspr \"#{control_id}\" #{position} #{ramp_time}\n")#, wait: false) + schedule.in(ramp_time.seconds + 200.milliseconds) { get_status(control_id) } + else + do_send("csp \"#{control_id}\" #{position}\n") + end + end + + def set_value(control_id : String, value : Val, ramp_time : Val? = nil, **options) + if ramp_time + do_send("csvr \"#{control_id}\" #{value} #{ramp_time}\n", **options)#, wait: false) + schedule.in(ramp_time.seconds + 200.milliseconds) { get_status(control_id) } + else + do_send("csv \"#{control_id}\" #{value}\n", **options) + end + end + + def about + do_send("sg\n", name: :status, priority: 0) + end + + def login(username : String? = nil, password : String? = nil) + username ||= @username + password ||= @password + do_send("login #{username} #{password}\n", name: :login, priority: 99) + end + + # Used to set a dial number/string + def set_string(control_ids : Ids, text : String) + ensure_array(control_ids).each do |id| + do_send("css \"#{id}\" \"#{text}\"\n").get + self[id] = text + end + end + + # Used to trigger dialing etc + def trigger(control_id : String) + logger.debug { "Sending trigger to Qsys: ct #{control_id}" } + do_send("ct \"#{control_id}\"\n")#, wait: false) + end + + # Compatibility Methods + def fader(fader_ids : Ids, level : Int32) + level = level / 10 + ensure_array(fader_ids).each { |f_id| set_value(f_id, level, name: "fader#{f_id}", fader_type: :fader) } + end + + def faders(fader_ids : Ids, level : Int32) + fader(fader_ids, level) + end + + def mute(mute_ids : Ids, state : Bool = true) + level = state ? 1 : 0 + ensure_array(mute_ids).each { |m_id| set_value(m_id, level, fader_type: :mute) } + end + + def mutes(mute_ids : Ids, state : Bool) + mute(mute_ids, state) + end + + def unmute(mute_ids : Ids) + mute(mute_ids, false) + end + + def mute_toggle(mute_id : Ids) + mute(mute_id, !self["fader#{mute_id}_mute"].try(&.as_bool)) + end + + def snapshot(name : String, index : Int32, ramp_time : Val = 1.5) + do_send("ssl \"#{name}\" #{index} #{ramp_time}\n")#, wait: false) + end + + def save_snapshot(name : String, index : Int32) + do_send("sss \"#{name}\" #{index}\n")#, wait: false) + end + + # For inter-module compatibility + def query_fader(fader_ids : Ids) + fad = ensure_array(fader_ids)[0] + get_status(fad, fader_type: :fader) + end + + def query_faders(fader_ids : Ids) + ensure_array(fader_ids).each { |f_id| get_status(f_id, fader_type: :fader) } + end + + def query_mute(fader_ids : Ids) + fad = ensure_array(fader_ids)[0] + get_status(fad, fader_type: :mute) + end + + def query_mutes(fader_ids : Ids) + ensure_array(fader_ids).each { |fad| get_status(fad, fader_type: :mute) } + end + + def phone_number(number : String, control_id : String) + set_string(control_id, number) + end + + def phone_dial(control_id : String) + trigger(control_id) + schedule.in(200.milliseconds) { poll_change_group(:phone) } + end + + def phone_hangup(control_id : String) + phone_dial(control_id) + end + + def phone_watch(control_ids : Ids) + # Ensure change group exists + group = create_change_group(:phone) + group_id = group[:id] + controls = group[:controls] + + # Add ids to change group + ensure_array(control_ids).each do |id| + unless controls.includes?(id) + controls << id + do_send("cga #{group_id} #{id}\n")#, wait: false) + end + end + + update_change_group(:phone, group_id, controls) + end + + private def create_change_group(name) : Group + if group = @change_groups[name]? + return group + end + + # Provide a unique group id + next_id = @change_group_id + @change_group_id += 1 + + @change_groups[name] = { + id: next_id, + controls: Set(String).new, + } + + # create change group and poll every 2 seconds + do_send("cgc #{next_id}\n")#, wait: false) + do_send("cgsna #{next_id} 2000\n")#, wait: false) + @change_groups[name] + end + + private def update_change_group(name, id, controls) : Group + @change_groups[name] = { + id: id, + controls: controls, + } + end + + private def poll_change_group(name) + if group = @change_groups[name] + do_send("cgpna #{group[:id]}\n")#, wait: false) + end + end + + def received(data, task) + process_response(data, task) + end + + private def process_response(data, task, fader_type : Symbol? = nil) + data = String.new(data) + return task.try(&.success) if data == "none\r\n" + logger.debug { "QSys sent: #{data}" } + resp = shellsplit(data) + + case resp[0] + when "cv" + control_id = resp[1] + # string rep = resp[2] + value = resp[3] + position = resp[4].to_i + + self["pos_#{control_id}"] = position + + if type = fader_type || @history[control_id]? + @history[control_id] = type + + case type + when :fader + self["fader#{control_id}"] = (value.to_f * 10).to_i + when :mute + self["fader#{control_id}_mute"] = value.to_i == 1 + end + else + value = resp[2] + if value == "false" || value == "true" + self[control_id] = value == "true" + else + self[control_id] = value.gsub('_', ' ') + end + logger.debug { "Received response from unknown ID type: #{control_id} == #{value}" } + end + when "cvv" # Control status, Array of control status + control_id = resp[1] + count = resp[2].to_i + + if type = fader_type || @history[control_id]? + @history[control_id] = type + + # Skip strings and extract the values + next_count = count + 3 + count = resp[next_count].to_i + 1.upto(count) do |index| + value = resp[next_count + index] + + case type + when :fader + self["fader#{control_id}"] = (value.to_f * 10).to_i + when :mute + self["fader#{control_id}_mute"] = value == 1 + end + end + else + # Don't skip strings here + next_count = 2 + 1.upto(count) do |index| + value = resp[next_count + index] + + if value == "false" || value == "true" + self[control_id] = value == "true" + else + self[control_id] = value.gsub('_', ' ') + end + end + logger.debug { "Received response from unknown ID type: #{control_id}" } + + # Jump to the position values + next_count = count + 3 + count = resp[next_count].to_i + end + + # Grab the positions + next_count = next_count + count + 1 + count = resp[next_count].to_i + 1.upto(count) do |index| + value = resp[next_count + index] + self["pos_#{control_id}"] = value + end + when "sr" # About response + self[:design_name] = resp[1] + self[:is_primary] = resp[3] == "1" + self[:is_active] = resp[4] == "1" + when "core_not_active", "bad_change_group_handle", "bad_command", "bad_id", "control_read_only", "too_many_change_groups" + return task.try(&.abort("Error response received: #{data}")) + when "login_required" + login if @username + return task.try(&.abort("Login is required!")) + when "login_success" + logger.debug { "Login success!" } + when "login_failed" + return task.try(&.abort("Invalid login details provided")) + when "rc" + logger.warn { "System is notifying us of a disconnect!" } + when "cmvv" + logger.debug { "received cmvv response" } + else + logger.warn { "Unknown response received #{data}" } + end + + task.try(&.success) + end + + private def do_send(req, fader_type : Symbol? = nil, **options) + logger.debug { "sending #{req}" } + send(req, **options) { |data, task| process_response(data, task, fader_type) } + end + + private def ensure_array(object) + object.is_a?(Array) ? object : [object] + end + + # Quick dirty port of https://github.com/ruby/ruby/blob/master/lib/shellwords.rb + private def shellsplit(line : String) : Array(String) + words = [] of String + field = "" + pattern = /\G\s*(?>([^\s\\\'\"]+)|'([^\']*)'|"((?:[^\"\\]|\\.)*)"|(\\.?)|(\S))(\s|\z)?/m + line.scan(pattern) do |match| + _, word, sq, dq, esc, garbage, sep = match.to_a + raise ArgumentError.new("Unmatched quote: #{line.inspect}") if garbage + field += (word || sq || dq.try(&.gsub(/\\([$`"\\\n])/, "\\1")) || esc.not_nil!.gsub(/\\(.)/, "\\1")) + if sep + words << field + field = "" + end + end + words + end +end diff --git a/drivers/qsc/q_sys_control_spec.cr b/drivers/qsc/q_sys_control_spec.cr new file mode 100644 index 00000000000..1fd9680dcd5 --- /dev/null +++ b/drivers/qsc/q_sys_control_spec.cr @@ -0,0 +1,70 @@ +DriverSpecs.mock_driver "Qsc::QSysControl" do + settings({ + username: "user", + password: "pass", + emergency: "6" + }) + + should_send("login user pass\n") + responds("login_success\r\n") + should_send("cgc 30\n") + responds("none\r\n") + should_send("cgsna 30 2000\n") + responds("none\r\n") + should_send("cga 30 6\n") + responds("none\r\n") + + exec(:about) + should_send("sg\n") + responds("sr \"MyDesign\" \"NIEC2bxnVZ6a\" 1 1\r\n") + status[:design_name].should eq("MyDesign") + status[:is_primary].should eq(true) + status[:is_active].should eq(true) + + exec(:mute, ["1","2","3"], true) + should_send("csv \"1\" 1\n") + responds("cv \"1\" \"control string\" 1 8\r\n") + status[:pos_1].should eq(8) + status[:fader1_mute].should eq(true) + should_send("csv \"2\" 1\n") + responds("cv \"2\" \"control string\" 1 5\r\n") + status[:pos_2].should eq(5) + status[:fader2_mute].should eq(true) + should_send("csv \"3\" 1\n") + responds("cv \"3\" \"control string\" 1 4\r\n") + status[:pos_3].should eq(4) + status[:fader3_mute].should eq(true) + + exec(:faders, ["1", "2", "3"], 90) + should_send("csv \"1\" 9.0\n") + responds("cv \"1\" \"control string\" 9 6\r\n") + status[:pos_1].should eq(6) + status[:fader1_mute].should eq(true) + should_send("csv \"2\" 9.0\n") + responds("cv \"2\" \"control string\" 9 7\r\n") + status[:pos_2].should eq(7) + status[:fader2].should eq(90) + should_send("csv \"3\" 9.0\n") + responds("cv \"3\" \"control string\" 9 8\r\n") + status[:pos_3].should eq(8) + status[:fader3].should eq(90) + + exec(:phone_watch, "0") + should_send("cgc 31\n") + responds("none\r\n") + should_send("cgsna 31 2000\n") + responds("none\r\n") + should_send("cga 31 0\n") + responds("none\r\n") + + exec(:phone_watch, ["1","2"]) + should_send("cga 31 1\n") + responds("none\r\n") + should_send("cga 31 2\n") + responds("none\r\n") + + exec(:phone_number, "0123456789", "1") + should_send("css \"1\" \"0123456789\"\n") + responds("cv \"1\" \"0123456789\" 9 8\r\n") + status[:"1"].should eq("0123456789") +end diff --git a/drivers/qsc/q_sys_remote.cr b/drivers/qsc/q_sys_remote.cr new file mode 100644 index 00000000000..9a03d2294a6 --- /dev/null +++ b/drivers/qsc/q_sys_remote.cr @@ -0,0 +1,416 @@ +require "json" + +# Documentation: https://aca.im/driver_docs/QSC/QRCDocumentation.pdf + +class Qsc::QSysRemote < PlaceOS::Driver + tcp_port 1710 + descriptive_name "QSC Audio DSP" + generic_name :Mixer + + @id : Int32 = 0 + @db_based_faders : Bool? = nil + @integer_faders : Bool? = nil + @username : String? = nil + @password : String? = nil + + Delimiter = "\0" + JsonRpcVer = "2.0" + Errors = { + -32700 => "Parse error. Invalid JSON was received by the server.", + -32600 => "Invalid request. The JSON sent is not a valid Request object.", + -32601 => "Method not found.", + -32602 => "Invalid params.", + -32603 => "Server error.", + 2 => "Invalid Page Request ID", + 3 => "Bad Page Request - could not create the requested Page Request", + 4 => "Missing file", + 5 => "Change Groups exhausted", + 6 => "Unknown change croup", + 7 => "Unknown component name", + 8 => "Unknown control", + 9 => "Illegal mixer channel index", + 10 => "Logon required", + } + + alias Num = Int32 | Float64 + alias ValTup = NamedTuple(Name: String, Value: Num) + alias PosTup = NamedTuple(Name: String, Position: Num) + alias Values = ValTup | PosTup | Array(ValTup) | Array(PosTup) + alias Ids = String | Array(String) + + def on_load + transport.tokenizer = Tokenizer.new(Delimiter) + on_update + end + + def on_update + @db_based_faders = setting?(Bool, :db_based_faders) + @integer_faders = setting?(Bool, :integer_faders) + @username = setting?(String, :username) + @password = setting?(String, :password) + logon if @username && @password + end + + def connected + schedule.every(20.seconds) do + logger.debug { "Maintaining Connection" } + no_op + end + @id = 0 + end + + def disconnected + schedule.clear + end + + # This command does nothing but is useful for making sure the socket is left open + def no_op + do_send(cmd: :NoOp, priority: 0) + end + + def get_status + do_send(next_id, cmd: :StatusGet, params: 0, priority: 0) + end + + def logon + do_send( + cmd: :Logon, + params: { + :User => @username, + :Password => @password, + }, + priority: 99 + ) + end + + def control_set(name : String, value : Num | Bool, ramp : Num? = nil, **options) + if ramp + params = { + :Name => name, + :Value => value, + :Ramp => ramp, + } + else + params = { + :Name => name, + :Value => value, + } + end + + do_send(next_id, "Control.Set", params, **options) + end + + def control_get(names : Array(String), **options) + do_send(next_id, "Control.Get", names, **options) + end + + def component_get(c_name : String, controls : Array(String), **options) + do_send(next_id, "Component.Get", { + :Name => c_name, + :Controls => controls.map { |ctrl| {:Name => ctrl} }, + }, **options) + end + + def component_set(c_name : String, values : Values, **options) + values = ensure_array(values) + + do_send(next_id, "Component.Set", { + :Name => c_name, + :Controls => values, + }, **options) + end + + def component_trigger(component : String, trigger : String, **options) + do_send(next_id, "Component.Trigger", { + :Name => component, + :Controls => [{:Name => trigger}], + }, **options) + end + + def get_components(**options) + do_send(next_id, "Component.GetComponents", **options) + end + + def change_group_add_controls(group_id : String, controls : Array(String), **options) + do_send(next_id, "ChangeGroup.AddControl", { + :Id => group_id, + :Controls => controls, + }, **options) + end + + def change_group_remove_controls(group_id : String, controls : Array(String), **options) + do_send(next_id, "ChangeGroup.Remove", { + :Id => group_id, + :Controls => controls, + }, **options) + end + + def change_group_add_component(group_id : String, component_name : String, controls : Array(String), **options) + do_send(next_id, "ChangeGroup.AddComponentControl", { + :Id => group_id, + :Component => { + :Name => component_name, + :Controls => controls.map { |ctrl| {:Name => ctrl} }, + }, + }, **options) + end + + # Returns values for all the controls + def poll_change_group(group_id : String, **options) + do_send(next_id, "ChangeGroup.Poll", {:Id => group_id}, **options) + end + + # Removes the change group + def destroy_change_group(group_id : String, **options) + do_send(next_id, "ChangeGroup.Destroy", {:Id => group_id}, **options) + end + + # Removes all controls from change group + def clear_change_group(group_id : String, **options) + do_send(next_id, "ChangeGroup.Clear", {:Id => group_id}, **options) + end + + # Where every is the number of seconds between polls + def auto_poll_change_group(group_id : String, every : Num, **options) + do_send(next_id, "ChangeGroup.AutoPoll", { + :Id => group_id, + :Rate => every, + }, **options) # , wait: false) + end + + # Example usage: + # mixer 'Parade', {1 => [2,3,4], 3 => 6}, true + def mixer(name : String, inouts : Hash(Int32, Int32 | Array(Int32)), mute : Bool = false, **options) + inouts.each do |input, outputs| + outputs = ensure_array(outputs) + + do_send(next_id, "Mixer.SetCrossPointMute", { + :Name => name, + :Inputs => input.to_s, + :Outputs => outputs.join(' '), + :Value => mute, + }, **options) + end + end + + Faders = { + matrix_in: { + type: :"Mixer.SetInputGain", + pri: :Inputs, + }, + matrix_out: { + type: :"Mixer.SetOutputGain", + pri: :Outputs, + }, + matrix_crosspoint: { + type: :"Mixer.SetCrossPointGain", + pri: :Inputs, + sec: :Outputs, + }, + } + + def matrix_fader(name : String, level : Num, index : Array(Int32), type : String = "matrix_out", **options) + info = Faders[type] + + if sec = info[:sec]? + params = { + :Name => name, + info[:pri] => index[0], + sec => index[1], + :Value => level, + } + else + params = { + :Name => name, + info[:pri] => index, + :Value => level, + } + end + + do_send(next_id, info[:type], params, **options) + end + + Mutes = { + matrix_in: { + type: :"Mixer.SetInputMute", + pri: :Inputs, + }, + matrix_out: { + type: :"Mixer.SetOutputMute", + pri: :Outputs, + }, + } + + def matrix_mute(name : String, value : Num, index : Array(Int32), type : String = "matrix_out", **options) + info = Mutes[type] + + do_send(next_id, info[:type], { + :Name => name, + info[:pri] => index, + :Value => value, + }, **options) + end + + # value can either be a number to set actual numeric values like decibels + # or Bool to deal with mute state + def fader(fader_ids : Ids, value : Num | Bool, component : String? = nil, type : String = "fader", use_value : Bool = false, **options) + faders = ensure_array(fader_ids) + if component && (val = value.as?(Num)) + if @db_based_faders || use_value + val = val / 10 if @integer_faders && !use_value + fads = faders.map { |fad| {Name: fad, Value: val} } + else + val = val / 1000 if @integer_faders + fads = faders.map { |fad| {Name: fad, Position: val} } + end + component_set(component, fads, name: "level_#{faders[0]}").get + component_get(component, faders) + else + reqs = faders.map { |fad| control_set(fad, value) } + reqs.last.get + control_get(faders) + end + end + + def faders(ids : Ids, value : Num | Bool, component : String? = nil, type : String = "fader", **options) + fader(ids, value, component, type, **options) + end + + def mute(fader_id : Ids, state : Bool = true, component : String? = nil, type : String = "fader", **options) + fader(fader_id, state, component, type, state, **options) + end + + def mutes(ids : Ids, state : Bool = true, component : String? = nil, type : String = "fader", **options) + mute(ids, state, component, type, **options) + end + + def unmute(fader_id : Ids, component : String? = nil, type : String = "fader", **options) + mute(fader_id, false, component, type, **options) + end + + def query_fader(fader_id : Ids, component : String? = nil, type : String = "fader") + faders = ensure_array(fader_id) + component ? component_get(component, faders) : control_get(faders) + end + + def query_faders(ids : Ids, component : String? = nil, type : String = "fader", **options) + query_fader(ids, component, type, **options) + end + + def query_mute(fader_id : Ids, component : String? = nil, type : String = "fader") + query_fader(fader_id, component, type) + end + + def query_mutes(ids : Ids, component : String? = nil, type : String = "fader", **options) + query_fader(ids, component, type, **options) + end + + def received(data, task) + data = String.new(data[0..-2]) + response = JSON.parse(data) + + logger.debug { "QSys sent:" } + logger.debug { response } + + if err = response["error"]? + code = err["code"] + logger.warn { "Error code #{code} - #{Errors[code]}" } + + if code == 10 + if @username && @password + logon.get + return task.try(&.retry("Logged on and retrying command")) + else + return task.try(&.abort("Login required but no username and/or password in settings")) + end + end + + return task.try(&.abort(err["message"])) + end + + return task.try(&.success("Unknown response")) unless result = response["result"]? + + case result + when .as_h? + if result["Controls"]? # Probably Component.Get + process(result["Controls"].as_a, result["Name"]?) + elsif result["Platform"]? # StatusGet + result.as_h.each { |k, v| self[k.underscore] = v } + end + when .as_a? # Control.Get + process(result.as_a) + end + + task.try(&.success) + end + + BoolVals = ["true", "false"] + + private def process(values : Array, name : JSON::Any? = nil) + component = name.try(&.as_s?) ? "_#{name}" : "" + values.each do |value| + name = value["Name"] + + next unless val = value["Value"]? + + pos = value["Position"]? + str = value["String"]?.try(&.as_s) + + if BoolVals.includes?(str) + self["fader#{name}#{component}_mute"] = str == "true" + else + # Seems like string values can be independent of the other values + # This should mostly work to detect a string value + if val == 0 && pos == 0 && str && str[0] != '0' + self["#{name}#{component}"] = str + next + end + + if pos && (pos = pos.as_i? || pos.as_f?) + self["fader#{name}#{component}_pos"] = @integer_faders ? (pos * 1000).to_i : pos + end + + if val.as_s? + self["#{name}#{component}"] = val + elsif val = (val.as_i? || val.as_f?) + self["fader#{name}#{component}_val"] = @integer_faders ? (val * 10).to_i : val + end + end + end + end + + def next_id + @id += 1 + @id + end + + private def do_send(id : Int32? = nil, cmd = nil, params = {} of String => String, **options) + if id + req = { + jsonrpc: JsonRpcVer, + id: id, + method: cmd, + params: params, + } + else + req = { + jsonrpc: JsonRpcVer, + method: cmd, + params: params, + } + end + + logger.debug { "sending: #{req}" } + + cmd = req.to_json + Delimiter + + logger.debug { "sending json" } + logger.debug { cmd.inspect } + + send(cmd, **options) + end + + private def ensure_array(object) + object.is_a?(Array) ? object : [object] + end +end diff --git a/drivers/qsc/q_sys_remote_spec.cr b/drivers/qsc/q_sys_remote_spec.cr new file mode 100644 index 00000000000..809117a7470 --- /dev/null +++ b/drivers/qsc/q_sys_remote_spec.cr @@ -0,0 +1,149 @@ +DriverSpecs.mock_driver "Qsc::QSysRemote" do + settings({ + username: "user", + password: "pass", + }) + + # logon + should_send({ + jsonrpc: "2.0", + method: "Logon", + params: { + "User" => "user", + "Password" => "pass", + }, + }.to_json + "\0") + responds({"TODO" => "response not defined in docs"}.to_json + "\0") + + exec(:no_op) + should_send({ + jsonrpc: "2.0", + method: "NoOp", + params: {} of String => String, + }.to_json + "\0") + responds({"TODO" => "response not defined in docs"}.to_json + "\0") + + exec(:get_status) + should_send({ + jsonrpc: "2.0", + id: 1, + method: "StatusGet", + params: 0, + }.to_json + "\0") + responds({ + "jsonrpc" => "2.0", + "id" => 1, + "result" => { + "Platform" => "Core 500i", + "State" => "Active", + "DesignName" => "SAF‐MainPA", + "DesignCode" => "qALFilm6IcAz", + "IsRedundant" => false, + "IsEmulator" => true, + "Status" => { + "Code" => 0, + "String" => "OK", + }, + }, + }.to_json + "\0") + status[:platform].should eq("Core 500i") + status[:state].should eq("Active") + status[:design_name].should eq("SAF‐MainPA") + status[:design_code].should eq("qALFilm6IcAz") + status[:is_redundant].should eq(false) + status[:is_emulator].should eq(true) + status[:status].should eq({ + "Code" => 0, + "String" => "OK", + }) + + exec(:control_set, "MainGain", -12) + should_send({ + "jsonrpc" => "2.0", + "id" => 2, + "method" => "Control.Set", + "params" => { + "Name" => "MainGain", + "Value" => -12, + }, + }.to_json + "\0") + responds({ + "jsonrpc" => "2.0", + "id" => 1234, + "result" => [ + { + "Name" => "MainGain", + "Value" => -12, + }, + ], + }.to_json + "\0") + status[:faderMainGain_val].should eq(-12) + + exec(:component_get, "My APM", ["ent.xfade.gain", "ent.xfade.gain2"]) + should_send({ + "jsonrpc" => "2.0", + "id" => 3, + "method" => "Component.Get", + "params" => { + "Name" => "My APM", + "Controls" => [ + {"Name" => "ent.xfade.gain"}, + {"Name" => "ent.xfade.gain2"}, + ], + }, + }.to_json + "\0") + responds({ + "jsonrpc" => "2.0", + "result" => { + "Name" => "My APM", + "Controls" => [ + { + "Name" => "ent.xfade.gain", + "Value" => -100.0, + "String" => "‐100.0dB", + "Position" => 0, + }, + { + "Name" => "ent.xfade.gain2", + "Value" => -50.0, + "String" => "‐50.0dB", + "Position" => 0, + }, + ], + }, + }.to_json + "\0") + status["faderent.xfade.gain_My APM_pos"].should eq(0) + status["faderent.xfade.gain_My APM_val"].should eq(-100) + status["faderent.xfade.gain2_My APM_pos"].should eq(0) + status["faderent.xfade.gain2_My APM_val"].should eq(-50) + + exec(:change_group_add_controls, "my change group", ["some control", "another control"]) + should_send({ + "jsonrpc" => "2.0", + "id" => 4, + "method" => "ChangeGroup.AddControl", + "params" => { + "Id" => "my change group", + "Controls" => ["some control", "another control"] + }, + }.to_json + "\0") + responds({ + "jsonrpc" => "2.0", + "id" => 4, + "result" => { + "Id" => "my change group", + "Changes" => [ + { + "Name" => "some control", + "Value" => -12, + "String" => "‐12dB" + }, + { + "Name" => "another control", + "Value" => -6, + "String" => "‐6dB" + } + ] + } + }.to_json + "\0") +end diff --git a/drivers/samsung/displays/mdc_protocol.cr b/drivers/samsung/displays/mdc_protocol.cr new file mode 100644 index 00000000000..e071ff71a21 --- /dev/null +++ b/drivers/samsung/displays/mdc_protocol.cr @@ -0,0 +1,346 @@ +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/muteable" +require "placeos-driver/interface/switchable" + +class Samsung::Displays::MDCProtocol < PlaceOS::Driver + include Interface::Powerable + include Interface::Muteable + + INDICATOR = 0xAA_u8 + + enum Input + Vga = 0x14 # pc in manual + Dvi = 0x18 + DviVideo = 0x1F + Hdmi = 0x21 + HdmiPc = 0x22 + Hdmi2 = 0x23 + Hdmi2Pc = 0x24 + Hdmi3 = 0x31 + Hdmi3Pc = 0x32 + Hdmi4 = 0x33 + Hdmi4Pc = 0x34 + DisplayPort = 0x25 + Dtv = 0x40 + Media = 0x60 + Widi = 0x61 + MagicInfo = 0x20 + Whiteboard = 0x64 + end + + include Interface::InputSelection(Input) + + # Discovery Information + tcp_port 1515 + descriptive_name "Samsung MD, DM & QM Series LCD" + generic_name :Display + + # Markdown description + description <<-DESC + For DM displays configure the following 1: + + 1. Network Standby = ON + 2. Set Auto Standby = OFF + 3. Set Eco Solution, Auto Off = OFF + + Hard Power off displays each night and hard power ON in the morning. + DESC + + default_settings({ + display_id: 0, + rs232_control: false, + }) + + @id : UInt8 = 0 + @rs232 : Bool = false + @blank : Input? + @previous_volume : Int32 = 50 + @input_target : Input? = nil + @power_target : Bool? = nil + + def on_load + transport.tokenizer = Tokenizer.new do |io| + bytes = io.peek + # Ensure message indicator is well-formed + disconnect unless bytes.first == INDICATOR + logger.debug { "Received: #{bytes}" } + # [header, command, id, data.size, [data], checksum] + # return 0 if the message is incomplete + bytes.size < 4 ? 0 : bytes[3].to_i + 5 + end + + on_update + end + + def on_update + @id = setting(UInt8, :display_id) + @rs232 = setting(Bool, :rs232_control) + @blank = setting?(String, :blanking_input).try &->Input.parse(String) + end + + def connected + do_device_config unless self[:hard_off]?.try &.as_bool + + schedule.every(30.seconds, true) do + do_poll + end + end + + def disconnected + self[:power] = false unless @rs232 + schedule.clear + end + + # As true power off disconnects the server we only want to power off the panel + def power(state : Bool) + @power_target = state + + if state + # Power on + do_send(Command::HardOff, 1) + do_send(Command::PanelMute, 0) + else + # Blank the screen before turning off panel if required + # required by some video walls where screens are chained + if (blanking_input = @blank) && self[:power]? + switch_to(blanking_input) + end + do_send(Command::PanelMute, 1) + end + end + + def hard_off + do_send(Command::PanelMute, 0) if self[:power]?.try &.as_bool + do_send(Command::HardOff, 0) + end + + def power?(**options) : Bool + do_send(Command::PanelMute, Bytes.empty, **options).get + !!self[:power]?.try(&.as_bool) + end + + # Mutes both audio/video + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + mute_video(state) if layer.video? || layer.audio_video? + mute_audio(state) if layer.audio? || layer.audio_video? + end + + # Adds video mute state compatible with projectors + def mute_video(state : Bool = true) + state = state ? 1 : 0 + do_send(Command::PanelMute, state) + end + + # Emulate audio mute + def mute_audio(state : Bool = true) + # Do nothing if already in desired state + return if self[:audio_mute]?.try(&.as_bool) == state + self[:audio_mute] = state + if state + @previous_volume = self[:volume]?.try(&.as_i) || 0 + volume(0) + else + volume(@previous_volume) + end + end + + # check software version + def software_version + do_send(Command::SoftwareVersion) + end + + def serial_number + do_send(Command::SerialNumber) + end + + def switch_to(input : Input, **options) + @input_target = input + do_send(Command::Input, input.value, **options) + end + + enum SpeakerMode + Internal = 0 + External = 1 + end + + def speaker_select(mode : SpeakerMode, **options) + do_send(Command::Speaker, mode.value, **options) + end + + def do_poll + do_send(Command::Status, Bytes.empty, priority: 0) + power? unless self[:hard_off]?.try &.as_bool + end + + DEVICE_SETTINGS = { + network_standby: Bool, + auto_off_timer: Bool, + auto_power: Bool, + volume: Int32, + contrast: Int32, + brightness: Int32, + sharpness: Int32, + colour: Int32, + tint: Int32, + red_gain: Int32, + green_gain: Int32, + blue_gain: Int32, + } + {% for name, kind in DEVICE_SETTINGS %} + @[Security(Level::Administrator)] + def {{name.id}}(value : {{kind}}, **options) + {% if kind.resolve == Bool %} + state = value ? 1 : 0 + data = {{name.id.stringify}} == "auto_off_timer" ? Bytes[0x81, state] : state + {% elsif kind.resolve == Int32 %} + data = value.clamp(0, 100) + {% end %} + do_send(Command.parse({{name.id.stringify}}), data, **options) + end + {% end %} + + def do_device_config + {% for name, kind in DEVICE_SETTINGS %} + %value = setting?({{kind}}, {{name.id.stringify}}) + {{name.id}}(%value) unless %value.nil? + {% end %} + end + + enum ResponseStatus + Ack = 0x41 # A + Nak = 0x4e # N + end + + def received(data, task) + hex = data.hexstring + logger.debug { "Samsung sent: #{hex}" } + + # Verify the checksum of the response + if data[-1] != (checksum = data[1..-2].sum(0) & 0xFF) + logger.error { "Invalid response, checksum should be: #{checksum.to_s(16)}" } + return task.try &.retry + end + + status = ResponseStatus.from_value(data[4]) + command = Command.from_value(data[5]) + values = data[6..-2] + value = values.first + + case status + when .ack? + case command + when .status? + self[:hard_off] = hard_off = values[0] == 0 + self[:power] = false if hard_off + self[:volume] = values[1] + self[:audio_mute] = values[2] == 1 + self[:input] = Input.from_value(values[3]) + check_power_state + when .panel_mute? + self[:power] = value == 0 + check_power_state + when .volume? + self[:volume] = value + self[:audio_mute] = false if value > 0 + when .brightness? + self[:brightness] = value + when .input? + current_input = Input.from_value(value) + self[:input] = current_input + # The input feedback behaviour seems to go a little odd when + # screen split is active. Ignore any input forcing when on. + unless self[:screen_split]?.try &.as_bool + if current_input == @input_target + @input_target = nil + elsif input_target = @input_target + switch_to(input_target) + end + end + when .speaker? + self[:speaker] = SpeakerMode.from_value(value) + when .hard_off? + unless self[:hard_off]?.try &.as_bool + self[:hard_off] = hard_off = value == 0 + self[:power] = false if hard_off + end + when .screen_split? + self[:screen_split] = value >= 0 + when .software_version? + self[:software_version] = values.join + when .serial_number? + self[:serial_number] = values.join + else + logger.debug { "Samsung responded with ACK: #{value}" } + end + + task.try &.success + when .nak? + task.try &.abort("Samsung responded with NAK: #{hex}") + else + task.try &.retry + end + end + + private def check_power_state + if self[:power]? == @power_target + @power_target = nil + elsif power_target = @power_target + power(power_target) + end + end + + enum Command : UInt8 + Status = 0x00 + HardOff = 0x11 # Completely powers off + PanelMute = 0xF9 # Screen blanking / visual mute + Volume = 0x12 + Contrast = 0x24 + Brightness = 0x25 + Sharpness = 0x26 + Colour = 0x27 + Tint = 0x28 + RedGain = 0x29 + GreenGain = 0x2A + BlueGain = 0x2B + Input = 0x14 + Mode = 0x18 + Size = 0x19 + Pip = 0x3C # picture in picture + AutoAdjust = 0x3D + WallMode = 0x5C # Video wall mode + Safety = 0x5D + WallOn = 0x84 # Video wall enabled + WallUser = 0x89 # Video wall user control + Speaker = 0x68 + NetworkStandby = 0xB5 # Keep NIC active in standby, enable power on (without WOL) + AutoOffTimer = 0xE6 # Eco options (auto power off) + AutoPower = 0x33 # Device auto power control (presumably signal based?) + ScreenSplit = 0xB2 # Tri / quad split (larger panels only) + SoftwareVersion = 0x0E + SerialNumber = 0x0B + Time = 0xA7 + Timer = 0xA4 + + def build(id : UInt8, data : Bytes) : Bytes + Bytes.new(data.size + 5).tap do |bytes| + bytes[0] = INDICATOR # Header + bytes[1] = self.value # Command + bytes[2] = id # Display ID + bytes[3] = data.size.to_u8 # Data size + data.each_with_index(4) { |b, i| bytes[i] = b } # Data + bytes[-1] = (bytes[1..-2].sum(0) & 0xFF).to_u8 # Checksum + end + end + end + + private def do_send(command : Command, data : Int | Bytes = Bytes.empty, **options) + data = Bytes[data] if data.is_a?(Int) + bytes = command.build(@id, data) + logger.debug { "Sending to Samsung: #{bytes.hexstring}" } + send(bytes, **options) + end +end diff --git a/drivers/samsung/displays/mdc_protocol_spec.cr b/drivers/samsung/displays/mdc_protocol_spec.cr new file mode 100644 index 00000000000..e0a1800b261 --- /dev/null +++ b/drivers/samsung/displays/mdc_protocol_spec.cr @@ -0,0 +1,65 @@ +# [header, command, id, data.size, [data], checksum] + +DriverSpecs.mock_driver "Samsung::Displays::MDCProtocol" do + id = "\x00" + + # connected -> do_poll + # power? will take priority over status as status has priority = 0 + # power? -> panel_mute + should_send("\xAA\xF9#{id}\x00\xF9") + responds("\xAA\xFF#{id}\x03A\xF9\x00\x3C") + status[:power].should eq(true) + # status + should_send("\xAA\x00#{id}\x00\x00") + responds("\xAA\xFF#{id}\x09A\x00\x01\x06\x00\x14\x00\x00\x00\x64") + status[:hard_off].should eq(false) + status[:power].should eq(true) + status[:volume].should eq(6) + status[:audio_mute].should eq(false) + status[:input].should eq("Vga") + + exec(:volume, 24) + should_send("\xAA\x12#{id}\x01\x18\x2B") + responds("\xAA\xFF#{id}\x03A\x12\x18\x6D") + status[:volume].should eq(24) + status[:audio_mute].should eq(false) + + exec(:volume, 6) + should_send("\xAA\x12#{id}\x01\x06\x19") + responds("\xAA\xFF#{id}\x03A\x12\x06\x5B") + status[:volume].should eq(6) + status[:audio_mute].should eq(false) + + exec(:mute) + # Video mute + should_send("\xAA\xF9#{id}\x01\x01\xFB") + responds("\xAA\xFF#{id}\x03A\xF9\x01\x3D") + status[:power].should eq(false) + # Audio mute + should_send("\xAA\x12#{id}\x01\x00\x13") + responds("\xAA\xFF\x00\x03A\x12\x00\x55") + status[:audio_mute].should eq(true) + status[:volume].should eq(0) + + exec(:unmute) + # Video unmute + should_send("\xAA\xF9#{id}\x01\x00\xFA") + responds("\xAA\xFF#{id}\x03A\xF9\x00\x3C") + status[:power].should eq(true) + # Audio unmute + should_send("\xAA\x12#{id}\x01\x06\x19") + responds("\xAA\xFF#{id}\x03A\x12\x06\x5B") + status[:audio_mute].should eq(false) + status[:volume].should eq(6) + + exec(:switch_to, "hdmi") + should_send("\xAA\x14#{id}\x01\x21\x36") + responds("\xAA\xFF#{id}\x03A\x14\x21\x78") + status[:input].should eq("Hdmi") + + # power(false) == video_mute(true) + exec(:power, false) + should_send("\xAA\xF9#{id}\x01\x01\xFB") + responds("\xAA\xFF#{id}\x03A\xF9\x01\x3D") + status[:power].should eq(false) +end diff --git a/drivers/screen_technics/connect.cr b/drivers/screen_technics/connect.cr new file mode 100644 index 00000000000..a489cfa8149 --- /dev/null +++ b/drivers/screen_technics/connect.cr @@ -0,0 +1,182 @@ +module ScreenTechnics; end + +require "placeos-driver/interface/moveable" +require "placeos-driver/interface/stoppable" + +# Documentation: https://aca.im/driver_docs/Screen%20Technics/Screen%20Technics%20IP%20Connect%20module.pdf +# Default user: Admin +# Default pass: Connect + +class ScreenTechnics::Connect < PlaceOS::Driver + include Interface::Moveable + include Interface::Stoppable + + # Discovery Information + descriptive_name "Screen Technics Projector Screen Control" + generic_name :Screen + tcp_port 3001 + + COMMANDS = { + up: 30, + down: 33, + status: 1, # this differs from the doc, but appears to work + stop: 36, + } + + CMD_LOOKUP = { + 30 => :up, + 33 => :down, + 1 => :status, + 36 => :stop, + } + + def on_load + # Communication settings + queue.delay = 500.milliseconds + transport.tokenizer = Tokenizer.new("\r\n") + + on_update + end + + def on_update + @count = setting?(Int32, :screen_count) || 1 + end + + def connected + schedule.every(15.seconds, immediate: true) { + (0...@count).each { |index| query_state(index) } + } + end + + def disconnected + queue.clear + schedule.clear + end + + def move(position : MoveablePosition, index : Int32 | String = 0) + index = index.to_i + + case position + when MoveablePosition::Up + up(index) + when MoveablePosition::Down + down(index) + else + raise "invalid position requested" + end + end + + def down(index : Int32 = 0) + return if down?(index) + stop(index) + do_send :down, index, name: "direction#{index}" + query_state(index) + end + + def down?(index : Int32 = 0) + {"moving_bottom", "at_bottom"}.includes?(self["screen#{index}"]?) + end + + def up(index : Int32 = 0) + return if up?(index) + stop(index) + do_send :up, index, name: "direction#{index}" + query_state(index) + end + + def up?(index : Int32 = 0) + {"moving_top", "at_top"}.includes?(self["screen#{index}"]?) + end + + def stop(index : Int32 | String = 0, emergency : Bool = false) + index = index.to_i + + do_send( + :stop, index, + name: "stop#{index}", + clear_queue: emergency, + priority: emergency ? (queue.priority + 50) : queue.priority + ) + end + + def query_state(index : Int32 = 0) + do_send :status, index, 0x20 + end + + STATUS = { + 0 => :moving_top, + 1 => :moving_bottom, + 2 => :moving_preset_1, + 3 => :moving_preset_2, + 4 => :moving_top, # preset top + 5 => :moving_bottom, # preset bottom + 6 => :at_top, + 7 => :at_bottom, + 8 => :at_preset_1, + 9 => :at_preset_2, + 10 => :stopped, + 11 => :error, + # 12 => undefined + 13 => :error_timeout, + 14 => :error_current, + 15 => :error_rattle, + 16 => :at_bottom, # preset bottom + } + + def received(data, task) + data = String.new(data) + logger.debug { "Screen sent #{data}" } + + # Builds an array of numbers from the returned string + parts = data.split(/,/).map { |part| part.strip.to_i } + cmd = CMD_LOOKUP[parts[0] - 100]? + + if cmd + index = parts[2] - 17 + + case cmd + when :up + logger.debug { "Screen#{index} moving up" } + self["position#{index}"] = MoveablePosition::Up + self["moving#{index}"] = true + when :down + logger.debug { "Screen#{index} moving down" } + self["position#{index}"] = MoveablePosition::Down + self["moving#{index}"] = true + when :stop + logger.debug { "Screen#{index} stopped" } + self["moving#{index}"] = false + screen = "screen#{index}" + self[screen] = :stopped unless {"at_top", "at_bottom"}.includes?(self[screen]?) + when :status + self["screen#{index}"] = status = STATUS[parts[-1]] + + case status + when :moving_top, :at_top + self["position#{index}"] = MoveablePosition::Up + self["moving#{index}"] = status == :moving_top + when :moving_bottom, :at_bottom + self["position#{index}"] = MoveablePosition::Down + self["moving#{index}"] = status == :moving_bottom + when :stopped + self["moving#{index}"] = false + when :error, :error_timeout, :error_current, :error_rattle + self["moving#{index}"] = false + end + end + + task.try &.success + else + error = "Unknown command #{parts[0]}" + logger.debug { error } + task.try &.abort(error) + end + end + + protected def do_send(cmd, index = 0, *args, **options) + address = index + 17 + parts = {COMMANDS[cmd], address} + args + request = "#{parts.join(", ")}\r\n" + send request, **options + end +end diff --git a/drivers/screen_technics/connect_spec.cr b/drivers/screen_technics/connect_spec.cr new file mode 100644 index 00000000000..8f5bea2e626 --- /dev/null +++ b/drivers/screen_technics/connect_spec.cr @@ -0,0 +1,66 @@ +DriverSpecs.mock_driver "ScreenTechnics::Connect" do + # On connect it queries the state of all screens + should_send("1, 17, 32\r\n") + responds("101, 1, 17, 1\r\n") + + status[:position0].should eq("Down") + status[:moving0].should be_true + status[:screen0].should eq("moving_bottom") + + # Screen Technics requires a large delay between requests + # The timeout is because this execute won't occur until after a delay + exec(:query_state, index: 1) do |ret_val| + should_send("1, 18, 32\r\n", timeout: 1.second) + responds("101, 1, 18, 6\r\n") + + # Wait for the execute return value + ret_val.get + + status[:position1].should eq("Up") + status[:moving1].should eq(false) + status[:screen1].should eq("at_top") + end + + # =================== + # Test emergency stop + # =================== + exec(:move, "Down", 2) do |ret_val| + # A call to down involves a + # * stop command + # * down command + # * status request + sleep 1.second + should_send("36, 19\r\n", timeout: 1.second) + responds("136, 1, 19\r\n") + + # --> Wait for the down command + should_send("33, 19\r\n", timeout: 1.second) + + # Execute the emergency stop and request another down request + exec(:stop, index: 2, emergency: true) do |response| + exec(:move, "Down", 2) + sleep 500.milliseconds + + # --> respond to the down command + responds("133, 1, 19, 1\r\n") + + # Should receive emergency stop command + should_send("36, 19\r\n", timeout: 1.second) + responds("136, 1, 19\r\n") + status[:moving2].should eq(false) + response.get + end + + # Original down command should have failed + expect_raises(PlaceOS::Driver::RemoteException, "queue cleared (Abort)") do + ret_val.get + end + + puts "(timeout below expected)" + + # Ensure second down command is not sent + expect_raises(Channel::ClosedError) do + should_send("33, 17\r\n", timeout: 1.second) + end + end +end diff --git a/drivers/sharp/pn_series.cr b/drivers/sharp/pn_series.cr new file mode 100644 index 00000000000..25bafd99edf --- /dev/null +++ b/drivers/sharp/pn_series.cr @@ -0,0 +1,264 @@ +require "placeos-driver/interface/powerable" +require "placeos-driver/interface/muteable" +require "placeos-driver/interface/switchable" + +# Documentation: https://aca.im/driver_docs/Sharp/pnl601b.pdf +# also https://aca.im/driver_docs/Sharp/PN_L802B_operation_guide.pdf + +class Sharp::PnSeries < PlaceOS::Driver + include Interface::Powerable + include Interface::Muteable + + enum Input + DVI = 1 + HDMI = 10 + HDMI2 = 13 + HDMI3 = 18 + DisplayPort = 14 + VGA = 2 + VGA2 = 16 + Component = 3 + + def data + "INPS" + self.value.to_s.rjust(4, '0') + end + end + + include Interface::InputSelection(Input) + + tcp_port 10008 + descriptive_name "Sharp Monitor" + generic_name :Display + + @volume_min : Int32 = 0 + @volume_max : Int32 = 31 + @brightness_min : Int32 = 0 + @brightness_max : Int32 = 31 + @contrast_min : Int32 = 0 + @contrast_max : Int32 = 60 # multiply by two when VGA selected + @dbl_contrast : Bool = true + @model_number : Bool = false + + @vol_status : PlaceOS::Driver::Proxy::Scheduler::TaskWrapper? = nil + + DELIMITER = "\x0D\x0A" + + def on_load + transport.tokenizer = Tokenizer.new(DELIMITER) + end + + def connected + # Will be sent after login is requested (config - wait ready) + send_credentials + + schedule.every(60.seconds) do + logger.debug { "-- Polling Display" } + do_poll + end + end + + def disconnected + schedule.clear + end + + def power(state : Bool) + delay = self[:power_on_delay]?.try(&.as_i) || 5 + + # If the requested state is different from the current state + if state != !!self[:power]?.try(&.as_bool) + if state + logger.debug { "-- Sharp LCD, requested to power on" } + do_send("POWR 1", name: :POWR, timeout: delay.seconds + 15.seconds) + self[:warming] = true + self[:power] = true + do_send("POWR????", name: :POWR, timeout: 10.seconds) # clears warming + else + logger.debug { "-- Sharp LCD, requested to power off" } + do_send("POWR 0", name: :POWR, timeout: 15.seconds) + self[:power] = false + end + end + + power? + mute_status(0) + volume_status(0) + end + + def power?(**options) + do_send("POWR????", **options, name: :POWR, timeout: 10.seconds).get + self[:power].as_bool + end + + # Resets the brightness and contrast settings + def reset + do_send("ARST 2") + end + + def switch_to(input : Input) + logger.debug { "-- Sharp LCD, requested to switch to: #{input}" } + do_send(input.data, name: :input, delay: 2.seconds, timeout: 20.seconds).get # does an auto adjust on switch to vga + video_input(40) + brightness_status(40) # higher status than polling commands - lower than input switching (vid then audio is common) + contrast_status(40) + end + + AUDIO = { + audio1: "ASDP 2", + audio2: "ASDP 3", + dvi: "ASDP 1", + dvi_alt: "ASDA 1", + hdmi: "ASHP 0", + hdmi_3mm: "ASHP 1", + hdmi_rca: "ASHP 2", + vga: "ASAP 1", + component: "ASCA 1", + } + AUDIO_RESPONSE = AUDIO.to_h.invert + + def switch_audio(input : String) + logger.debug { "-- Sharp LCD, requested to switch audio to: #{input}" } + + do_send(AUDIO[input], name: "audio") + mute_status(40) # higher status than polling commands - lower than input switching + volume_status(40) # Mute response requests volume + end + + def auto_adjust + do_send("AGIN 1", timeout: 20.seconds) + end + + def brightness(val : Int32) + do_send("VLMP#{val.clamp(@brightness_min, @brightness_max).to_s.rjust(4, ' ')}") + end + + def contrast(val : Int32) + # See Sharp manual + multiplier = self[:input]? == "VGA" && @dbl_contrast ? 2 : 1 + val = val.clamp(@contrast_min, @contrast_max) * multiplier + do_send("CONT#{val.to_s.rjust(4, ' ')}") + end + + def volume(val : Int32) + @vol_status.try(&.cancel) + @vol_status = schedule.in(2.seconds) do + @vol_status = nil + volume_status + end + do_send("VOLM#{val.clamp(@volume_min, @volume_max).to_s.rjust(4, ' ')}") + end + + # There seems to only be audio mute available + def mute( + state : Bool = true, + index : Int32 | String = 0, + layer : MuteLayer = MuteLayer::AudioVideo + ) + if layer == MuteLayer::Video + logger.warn { "Sharp LCD requested to mute video which is unsupported" } + else + logger.debug { "Sharp LCD, requested to mute #{state}" } + do_send("MUTE #{state ? '1' : '0'}") + mute_status(50) # High priority mute status + end + end + + OPERATION_CODE = { + video_input: "INPS", + volume_status: "VOLM", + mute_status: "MUTE", + power_on_delay: "PWOD", + contrast_status: "CONT", + brightness_status: "VLMP", + model_number: "INF1", + } + {% for name, cmd in OPERATION_CODE %} + @[Security(Level::Administrator)] + def {{name.id}}(priority : Int32 = 0, **options) + data = {{cmd.id.stringify}} + "????" + logger.debug { "Sharp sending: #{data}" } + do_send(data, **options, priority: priority) # Status polling is a low priority + end + {% end %} + + def do_poll + if power? + model_number unless self[:model_number]? # only query the model number if we don't already have it + power_on_delay + mute_status + end + end + + private def determine_contrast_mode + # As of 09/2015 only the PN-L802B does not have double contrast on RGB input. + # All prior models do double the contrast and don't have an L so let's assume it's the L in the model number that determines this for now + # (we can confirm the logic as more models are released) + @dbl_contrast = false if self[:model_number].as_s.includes?('L') + logger.debug { "dbl_contrast is #{@dbl_contrast}" } + end + + private def send_credentials + do_send(setting?(String?, :username) || "", priority: 100, delay: 500.milliseconds) # , wait: false) + # TODO: figure out equivalent in crystal for delay_on_receive + do_send(setting?(String?, :password) || "", priority: 100) # , delay_on_receive: 1000) + end + + def received(data, task) + data = String.new(data[0..-3]) + logger.debug { "-- Sharp LCD, received: #{data}" } + + if data == "Password:OK" + return task.try(&.success("Login successful")) + elsif data == "Password:Login incorrect" + schedule.in(5.seconds) { send_credentials } + return task.try(&.success("Sharp LCD, bad login or logged off. Attempting login..")) + elsif data == "OK" + return task.try(&.success) + elsif data == "WAIT" + logger.debug { "-- Sharp LCD, wait" } + return + elsif data == "ERR" + return task.try(&.abort("-- Sharp LCD, error")) + elsif data.size < 8 # Out of order send? + return task.try(&.abort("Sharp sent out of order response: #{data}")) + end + + command, value = data.split + + case command + when "POWR" # Power status + self[:warming] = false + self[:power] = value.to_i > 0 + when "INPS" # Input status + input = Input.from_value?(value.to_i) + self[:input] = input || "unknown" + logger.debug { "-- Sharp LCD, input #{self[:input]} == #{value}" } + when "VOLM" # Volume status + self[:volume] = value.to_i unless self[:audio_mute]?.try(&.as_bool) + when "MUTE" # Mute status + self[:audio_mute] = (mute = value.to_i == 1) + if mute + self[:volume] = 0 + else + volume_status(90) # high priority + end + when "CONT" # Contrast status + self[:contrast] = value.to_i / (self[:input]? == "VGA" && @dbl_contrast ? 2 : 1) + when "VLMP" # brightness status + self[:brightness] = value.to_i + when "PWOD" + self[:power_on_delay] = value.to_i + when "INF1" + self[:model_number] = value + logger.debug { "-- Sharp LCD, model number #{self[:model_number]}" } + determine_contrast_mode + when "ASDP", "ASDA", "ASHP", "ASAP", "ASCA" # audio switching commands + self[:audio_input] = AUDIO_RESPONSE[data] || "unknown" + end + + task.try(&.success) + end + + private def do_send(data, delay = 100.milliseconds, **options) + send("#{data}#{DELIMITER}", **options, delay: delay) + end +end diff --git a/drivers/sharp/pn_series_spec.cr b/drivers/sharp/pn_series_spec.cr new file mode 100644 index 00000000000..4a268fa8a46 --- /dev/null +++ b/drivers/sharp/pn_series_spec.cr @@ -0,0 +1,75 @@ +DriverSpecs.mock_driver "Sharp::PnSeries" do + # connected + # send_credentials + should_send("\x0D\x0A") + responds("OK\x0D\x0A") + should_send("\x0D\x0A") + responds("Password:Login incorrect\x0D\x0A") + + # Settings can only be accessed after on_load and connected + settings({ + username: "user", + password: "pass", + }) + + # Retrying send_credentials + sleep 5 + should_send("user\x0D\x0A") + responds("OK\x0D\x0A") + should_send("pass\x0D\x0A") + responds("Password:OK\x0D\x0A") + + exec(:do_poll) + should_send("POWR????\x0D\x0A") + responds("POWR 001\x0D\x0A") + status[:warming].should eq(false) + status[:power].should eq(true) + should_send("INF1????\x0D\x0A") + responds("INF1 P802B\x0D\x0A") + status[:model_number].should eq("P802B") + should_send("PWOD????\x0D\x0A") + responds("PWOD 002\x0D\x0A") + status[:power_on_delay].should eq(2) + should_send("MUTE????\x0D\x0A") + responds("MUTE 000\x0D\x0A") + status[:audio_mute].should eq(false) + should_send("VOLM????\x0D\x0A") + responds("VOLM 010\x0D\x0A") + status[:volume].should eq(10) + + exec(:switch_to, "hdmi") + should_send("INPS0010\x0D\x0A") + responds("WAIT\x0D\x0A") + responds("OK\x0D\x0A") + sleep 2 + should_send("INPS????\x0D\x0A") + responds("INPS 10\x0D\x0A") + status[:input].should eq("HDMI") + should_send("VLMP????\x0D\x0A") + responds("VLMP 15\x0D\x0A") + status[:brightness].should eq(15) + should_send("CONT????\x0D\x0A") + responds("CONT 20\x0D\x0A") + status[:contrast].should eq(20) + + exec(:switch_audio, "component") + should_send("ASCA 1\x0D\x0A") + responds("ASCA 1\x0D\x0A") + status[:audio_input].should eq("component") + + exec(:volume, 100) + should_send("VOLM 31\x0D\x0A") + responds("ASCA 1\x0D\x0A") + + exec(:power, false) + should_send("POWR 0\x0D\x0A") + responds("OK\x0D\x0A") + should_send("POWR????\x0D\x0A") + responds("POWR 0\x0D\x0A") + status[:warming].should eq(false) + status[:power].should eq(false) + should_send("MUTE????\x0D\x0A") + responds("MUTE 1\x0D\x0A") + status[:audio_mute].should eq(true) + status[:volume].should eq(0) +end diff --git a/drivers/sony/camera/cgi_protocol.cr b/drivers/sony/camera/cgi_protocol.cr new file mode 100644 index 00000000000..d5a20e2a4ff --- /dev/null +++ b/drivers/sony/camera/cgi_protocol.cr @@ -0,0 +1,316 @@ +require "placeos-driver/interface/camera" + +module Sony; end + +module Sony::Camera; end + +# Documentation: https://aca.im/driver_docs/Sony/sony-camera-CGI-Commands-1.pdf + +class Sony::Camera::CGI < PlaceOS::Driver + # include Interface::Powerable + include Interface::Camera + + # Discovery Information + generic_name :Camera + descriptive_name "Sony Camera HTTP CGI Protocol" + + default_settings({ + basic_auth: { + username: "admin", + password: "Admin_1234", + }, + invert_controls: false, + presets: { + name: {pan: 1, tilt: 1, zoom: 1}, + }, + }) + + enum Movement + Idle + Moving + Unknown + end + + def on_load + # Configure the constants + @pantilt_speed = -100..100 + self[:pan_speed] = self[:tilt_speed] = {min: -100, max: 100, stop: 0} + self[:has_discrete_zoom] = true + + schedule.every(60.seconds) { query_status } + schedule.in(5.seconds) do + query_status + info? + end + on_update + end + + @invert_controls = false + @presets = {} of String => NamedTuple(pan: Int32, tilt: Int32, zoom: Int32) + + def on_update + self[:invert_controls] = @invert_controls = setting?(Bool, :invert_controls) || false + @presets = setting?(Hash(String, NamedTuple(pan: Int32, tilt: Int32, zoom: Int32)), :presets) || {} of String => NamedTuple(pan: Int32, tilt: Int32, zoom: Int32) + self[:presets] = @presets.keys + end + + # 24bit twos complement + private def twos_complement(value) + if value > 0 + value > 0x80000 ? -(((~(value & 0xFFFFF)) + 1) & 0xFFFFF) : value + else + ((~(-value & 0xFFFFF)) + 1) & 0xFFFFF + end + end + + private def query(path, **opts, &block : Hash(String, String) -> _) + queue(**opts) do |task| + response = get(path) + data = response.body.not_nil! + + raise "unexpected response #{response.status_code}\n#{response.body}" unless response.success? + + # convert data into more consumable state + state = {} of String => String + data.split("&").each do |key_value| + parts = key_value.strip.split("=") + state[parts[0]] = parts[1] + end + + result = block.call(state) + task.success result + end + end + + # Temporary values until the camera is queried + @moving = false + @zooming = false + @max_speed = 1 + + def query_status(priority : Int32 = 0) + # Response looks like: + # AbsolutePTZF=15400,fd578,0000,cbde&PanMovementRange=eac00,15400 + query("/command/inquiry.cgi?inq=ptzf", priority: priority) do |response| + # load the current state + response.each do |key, value| + case key + when "AbsolutePTZF" + # Pan, Tilt, Zoom,Focus + # AbsolutePTZF=15400,fd578,0000,ca52 + parts = value.split(",") + self[:pan] = @pan = twos_complement parts[0].to_i(16) + self[:tilt] = @tilt = twos_complement parts[1].to_i(16) + self[:zoom] = @zoom = parts[2].to_i(16) + when "PanMovementRange" + # PanMovementRange=eac00,15400 + parts = value.split(",") + pan_min = twos_complement parts[0].to_i(16) + pan_max = twos_complement parts[1].to_i(16) + @pan_range = pan_min..pan_max + self[:pan_range] = {min: pan_min, max: pan_max} + when "TiltMovementRange" + # TiltMovementRange=fc400,b400 + parts = value.split(",") + tilt_min = twos_complement parts[0].to_i(16) + tilt_max = twos_complement parts[1].to_i(16) + @tilt_range = tilt_min..tilt_max + self[:tilt_range] = {min: tilt_min, max: tilt_max} + when "ZoomMovementRange" + # min, max, digital + # ZoomMovementRange=0000,4000,7ac0 + parts = value.split(",") + zoom_min = parts[0].to_i(16) + zoom_max = parts[1].to_i(16) + @zoom_range = zoom_min..zoom_max + self[:zoom_range] = {min: zoom_min, max: zoom_max} + when "PtzfStatus" + # PtzfStatus=idle,idle,idle,idle + parts = value.split(",").map { |state| Movement.parse(state) }[0..2] + self[:moving] = @moving = parts.includes?(Movement::Moving) + + # when "AbsoluteZoom" + # # AbsoluteZoom=609 + # self[:zoom] = @zoom = value.to_i(16) + + # NOTE:: These are not required as speeds are scaled + # + # when "ZoomMaxVelocity" + # # ZoomMaxVelocity=8 + # @zoom_speed = 1..value.to_i(16) + + when "PanTiltMaxVelocity" + # PanTiltMaxVelocity=24 + @max_speed = value.to_i(16) + end + end + + response + end + end + + def info? + query("/command/inquiry.cgi?inq=system", priority: 0) do |response| + response.each do |key, value| + if {"ModelName", "Serial", "SoftVersion", "ModelForm", "CGIVersion"}.includes?(key) + self[key.underscore] = value + end + end + response + end + end + + private def action(path, **opts, &block : HTTP::Client::Response -> _) + queue(**opts) do |task| + response = get(path) + raise "request error #{response.status_code}\n#{response.body}" unless response.success? + + result = block.call(response) + task.success result + end + end + + # Implement Stoppable interface + def stop(index : Int32 | String = 0, emergency : Bool = false) + # indexes start at 1 on sony cameras + index = index.to_i + 1 + + action("/command/ptzf.cgi?Move=stop,motor,image#{index}", + priority: 999, + name: "moving", + clear_queue: emergency + ) do + zoom ZoomDirection::Stop if @zooming + self[:moving] = @moving = false + query_status + end + end + + # Implement Moveable interface + def move(position : MoveablePosition, index : Int32 | String = 0) + # indexes start at 1 on sony cameras + index = index.to_i + 1 + + case position + when MoveablePosition::Up, MoveablePosition::Down, + MoveablePosition::Left, MoveablePosition::Right + # Tilt, Pan + if @invert_controls && (position.up? || position.down?) + position = position.up? ? MoveablePosition::Down : MoveablePosition::Up + end + + action("/command/ptzf.cgi?Move=#{position.to_s.downcase},0,image#{index}", + name: "moving" + ) { self[:moving] = @moving = true } + when MoveablePosition::In + zoom ZoomDirection::In + when MoveablePosition::Out + zoom ZoomDirection::Out + else + raise "unsupported direction: #{position}" + end + end + + macro in_range(range, value) + {{value}} = if {{range}}.includes? {{value}} + {{value}} + else + {{value}} < {{range}}.begin ? {{range}}.begin : {{range}}.end + end + {{value}} = twos_complement({{value}}) + end + + def pantilt(pan : Int32, tilt : Int32, zoom : Int32? = nil) : Nil + in_range @pan_range, pan + in_range @tilt_range, tilt + + if zoom + in_range @zoom_range, zoom + + action("/command/ptzf.cgi?AbsolutePTZF=#{pan.to_s(16)},#{tilt.to_s(16)},#{zoom.to_s(16)}", + name: "position" + ) do + self[:pan] = @pan = pan + self[:tilt] = @tilt = tilt + self[:zoom] = @zoom = zoom.not_nil! + end + else + action("/command/ptzf.cgi?AbsolutePanTilt=#{pan.to_s(16)},#{tilt.to_s(16)},#{@max_speed.to_s(16)}", + name: "position" + ) do + self[:pan] = @pan = pan + self[:tilt] = @tilt = tilt + end + end + end + + # Implement Camera interface + def joystick(pan_speed : Int32, tilt_speed : Int32, index : Int32 | String = 0) + index = index.to_i + 1 + range = -100..100 + in_range range, pan_speed + in_range range, tilt_speed + + tilt_speed = -tilt_speed if @invert_controls && tilt_speed != 0 + + action("/command/ptzf.cgi?ContinuousPanTiltZoom=#{pan_speed.to_s(16)},#{tilt_speed.to_s(16)},0,image#{index}", + name: "moving" + ) do + self[:moving] = @moving = (pan_speed != 0 || tilt_speed != 0) + query_status if !@moving + @moving + end + end + + def zoom_to(position : Int32, auto_focus : Bool = true, index : Int32 | String = 0) + index = index.to_i + 1 + + in_range @zoom_range, position + action("/command/ptzf.cgi?AbsoluteZoom=#{position.to_s(16)}", + name: "zooming" + ) { self[:zoom] = @zoom = position } + end + + def zoom(direction : ZoomDirection, index : Int32 | String = 0) + index = index.to_i + 1 + + if direction.stop? + action("/command/ptzf.cgi?Move=stop,zoom,image#{index}", + priority: 999, + name: "zooming" + ) { self[:zooming] = @zooming = false } + else + action("/command/ptzf.cgi?Move=#{direction.out? ? "wide" : "near"},0,image#{index}", + name: "zooming" + ) { self[:zooming] = @zooming = true } + end + end + + def home + action("/command/presetposition.cgi?HomePos=ptz-recall", + name: "position" + ) { query_status } + end + + def recall(position : String, index : Int32 | String = 0) + preset = @presets[position]? + if preset + pantilt **preset + else + raise "unknown preset #{position}" + end + end + + def save_position(name : String, index : Int32 | String = 0) + @presets[name] = { + pan: @pan, tilt: @tilt, zoom: @zoom, + } + # TODO:: persist this to the database + self[:presets] = @presets.keys + end + + def delete_position(name : String, index : Int32 | String = 0) + @presets.delete name + # TODO:: persist this to the database + self[:presets] = @presets.keys + end +end diff --git a/drivers/sony/camera/cgi_protocol_spec.cr b/drivers/sony/camera/cgi_protocol_spec.cr new file mode 100644 index 00000000000..755d7ff0b2e --- /dev/null +++ b/drivers/sony/camera/cgi_protocol_spec.cr @@ -0,0 +1,18 @@ +DriverSpecs.mock_driver "Floorsense::Desks" do + # Send the request + retval = exec(:query_status) + + # We should request a new token from Floorsense + expect_http_request do |_request, response| + response.status_code = 200 + response.output.puts %(AbsolutePTZF=15400,fd578,0000,cb5a&PanMovementRange=eac00,15400&PanPanoramaRange=de00,2200&PanTiltMaxVelocity=24&PtzInstance=1&TiltMovementRange=fc400,b400&TiltPanoramaRange=fc00,1200&ZoomMaxVelocity=8&ZoomMovementRange=0000,4000,7ac0&PtzfStatus=idle,idle,idle,idle&AbsoluteZoom=609) + end + + # What the function should return (for use in making further requests) + retval.get.not_nil!["AbsoluteZoom"].should eq("609") + status[:pan].should eq(87040) + status[:pan_range].should eq({"min" => -87040, "max" => 87040}) + + status[:tilt].should eq(-10888) + status[:tilt_range].should eq({"min" => -15360, "max" => 46080}) +end diff --git a/drivers/steinel/hpd2.cr b/drivers/steinel/hpd2.cr new file mode 100644 index 00000000000..9ffd72e3bcb --- /dev/null +++ b/drivers/steinel/hpd2.cr @@ -0,0 +1,126 @@ +module Steinel; end + +class Steinel::HPD2 < PlaceOS::Driver + # Discovery Information + generic_name :PeopleCounter + descriptive_name "Steinel HPD-2" + + # Local network + uri_base "https://192.168.0.20" + + default_settings({ + basic_auth: { + username: "admin", + password: "steinel", + }, + }) + + def on_load + on_update + end + + def on_update + schedule.every(5.seconds) { get_status } + end + + def get_status + response = get("/api/sensorstatus.php") + + logger.debug { "received #{response.body}" } + + if response.success? + SensorStatus.from_json(response.body.not_nil!) + else + raise "unexpected response #{response.status_code}\n#{response.body}" + end + end + + class SensorStatus + include JSON::Serializable + + @[JSON::Field(key: "AppVersion")] + property app_version : String + + @[JSON::Field(key: "FpgaVersion")] + property fpga_version : String + + @[JSON::Field(key: "KnxSapNumber")] + property knx_sap_number : String + + @[JSON::Field(key: "KnxVersion")] + property knx_version : String + + @[JSON::Field(key: "KnxAddr")] + property knx_address : String + + @[JSON::Field(key: "GitRevision")] + property git_revision : String + + @[JSON::Field(key: "ModelName")] + property model_name : String + + @[JSON::Field(key: "FrameProcessingTimeMs")] + property frame_processing_time_ms : Int32 + + @[JSON::Field(key: "AverageFps5")] + property average_fps5 : Float64 + + @[JSON::Field(key: "AverageFps50")] + property average_fps50 : Float64 + + @[JSON::Field(key: "RunningTimeHHMMSS")] + property running_time : String + + @[JSON::Field(key: "UptimeHHMMSS")] + property uptime : String + + @[JSON::Field(key: "IrLedOn")] + property ir_led_on : Int32 + + @[JSON::Field(key: "DetectedPersons")] + property detected_persons : Int32 + + @[JSON::Field(key: "PersonPresence")] + property person_presence : Int32 + + @[JSON::Field(key: "DetectedPersonsZone")] + property detected_persons_zone : Array(Int32) + + @[JSON::Field(key: "PersonPresenceZone")] + property person_presence_zone : Array(Int32) + + @[JSON::Field(key: "DetectionZonesPresent")] + property detection_zones_present : Int32 + + @[JSON::Field(key: "GlobalIlluminanceLux")] + property global_illuminance_lux : Float64 + + @[JSON::Field(key: "LuxZone")] + property lux_zone : Array(Float64) + + @[JSON::Field(key: "GlobalLightValue")] + property global_light_value : Int32 + + @[JSON::Field(key: "ArmsensorCpuUsage")] + property arm_sensor_cpu_usage : String + + @[JSON::Field(key: "WebServerCpuUsage")] + property web_server_cpu_usage : String + + @[JSON::Field(key: "Temperature")] + property temperature : String + + @[JSON::Field(key: "Humidity")] + property humidity : String + + @[JSON::Field(key: "KnxDetected")] + property knx_detected : String + + @[JSON::Field(key: "KnxProgramMode")] + property knx_program_mode : String + + @[JSON::Field(key: "KnxLedState")] + property knx_led_state : String + property final : String + end +end diff --git a/drivers/steinel/hpd2_spec.cr b/drivers/steinel/hpd2_spec.cr new file mode 100644 index 00000000000..f9b1a7adc17 --- /dev/null +++ b/drivers/steinel/hpd2_spec.cr @@ -0,0 +1,27 @@ +DriverSpecs.mock_driver "Xovis::SensorAPI" do + # Send the request + retval = exec(:get_status) + data = %({"AppVersion": "3.2.3", "FpgaVersion": "v300", "KnxSapNumber": "0", "KnxVersion": "0", "KnxAddr": + "", "GitRevision": "d45734c2", "ModelName": "15_2xroute_fix26", "FrameProcessingTimeMs": 1179, + "AverageFps5": 0.850314, "AverageFps50": 0.855873, "RunningTimeHHMMSS": "672:55:58", + "UptimeHHMMSS": "672:56:35", "IrLedOn": 0, "DetectedPersons": 0, "PersonPresence": 0, + "DetectedPersonsZone": [0, 0, 0, 0, 0], "PersonPresenceZone": [0, 0, 0, 0, 0], + "DetectionZonesPresent": 0, "GlobalIlluminanceLux": 39.0, "LuxZone": [0.0, 0.0, 0.0, 0.0, 0.0], + "GlobalLightValue": 72, "ArmsensorCpuUsage": "20", "WebServerCpuUsage": "2", "Temperature": + "27.745661", "Humidity": "25.286158", "KnxDetected": "0", "KnxProgramMode": "0", "KnxLedState": + "0", "final": "OK" }) + + # We should request a new token from Floorsense + expect_http_request do |request, response| + if request.headers["Authorization"]? == "Basic YWRtaW46c3RlaW5lbA==" + response.status_code = 200 + response.output.puts data + else + puts request.headers.inspect + response.status_code = 401 + end + end + + # What the function should return (for use in making further requests) + retval.get.should eq(JSON.parse(data)) +end diff --git a/drivers/vergesense/location_service.cr b/drivers/vergesense/location_service.cr new file mode 100644 index 00000000000..60614ad96c7 --- /dev/null +++ b/drivers/vergesense/location_service.cr @@ -0,0 +1,117 @@ +module Vergesense; end + +require "json" +require "oauth2" +require "placeos-driver/interface/locatable" +require "./models" + +class Vergesense::LocationService < PlaceOS::Driver + include Interface::Locatable + + descriptive_name "Vergesense Location Service" + generic_name :VergesenseLocationService + description %(collects desk booking data from the staff API and overlays Vergesense data for visualising on a map) + + accessor area_manager : AreaManagement_1 + accessor vergesense : Vergesense_1 + + default_settings({ + floor_mappings: { + "vergesense_building_id-floor_id": { + building_id: "zone-building", + level_id: "zone-level", + name: "friendly name for documentation", + }, + }, + }) + + @floor_mappings : Hash(String, NamedTuple(building_id: String?, level_id: String)) = {} of String => NamedTuple(building_id: String?, level_id: String) + @zone_filter : Array(String) = [] of String + @building_mappings : Hash(String, String?) = {} of String => String? + + def on_load + on_update + end + + def on_update + @floor_mappings = setting(Hash(String, NamedTuple(building_id: String?, level_id: String)), :floor_mappings) + @zone_filter = @floor_mappings.values.map do |z| + level = z[:level_id] + @building_mappings[level] = z[:building_id] + level + end + + bind_floor_status + end + + # =================================== + # Bindings into Vergesense data + # =================================== + protected def bind_floor_status + subscriptions.clear + + @floor_mappings.each do |floor_id, details| + zone_id = details[:level_id] + vergesense.subscribe(floor_id) do |_sub, payload| + level_state_change(zone_id, Floor.from_json(payload)) + end + end + end + + # Zone_id => Floor + @occupancy_mappings : Hash(String, Floor) = {} of String => Floor + + protected def level_state_change(zone_id, floor) + @occupancy_mappings[zone_id] = floor + area_manager.update_available({zone_id}) + rescue error + logger.error(exception: error) { "error updating level #{zone_id} space changes" } + end + + # =================================== + # Locatable Interface functions + # =================================== + def locate_user(email : String? = nil, username : String? = nil) + logger.debug { "sensor incapable of locating #{email} or #{username}" } + [] of Nil + end + + def macs_assigned_to(email : String? = nil, username : String? = nil) : Array(String) + logger.debug { "sensor incapable of tracking #{email} or #{username}" } + [] of String + end + + def check_ownership_of(mac_address : String) : OwnershipMAC? + logger.debug { "sensor incapable of tracking #{mac_address}" } + nil + end + + def device_locations(zone_id : String, location : String? = nil) + logger.debug { "searching locatable in zone #{zone_id}" } + return [] of Nil unless @zone_filter.includes?(zone_id) + + floor = @occupancy_mappings[zone_id]? + return [] of Nil unless floor + + floor.spaces.compact_map do |space| + loc_type = space.space_type == "desk" ? "desk" : "area" + next if location.presence && location != loc_type + + people_count = space.people.try(&.count) + + if people_count && people_count > 0 + { + location: loc_type, + at_location: people_count, + map_id: space.name, + level: zone_id, + building: @building_mappings[zone_id]?, + capacity: space.capacity, + + vergesense_space_id: space.space_ref_id, + vergesense_space_type: space.space_type, + } + end + end + end +end diff --git a/drivers/vergesense/location_service_spec.cr b/drivers/vergesense/location_service_spec.cr new file mode 100644 index 00000000000..71ca9d59af8 --- /dev/null +++ b/drivers/vergesense/location_service_spec.cr @@ -0,0 +1,64 @@ +DriverSpecs.mock_driver "Vergesense::LocationService" do + system({ + Vergesense: {Vergesense}, + AreaManagement: {AreaManagement}, + }) + + resp = exec(:device_locations, "zone-level").get + resp.should eq([ + {"location" => "area", "at_location" => 21, "map_id" => "Conference Room 0721", "level" => "zone-level", "building" => "zone-building", "capacity" => 30, "vergesense_space_id" => "CR_0721", "vergesense_space_type" => "conference_room"}, + {"location" => "desk", "at_location" => 1, "map_id" => "desk-1234", "level" => "zone-level", "building" => "zone-building", "capacity" => 1, "vergesense_space_id" => "CR_0722", "vergesense_space_type" => "desk"}, + ]) +end + +class Vergesense < DriverSpecs::MockDriver + def on_load + self["vergesense_building_id-floor_id"] = { + "floor_ref_id" => "floor_id", + "name" => "Floor 1", + "capacity" => 84, + "max_capacity" => 60, + "spaces" => [ + { + "building_ref_id" => "vergesense_building_id", + "floor_ref_id" => "floor_id", + "space_ref_id" => "CR_0721", + "space_type" => "conference_room", + "name" => "Conference Room 0721", + "capacity" => 30, + "max_capacity" => 32, + "geometry" => {"type" => "Polygon", "coordinates" => [[[93.850772, 44.676952], [93.850739, 44.676929], [93.850718, 44.67695], [93.850751, 44.676973], [93.850772, 44.676952], [93.850772, 44.676952]]]}, + "people" => { + "count" => 21, + "coordinates" => [[[2.2673, 4.3891], [6.2573, 1.5303]]], + }, + "timestamp" => "2019-08-21T21:10:25Z", + "motion_detected" => true, + }, + { + "building_ref_id" => "vergesense_building_id", + "floor_ref_id" => "floor_id", + "space_ref_id" => "CR_0722", + "space_type" => "desk", + "name" => "desk-1234", + "capacity" => 1, + "max_capacity" => 1, + "geometry" => {"type" => "Polygon", "coordinates" => [[[93.850772, 44.676952], [93.850739, 44.676929], [93.850718, 44.67695], [93.850751, 44.676973], [93.850772, 44.676952], [93.850772, 44.676952]]]}, + "people" => { + "count" => 1, + "coordinates" => [[[2.2673, 4.3891], [6.2573, 1.5303]]], + }, + "timestamp" => "2019-08-21T21:10:25Z", + "motion_detected" => true, + }, + ], + } + end +end + +class AreaManagement < DriverSpecs::MockDriver + def update_available(zones : Array(String)) + logger.info { "requested update to #{zones}" } + nil + end +end diff --git a/drivers/vergesense/models.cr b/drivers/vergesense/models.cr new file mode 100644 index 00000000000..36f72e4f68b --- /dev/null +++ b/drivers/vergesense/models.cr @@ -0,0 +1,63 @@ +require "json" + +# Vergesense Data Models +module Vergesense + struct Building + include JSON::Serializable + + property name : String + property building_ref_id : String + property address : String? + end + + struct BuildingWithFloors + include JSON::Serializable + + property building_ref_id : String + property floors : Array(Floor) + end + + struct Floor + include JSON::Serializable + + property floor_ref_id : String + property name : String + property capacity : UInt32? + property max_capacity : UInt32? + property spaces : Array(Space) + end + + class Space + include JSON::Serializable + + property building_ref_id : String? + property floor_ref_id : String? + property space_ref_id : String + property space_type : String? + property name : String? + property capacity : UInt32? + property max_capacity : UInt32? + property geometry : Geometry? + property people : People? + property timestamp : String? + property motion_detected : Bool? + + def floor_key + "#{building_ref_id}-#{floor_ref_id}".strip + end + end + + struct Geometry + include JSON::Serializable + + property type : String + property coordinates : Array(Array(Array(Float64))) + end + + struct People + include JSON::Serializable + + property count : UInt32? + property coordinates : Array(Array(Array(Float64)))? + end +end diff --git a/drivers/vergesense/vergesense_api.cr b/drivers/vergesense/vergesense_api.cr new file mode 100644 index 00000000000..4cbbde1435f --- /dev/null +++ b/drivers/vergesense/vergesense_api.cr @@ -0,0 +1,151 @@ +module Vergesense; end + +require "./models" + +class Vergesense::VergesenseAPI < PlaceOS::Driver + # Discovery Information + descriptive_name "Vergesense API" + generic_name :Vergesense + uri_base "https://api.vergesense.com" + description "for more information visit: https://vergesense.readme.io/" + + default_settings({ + vergesense_api_key: "VS-API-KEY", + }) + + @api_key : String = "" + + @buildings : Array(Building) = [] of Building + @floors : Hash(String, Floor) = {} of String => Floor + + @debug_payload : Bool = false + @poll_every : Time::Span? = nil + @sync_lock : Mutex = Mutex.new + + def on_load + on_update + schedule.in(200.milliseconds) { init_sync } + end + + def on_update + @api_key = setting(String, :vergesense_api_key) + @debug_payload = setting?(Bool, :debug_payload) || false + + @poll_every = setting?(Int32, :poll_every).try &.seconds + + schedule.clear + if poll_time = @poll_every + schedule.every(poll_time) { init_sync } + end + end + + # Performs initial sync by loading buildings / floors / spaces + def init_sync + begin + @sync_lock.synchronize do + init_buildings + + if @buildings + init_floors + init_spaces + init_floors_status + end + end + rescue e + logger.error { "failed to perform vergesense API sync\n#{e.inspect_with_backtrace}" } + end + end + + EMPTY_HEADERS = {} of String => String + SUCCESS_RESPONSE = {HTTP::Status::OK, EMPTY_HEADERS, nil} + + # Webhook endpoint for space_report API, expects version 2 + def space_report_api(method : String, headers : Hash(String, Array(String)), body : String) + logger.debug { "space_report API received: #{method},\nheaders #{headers},\nbody size #{body.size}" } + logger.debug { body } if @debug_payload + + # Parse the data posted + begin + remote_space = Space.from_json(body) + logger.debug { "parsed vergesense payload" } + + update_floor_space(remote_space) + update_single_floor_status(remote_space.floor_key, @floors[remote_space.floor_key]?) + rescue e + logger.error { "failed to parse vergesense space_report API payload\n#{e.inspect_with_backtrace}" } + logger.debug { "failed payload body was\n#{body}" } + end + + # Return a 200 response + SUCCESS_RESPONSE + end + + private def init_buildings + @buildings = Array(Building).from_json(get_request("/buildings")) + end + + private def init_floors + @buildings.not_nil!.each do |building| + building_with_floors = BuildingWithFloors.from_json(get_request("/buildings/#{building.building_ref_id}")) + if building_with_floors + building_with_floors.floors.each do |floor| + floor_key = "#{building.building_ref_id}-#{floor.floor_ref_id}".strip + @floors[floor_key] = floor + end + end + end + @floors + end + + private def init_spaces + spaces = Array(Space).from_json(get_request("/spaces")) + spaces.each do |remote_space| + update_floor_space(remote_space) + end + + spaces + end + + private def init_floors_status + @floors.each do |floor_key, floor| + update_single_floor_status(floor_key, floor) + end + end + + private def update_single_floor_status(floor_key, floor) + if floor_key && floor + self[floor_key] = floor.not_nil! + end + end + + # Finds a space on a given floor and updates it in place. + private def update_floor_space(remote_space) + floor = @floors[remote_space.floor_key]? + if floor + floor_space = floor.spaces.find { |space| space.space_ref_id == remote_space.space_ref_id } + if floor_space + floor_space.building_ref_id = remote_space.building_ref_id + floor_space.floor_ref_id = remote_space.floor_ref_id + floor_space.people = remote_space.people + floor_space.motion_detected = remote_space.motion_detected + floor_space.timestamp = remote_space.timestamp + end + end + end + + private def get_request(path) + begin + response = get(path, + headers: { + "vs-api-key" => @api_key, + } + ) + + if response.success? + response.body + else + raise "unexpected response #{response.status_code}\n#{response.body}" + end + end + end +end diff --git a/drivers/vergesense/vergesense_api_spec.cr b/drivers/vergesense/vergesense_api_spec.cr new file mode 100644 index 00000000000..5c2fa9e6b2c --- /dev/null +++ b/drivers/vergesense/vergesense_api_spec.cr @@ -0,0 +1,200 @@ +DriverSpecs.mock_driver "Vergesense::VergesenseAPI" do + expect_http_request do |request, response| + case request.path + when "/buildings" + response.status_code = 200 + response << %([{ + "name": "HQ 1", + "building_ref_id": "HQ1", + "address": null + }]) + end + end + + puts "SENT BUILDINGS" + + expect_http_request do |request, response| + case request.path + when "/buildings/HQ1" + response.status_code = 200 + response << %({ + "building_ref_id": "HQ1", + "capacity": 84, + "minimum_social_distance": 2.0, + "floors": [ + { + "name": "Floor 1", + "floor_ref_id": "FL1", + "capacity": 84, + "max_capacity": 60, + "spaces": [ + { + "name": "Conference Room 0721", + "space_ref_id": "CR_0721", + "space_type": "conference_room", + "capacity": 4, + "max_capacity": 3, + "sensors": [ + { + "id": "L_000018", + "partitions": [ + { + "id": "L_000018/321" + } + ] + } + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 93.850772, + 44.676952 + ], + [ + 93.850739, + 44.676929 + ], + [ + 93.850718, + 44.67695 + ], + [ + 93.850751, + 44.676973 + ], + [ + 93.850772, + 44.676952 + ], + [ + 93.850772, + 44.676952 + ] + ] + ] + } + } + ] + } + ] +}) + end + end + + puts "SENT FLOORS" + + expect_http_request do |request, response| + case request.path + when "/spaces" + response.status_code = 200 + response << %([ + { + "building_ref_id": "HQ1", + "floor_ref_id": "FL1", + "space_ref_id": "CR_0721", + "name": "Conference Room 0721", + "space_type": "conference_room", + "last_reports": [ + { + "id": "W91-IGI", + "person_count": 2, + "signs_of_life": null, + "motion_detected": null, + "timestamp": "2019-07-29T18:42:19Z" + } + ], + "people": { + "count": 2, + "distances": { + "units": "meters", + "values": [2.42] + } + } + } +]) + end + end + + puts "SENT SPACES" + + status["HQ1-FL1"].should eq({ + "floor_ref_id" => "FL1", + "name" => "Floor 1", + "capacity" => 84, + "max_capacity" => 60, + "spaces" => [ + { + "building_ref_id" => "HQ1", + "floor_ref_id" => "FL1", + "space_ref_id" => "CR_0721", + "space_type" => "conference_room", + "name" => "Conference Room 0721", + "capacity" => 4, + "max_capacity" => 3, + "geometry" => {"type" => "Polygon", "coordinates" => [[[93.850772, 44.676952], [93.850739, 44.676929], [93.850718, 44.67695], [93.850751, 44.676973], [93.850772, 44.676952], [93.850772, 44.676952]]]}, + "people" => {"count" => 2}, + }, + ], + }) + + # Testing webhook save + webhook_space_report_event = %({ + "building_ref_id": "HQ1", + "floor_ref_id": "FL1", + "space_ref_id": "CR_0721", + "sensor_ids": ["VS0-123", "VS1-321"], + "person_count": 21, + "signs_of_life": null, + "motion_detected": true, + "event_type": "space_report", + "timestamp": "2019-08-21T21:10:25Z", + "people": { + "count": 21, + "coordinates": [ + [ + [ + 2.2673, + 4.3891 + ], + [ + 6.2573, + 1.5303 + ] + ] + ], + "distances": { + "units": "meters", + "values": [1.5] + } + } + }) + + exec(:space_report_api, method: "update", headers: {"test" => ["test"]}, body: webhook_space_report_event).get + + status["HQ1-FL1"].should eq({ + "floor_ref_id" => "FL1", + "name" => "Floor 1", + "capacity" => 84, + "max_capacity" => 60, + "spaces" => [ + { + "building_ref_id" => "HQ1", + "floor_ref_id" => "FL1", + "space_ref_id" => "CR_0721", + "space_type" => "conference_room", + "name" => "Conference Room 0721", + "capacity" => 4, + "max_capacity" => 3, + "geometry" => {"type" => "Polygon", "coordinates" => [[[93.850772, 44.676952], [93.850739, 44.676929], [93.850718, 44.67695], [93.850751, 44.676973], [93.850772, 44.676952], [93.850772, 44.676952]]]}, + "people" => { + "count" => 21, + "coordinates" => [[[2.2673, 4.3891], [6.2573, 1.5303]]], + }, + "timestamp" => "2019-08-21T21:10:25Z", + "motion_detected" => true, + }, + ], + }) +end diff --git a/drivers/whispir/messages.cr b/drivers/whispir/messages.cr new file mode 100644 index 00000000000..24e42b32a7f --- /dev/null +++ b/drivers/whispir/messages.cr @@ -0,0 +1,59 @@ +module Whispir; end + +# Documentation: https://whispir.github.io/api/#messages +require "placeos-driver/interface/sms" + +class Whispir::Messages < PlaceOS::Driver + include Interface::SMS + + # Discovery Information + generic_name :SMS + descriptive_name "Whispir messages service" + uri_base "https://api.au.whispir.com" + + # For whatever reason, you need both basic auth and an API key + default_settings({ + basic_auth: { + username: "username", + password: "password", + }, + api_key: "12345", + }) + + def on_load + on_update + end + + @api_key : String = "" + + def on_update + @api_key = setting(String, :api_key) + end + + def send_sms( + phone_numbers : String | Array(String), + message : String, + format : String? = "SMS", + source : String? = nil + ) + phone_numbers = [phone_numbers] unless phone_numbers.is_a?(Array) + + response = post("/messages?apikey=#{@api_key}", body: { + to: phone_numbers.join(";"), + # As far as I can tell, this field is not passed to the recipients + subject: "PlaceOS Notification", + body: message, + }.to_json, headers: { + "Content-Type" => "application/vnd.whispir.message-v1+json", + "Accept" => "application/vnd.whispir.message-v1+json", + "x-api-key" => @api_key, + }) + + raise "request failed with #{response.status_code}" unless response.status_code == 202 + + location = response.headers["Location"]? + logger.debug { "message sent: #{location}" } + + location + end +end diff --git a/drivers/whispir/messages_spec.cr b/drivers/whispir/messages_spec.cr new file mode 100644 index 00000000000..8d654bc90e3 --- /dev/null +++ b/drivers/whispir/messages_spec.cr @@ -0,0 +1,30 @@ +DriverSpecs.mock_driver "Whispir::Messages" do + # Send the request + retval = exec(:send_sms, + phone_numbers: "+61418419954", + message: "hello steve" + ) + + # sms should send a HTTP request + expect_http_request do |request, response| + headers = request.headers + io = request.body + if io + data = io.gets_to_end + request = JSON.parse(data) + if request["to"] == "+61418419954" && + headers["x-api-key"]? == "12345" && + headers["Authorization"]? == "Basic #{Base64.strict_encode("username:password")}" + response.status_code = 202 + response.headers["Location"] = "https://api.au.whispir.com/messages/id" + else + response.status_code = 401 + end + else + raise "expected request to include dialing details #{request.inspect}" + end + end + + # What the sms function should return + retval.get.should eq("https://api.au.whispir.com/messages/id") +end diff --git a/drivers/xovis/sensor_api.cr b/drivers/xovis/sensor_api.cr new file mode 100644 index 00000000000..c5e9bcc9a6d --- /dev/null +++ b/drivers/xovis/sensor_api.cr @@ -0,0 +1,206 @@ +module Xovis; end + +require "xml" + +class Xovis::SensorAPI < PlaceOS::Driver + # Discovery Information + generic_name :XovisSensor + descriptive_name "Xovis Flow Sensor" + + uri_base "https://192.168.0.1" + + default_settings({ + basic_auth: { + username: "account", + password: "password!", + }, + poll_rate: 15, + }) + + def on_load + on_update + end + + @poll_rate : Time::Span = 15.seconds + + def on_update + @poll_rate = (setting?(Int32, :poll_rate) || 15).seconds + schedule.clear + schedule.every(@poll_rate) do + count_data + capacity_data + end + schedule.every(5.minutes) { device_status } + schedule.in(5.seconds) do + count_data + capacity_data + device_status + end + end + + # Alternative to using basic auth, but here really only for testing with postman + @[Security(Level::Support)] + def get_token + response = get("/api/auth/token", headers: {"Accept" => "text"}) + raise "issue obtaining token: #{response.status_code}\n#{response.body}" unless response.success? + response.body + end + + @[Security(Level::Support)] + def get_logs + response = get("/api/info/log", headers: {"Accept" => "text"}) + raise "issue obtaining logs: #{response.status_code}\n#{response.body}" unless response.success? + response.body + end + + @[Security(Level::Support)] + def reset_count + response = get("/api/count-data/reset", headers: {"Accept" => "text/xml"}) + check_success(response) + true + end + + def is_alive? + response = get("/api/info/alive", headers: {"Accept" => "text/xml"}) + check_success(response) + true + rescue + false + end + + def count_data + response = get("/api/count-data", headers: {"Accept" => "text/xml"}) + document = check_success(response) + + lines = {} of String => NamedTuple(name: String, id: String, type: String, sensor: String, data: Hash(String, String | Int32 | Float32)) + lines_xml = document.xpath_nodes("//lines/line") + + self[:lines] = lines_xml.map do |line| + attrs = {} of String => String | Hash(String, Int32) + counts = {} of String => Int32 + line.attributes.each { |attr| attrs[attr.name] = attr.content } + line.children.each { |child| + next if child.name == "text" + counts[child.name] = child.text.to_i + } + attrs["counts"] = counts + attrs + end + end + + def capacity_data + response = get("/api/info/persistence", headers: {"Accept" => "text/xml"}) + document = check_success(response) + + {"line", "zone-occupancy", "zone-in-out"}.each do |count_name| + xml_key_name = "//count-#{count_name}-storage" + if count_data = document.xpath_nodes(xml_key_name).first? + count_type = count_name.split("-", 2)[0] + capacity = xpath_text(document, "#{xml_key_name}/capacity", &.to_i) + + self["#{count_name}-counts"] = document.xpath_nodes("#{xml_key_name}/count-#{count_type}s/count-#{count_type}").map do |zone| + attrs = {} of String => String | Int32 | Time | Nil + + zone.children.each do |child| + content = child.text.strip + attrs[child.name] = case child.name + when "entry-count" + content.to_i + when "first-entry", "last-entry" + content.empty? ? nil : Time.parse!(content, "%Y-%m-%dT%H:%M:%S%z") + when "text" + next + else + content + end + end + + attrs["capacity"] = capacity + attrs + end + end + end + true + end + + # Combined `/info` and `/info/status` + def device_status + response = get("/api/info/sensor-status", headers: {"Accept" => "text/xml"}) + document = check_success(response) + + parse_type_info(document, "version") + parse_type_info(document, "temperature") + + parse_text_info(document, "sensor") + parse_text_info(document, "illumination") + parse_text_info(document, "configuration") + parse_text_info(document, "operation") + + true + end + + protected def xpath_text(document, path) + document.xpath_nodes(path).first?.try(&.text.strip) + end + + protected def xpath_text(document, path) + if node = document.xpath_nodes(path).first? + yield node.text.strip + end + end + + protected def parse_type_info(document, xpath_key) : Nil + ver_data = document.xpath_nodes("//#{xpath_key}s/#{xpath_key}") + attrs = {} of String => String + ver_data.each do |data| + key = data.attributes.select { |attr| attr.name == "type" }.first?.try &.content + next unless key + attrs[key] = data.text.strip + end + self[xpath_key] = attrs.empty? ? nil : attrs + end + + protected def parse_text_info(document, status) : Nil + if keys = document.xpath_nodes("//#{status}").first?.try(&.children) + attrs = {} of String => String + keys.each do |data| + key = data.name + next if key == "text" + attrs[key.underscore] = data.text.strip + end + self[status] = attrs.empty? ? nil : attrs + else + self[status] = nil + end + end + + protected def check_success(response) + raise "issue with request: #{response.status_code}\n#{response.body}" unless response.success? + document = parse_without_namespaces(response.body) + status = document.xpath_nodes("//request-status/status").first?.try &.text.strip + raise "request failed with #{status}\n#{response.body}" unless status == "OK" + sensor_time(document) + document + end + + protected def sensor_time(document) : Time? + if time_text = document.xpath_nodes("//sensor-time").first?.try &.text + self[:sensor_time] = Time.parse!(time_text, "%Y-%m-%dT%H:%M:%S%z") + end + end + + protected def parse_without_namespaces(xml : String) + xml = xml.strip + document = XML.parse(xml) + namespace_node = document.children[0].name == "xml" ? document.children[1].name : document.children[0].name + namespaces = document.children[0].namespaces.keys.compact_map { |name| name.starts_with?("xmlns:") ? "#{name[6..-1]}\\:" : nil } + + # Clean up namespaces from node names + xml = xml.gsub(Regex.new(namespaces.join("|")), "") + # Replace namespace node + xml = xml.sub(Regex.new("<#{namespace_node}.+>"), "<#{namespace_node}>") + + # Return the parsed document + XML.parse(xml) + end +end diff --git a/drivers/xovis/sensor_api_spec.cr b/drivers/xovis/sensor_api_spec.cr new file mode 100644 index 00000000000..118f0a63062 --- /dev/null +++ b/drivers/xovis/sensor_api_spec.cr @@ -0,0 +1,261 @@ +DriverSpecs.mock_driver "Xovis::SensorAPI" do + # ========================= + # GET TOKEN + # ========================= + retval = exec(:get_token) + + # We should request a new token from Floorsense + expect_http_request do |request, response| + auth = request.headers["Authorization"] + if auth == "Basic YWNjb3VudDpwYXNzd29yZCE=" + response.status_code = 200 + response.output << "jwt_token" + else + response.status_code = 401 + puts "invalid auth header #{auth}" + end + end + + # What the function should return (for use in making further requests) + retval.get.should eq("jwt_token") + + # ========================= + # RESET COUNT + # ========================= + retval = exec(:reset_count) + + # We should request a new token from Floorsense + expect_http_request do |request, response| + response.status_code = 200 + response.output << %( + + 2020-05-04T12:34:46Z + + OK + + ) + end + + # What the function should return (for use in making further requests) + retval.get.should eq(true) + status["sensor_time"].should eq("2020-05-04T12:34:46Z") + + # ========================= + # COUNT DATA + # ========================= + retval = exec(:count_data) + + # We should request a new token from Floorsense + expect_http_request do |request, response| + response.status_code = 200 + response.output << %( + + + + + 0 + 0 + + + + 2020-05-05T11:20:47+02:00 + + OK + + ) + end + + # What the function should return (for use in making further requests) + line_data = [{ + "name" => "Line 0", + "id" => "0", + "sensor-type" => "SINGLE_SENSOR", + "counts" => { + "fw-count" => 0, + "bw-count" => 0, + }, + }] + retval.get.should eq(line_data) + status["lines"].should eq(line_data) + + # ========================= + # DEVICE INFO + # ========================= + retval = exec(:device_status) + + # We should request a new token from Floorsense + expect_http_request do |request, response| + response.status_code = 200 + response.output << <<-XML + + + OK + 2020-05-05T13:11:38+02:00 81551360 + + 5 + AB E + B + 1.2.12 4.3.1 (5b57718) + + 80:1F:12:73:2F:A4 192.168.1.115 + S01 + Test + PC2S + PC2S + + 49.0 + 46.0 + + 8.0 1.20166 0.0 0.0139272 true + + true 85.823784 8.23138 + 0.143171 0.98707 0.0720739 + + false + + false 492810AF623279442C87D2D65D4A6240 2020-05-05T12:22:10+02:00 false + + tracking 1018951 1 true Europe/Zurich + + OK + + XOVIS-PC + 192.168.1.115 255.255.255.0 192.168.1.1 + 192.168.1.1 + + false + false + + true 2020-05-05T13:06:58+02:00 91 false + + + sensor-support.xovis.com:443 true + true true true true 2020-05-05T02:11:07+02:002020-05-05T02:02:53+02:00false + + + XML + end + + # What the function should return (for use in making further requests) + retval.get.should eq(true) + status["version"].should eq({ + "HW" => "5", + "PROD" => "AB", + "BOM" => "E", + "PCB" => "B", + "FW" => "1.2.12", + "SW" => "4.3.1 (5b57718)", + }) + status["temperature"].should eq({ + "die" => "49.0", + "housing" => "46.0", + }) + status["sensor"].should eq({ + "serial-number" => "80:1F:12:73:2F:A4", + "ip-address" => "192.168.1.115", + "name" => "S01", + "group" => "Test", + "type" => "PC2S", + "device-type" => "PC2S", + }) + + # ========================= + # ALIVE CHECK + # ========================= + retval = exec(:is_alive?) + + # We should request a new token from Floorsense + expect_http_request do |request, response| + response.status_code = 200 + response.output << %( + + 2020-05-04T12:34:46Z + + OK + + ) + end + + # What the function should return (for use in making further requests) + retval.get.should eq(true) + + # ========================= + # CAPACITY DATA + # ========================= + retval = exec(:capacity_data) + + # We should request a new token from Floorsense + expect_http_request do |request, response| + response.status_code = 200 + response.output << %( + + 2020-05-05T13:06:06+02:00 + + OK + + + 100 + + + Line 0 + 0 + 2020-04-27T11:40:00+02:00 + 2020-05-05T13:05:00+02:00 + 9 + + + + + 80 + + + Zone 0 + 0 + 2020-05-05T12:22:00+02:00 + 2020-05-05T13:05:00+02:00 + 1 + + + + + 80 + + + Zone 0 + 0 + 2020-05-05T12:22:00+02:00 + 2020-05-05T13:05:00+02:00 + 1 + + + + ) + end + + # What the function should return (for use in making further requests) + retval.get.should eq(true) + status["line-counts"].should eq([{ + "name" => "Line 0", + "id" => "0", + "first-entry" => "2020-04-27T11:40:00+02:00", + "last-entry" => "2020-05-05T13:05:00+02:00", + "entry-count" => 9, + "capacity" => 100, + }]) + status["zone-occupancy-counts"].should eq([{ + "name" => "Zone 0", + "id" => "0", + "first-entry" => "2020-05-05T12:22:00+02:00", + "last-entry" => "2020-05-05T13:05:00+02:00", + "entry-count" => 1, + "capacity" => 80, + }]) + status["zone-in-out-counts"].should eq([{ + "name" => "Zone 0", + "id" => "0", + "first-entry" => "2020-05-05T12:22:00+02:00", + "last-entry" => "2020-05-05T13:05:00+02:00", + "entry-count" => 1, + "capacity" => 80, + }]) +end diff --git a/drivers/xy_sense/location_service.cr b/drivers/xy_sense/location_service.cr new file mode 100644 index 00000000000..6bd8d296c5a --- /dev/null +++ b/drivers/xy_sense/location_service.cr @@ -0,0 +1,189 @@ +module XYSense; end + +require "json" +require "oauth2" +require "placeos-driver/interface/locatable" + +class XYSense::LocationService < PlaceOS::Driver + include Interface::Locatable + + descriptive_name "XY Sense Locations" + generic_name :XYLocationService + description %(collects desk booking data from the staff API and overlays XY Sense data for visualising on a map) + + accessor area_manager : AreaManagement_1 + accessor xy_sense : XYSense_1 + bind XYSense_1, :floors, :floor_details_changed + + default_settings({ + floor_mappings: { + "xy-sense-floor-id": { + zone_id: "placeos-zone-id", + name: "friendly name for documentation", + }, + }, + }) + + @floor_mappings : Hash(String, NamedTuple(zone_id: String)) = {} of String => NamedTuple(zone_id: String) + @zone_filter : Array(String) = [] of String + + def on_load + on_update + end + + def on_update + @floor_mappings = setting(Hash(String, NamedTuple(zone_id: String)), :floor_mappings) + @zone_filter = @floor_mappings.map { |_, detail| detail[:zone_id] } + end + + # =================================== + # Bindings into xy-sense data + # =================================== + class FloorDetails + include JSON::Serializable + + property floor_id : String + property floor_name : String + property location_id : String + property location_name : String + + property spaces : Array(SpaceDetails) + end + + class SpaceDetails + include JSON::Serializable + + property id : String + property name : String + property capacity : Int32 + property category : String + end + + class Occupancy + include JSON::Serializable + + property status : String + property headcount : Int32 + property space_id : String + + @[JSON::Field(converter: Time::Format.new("%FT%T", Time::Location::UTC))] + property collected : Time + + @[JSON::Field(ignore: true)] + property! details : SpaceDetails + end + + # Floor id => subscription + @floor_subscriptions = {} of String => PlaceOS::Driver::Subscriptions::Subscription + @space_details = {} of String => SpaceDetails + @change_lock = Mutex.new + + protected def floor_details_changed(_sub = nil, payload = nil) + @change_lock.synchronize do + # Get the floor details from either the status push event or module update + floors = payload ? Hash(String, FloorDetails).from_json(payload) : xy_sense.status(Hash(String, FloorDetails), :floors) + space_details = {} of String => SpaceDetails + + # work out what we should be watching + monitor = {} of String => String + floors.each do |floor_id, floor| + mapping = @floor_mappings[floor_id]? + next unless mapping + + monitor[floor_id] = mapping[:zone_id] + + # track space data + floor.spaces.each { |space| space_details[space.id] = space } + end + + # unsubscribe from floors we're not interested in + existing = @floor_subscriptions.keys + desired = monitor.keys + (existing - desired).each { |sub| subscriptions.unsubscribe @floor_subscriptions.delete(sub).not_nil! } + + # update to new space details + @space_details = space_details + + # Subscribe to new data + (desired - existing).each { |floor_id| + zone_id = monitor[floor_id] + @floor_subscriptions[floor_id] = xy_sense.subscribe(floor_id) do |_sub, payload| + level_state_change(zone_id, Array(Occupancy).from_json(payload)) + end + } + end + end + + # Zone_id => area => occupancy details + @occupancy_mappings : Hash(String, Hash(String, Occupancy)) = {} of String => Hash(String, Occupancy) + + def level_state_change(zone_id : String, spaces : Array(Occupancy)) + area_occupancy = {} of String => Occupancy + spaces.each do |space| + space.details = @space_details[space.space_id] + area_occupancy[space.details.name] = space + end + @occupancy_mappings[zone_id] = area_occupancy + area_manager.update_available({zone_id}) + rescue error + logger.error(exception: error) { "error updating level #{zone_id} space changes" } + end + + # =================================== + # Locatable Interface functions + # =================================== + def locate_user(email : String? = nil, username : String? = nil) + logger.debug { "sensor incapable of locating #{email} or #{username}" } + [] of Nil + end + + def macs_assigned_to(email : String? = nil, username : String? = nil) : Array(String) + logger.debug { "sensor incapable of tracking #{email} or #{username}" } + [] of String + end + + def check_ownership_of(mac_address : String) : OwnershipMAC? + logger.debug { "sensor incapable of tracking #{mac_address}" } + nil + end + + def device_locations(zone_id : String, location : String? = nil) + logger.debug { "searching locatable in zone #{zone_id}" } + return [] of Nil unless @zone_filter.includes?(zone_id) + + @occupancy_mappings[zone_id].compact_map do |space_name, space| + # Assume this means we're looking at a desk + capacity = space.details.capacity + if capacity == 1 + next unless space.headcount > 0 + next if location.presence && location != "desk" + { + location: :desk, + at_location: space.headcount, + map_id: space_name, + level: zone_id, + capacity: capacity, + + xy_sense_space_id: space.space_id, + xy_sense_status: space.status, + xy_sense_collected: space.collected.to_unix, + xy_sense_category: space.details.category, + } + else + next if location.presence && location != "area" + { + location: :area, + at_location: space.headcount, + map_id: space_name, + level: zone_id, + capacity: capacity, + + xy_sense_space_id: space.space_id, + xy_sense_status: space.status, + xy_sense_collected: space.collected.to_unix, + xy_sense_category: space.details.category, + } + end + end + end +end diff --git a/drivers/xy_sense/location_service_spec.cr b/drivers/xy_sense/location_service_spec.cr new file mode 100644 index 00000000000..651a4531733 --- /dev/null +++ b/drivers/xy_sense/location_service_spec.cr @@ -0,0 +1,76 @@ +DriverSpecs.mock_driver "XYSense::LocationService" do + system({ + XYSense: {XYSense}, + AreaManagement: {AreaManagement}, + }) + + now = Time.local + start = now.at_beginning_of_day.to_unix + ending = now.at_end_of_day.to_unix + + resp = exec(:device_locations, "placeos-zone-id").get + puts resp + resp.should eq([ + {"location" => "desk", "at_location" => 1, "map_id" => "desk-456", "level" => "placeos-zone-id", "capacity" => 1, "xy_sense_space_id" => "xysense-desk-456-id", "xy_sense_status" => "recentlyOccupied", "xy_sense_collected" => 1605088820, "xy_sense_category" => "Workpoint"}, + {"location" => "area", "at_location" => 8, "map_id" => "area-567", "level" => "placeos-zone-id", "capacity" => 20, "xy_sense_space_id" => "xysense-area-567-id", "xy_sense_status" => "currentlyOccupied", "xy_sense_collected" => 1605088820, "xy_sense_category" => "Lobby"}, + ]) +end + +class XYSense < DriverSpecs::MockDriver + def on_load + self[:floors] = { + "xy-sense-floor-id" => { + floor_id: "xy-sense-floor-id", + floor_name: "Fancy floor", + location_id: "xysense-building", + location_name: "Fancy building", + spaces: [{ + id: "xysense-desk-123-id", + name: "desk-123", + capacity: 1, + category: "Workpoint", + }, + { + id: "xysense-desk-456-id", + name: "desk-456", + capacity: 1, + category: "Workpoint", + }, + { + id: "xysense-area-567-id", + name: "area-567", + capacity: 20, + category: "Lobby", + }], + }, + } + + self["xy-sense-floor-id"] = [ + { + status: "notOccupied", + headcount: 0, + space_id: "xysense-desk-123-id", + collected: "2020-11-11T10:00:20", + }, + { + status: "recentlyOccupied", + headcount: 1, + space_id: "xysense-desk-456-id", + collected: "2020-11-11T10:00:20", + }, + { + status: "currentlyOccupied", + headcount: 8, + space_id: "xysense-area-567-id", + collected: "2020-11-11T10:00:20", + }, + ] + end +end + +class AreaManagement < DriverSpecs::MockDriver + def update_available(zones : Array(String)) + logger.info { "requested update to #{zones}" } + nil + end +end diff --git a/src/models/.keep b/repositories/.keep similarity index 100% rename from src/models/.keep rename to repositories/.keep diff --git a/shard.lock b/shard.lock new file mode 100644 index 00000000000..c7a8377f438 --- /dev/null +++ b/shard.lock @@ -0,0 +1,235 @@ +# NOTICE: This lockfile contains some overrides from shard.override.yml +version: 2.0 +shards: + CrystalEmail: + git: https://github.com/Nephos/CrystalEmail.git + version: 0.2.4 + + action-controller: + git: https://github.com/spider-gazelle/action-controller.git + version: 4.1.0 + + active-model: + git: https://github.com/spider-gazelle/active-model.git + version: 2.0.3 + + bindata: + git: https://github.com/spider-gazelle/bindata.git + version: 1.8.1 + + bisect: + git: https://github.com/spider-gazelle/bisect.git + version: 1.2.0 + + connect-proxy: + git: https://github.com/spider-gazelle/connect-proxy.git + version: 1.2.0 + + crc16: + git: https://github.com/maiha/crc16.cr.git + version: 0.1.0 + + cron_parser: + git: https://github.com/kostya/cron_parser.git + version: 0.3.0 + + db: + git: https://github.com/crystal-lang/crystal-db.git + version: 0.10.0 + + debug: + git: https://github.com/Sija/debug.cr.git + version: 2.0.0 + + email: + git: https://github.com/arcage/crystal-email.git + version: 0.6.2 + + etcd: + git: https://github.com/place-labs/crystal-etcd.git + version: 1.0.7 + + exec_from: + git: https://github.com/place-labs/exec_from.git + version: 1.2.0 + + future: + git: https://github.com/crystal-community/future.cr.git + version: 0.1.0 + + google: + git: https://github.com/PlaceOS/google.git + version: 3.0.0 + + habitat: + git: https://github.com/luckyframework/habitat.git + version: 0.4.5 + + hound-dog: + git: https://github.com/place-labs/hound-dog.git + version: 2.6.0 + + http-params-serializable: + git: https://github.com/caspiano/http-params-serializable.git + version: 0.4.1+git.commit.1610a206f676269c1b862281cda3ed82548ced34 + + ipaddress: + git: https://github.com/Sija/ipaddress.cr.git + version: 0.2.1 + + json_mapping: + git: https://github.com/crystal-lang/json_mapping.cr.git + version: 0.1.0 + + jwt: + git: https://github.com/crystal-community/jwt.git + version: 1.5.0 + + link-header: + git: https://github.com/spider-gazelle/link-header.git + version: 1.0.1 + + log_helper: + git: https://github.com/spider-gazelle/log_helper.git + version: 1.0.2 + + lucky_router: + git: https://github.com/luckyframework/lucky_router.git + version: 0.4.1 + + murmur3: + git: https://github.com/aca-labs/murmur3.git + version: 0.1.1+git.commit.7cbe25c0ca8d052c9d98c377c824dcb0e038c790 + + neuroplastic: + git: https://github.com/spider-gazelle/neuroplastic.git + version: 1.7.1 + + ntlm: + git: https://github.com/spider-gazelle/ntlm.git + version: 1.0.0 + + office365: + git: https://github.com/PlaceOS/office365.git + version: 1.12.1 + + openssl_ext: + git: https://github.com/spider-gazelle/openssl_ext.git + version: 2.1.0 + + pars: + git: https://github.com/place-labs/pars.git + version: 1.1.0+git.commit.16bf2c909110cc148818f7af298afeded538123c + + pinger: + git: https://github.com/spider-gazelle/pinger.git + version: 1.1.0 + + place_calendar: + git: https://github.com/PlaceOS/calendar.git + version: 4.9.1 + + placeos: + git: https://github.com/placeos/crystal-client.git + version: 2.2.0 + + placeos-compiler: + git: https://github.com/placeos/compiler.git + version: 3.3.1 + + placeos-driver: + git: https://github.com/placeos/driver.git + version: 4.2.0 + + placeos-models: + git: https://github.com/placeos/models.git + version: 4.12.1 + + pool: + git: https://github.com/ysbaddaden/pool.git + version: 0.2.3 + + priority-queue: + git: https://github.com/spider-gazelle/priority-queue.git + version: 1.0.0+git.commit.bde47d11cb4b4d1ed7680a7f417ff707c434b1e4 + + promise: + git: https://github.com/spider-gazelle/promise.git + version: 2.2.1 + + protobuf: + git: https://github.com/jeromegn/protobuf.cr.git + version: 2.2.2 + + qr-code: + git: https://github.com/spider-gazelle/qr-code.git + version: 1.0.1 + + redis: + git: https://github.com/maiha/crystal-redis.git + version: 2.6.0 + + redis-cluster: + git: https://github.com/caspiano/redis-cluster.cr.git + version: 0.8.4 + + rendezvous-hash: + git: https://github.com/caspiano/rendezvous-hash.git + version: 0.3.1 + + rethinkdb: + git: https://github.com/kingsleyh/crystal-rethinkdb.git + version: 0.2.1 + + rethinkdb-orm: + git: https://github.com/spider-gazelle/rethinkdb-orm.git + version: 3.2.2 + + retriable: # Overridden + git: https://github.com/sija/retriable.cr.git + version: 0.2.2 + + rwlock: + git: https://github.com/spider-gazelle/readers-writer.git + version: 1.0.6 + + s2_cells: + git: https://github.com/spider-gazelle/s2_cells.git + version: 1.0.0 + + simple_retry: + git: https://github.com/spider-gazelle/simple_retry.git + version: 1.1.0 + + ssh2: + git: https://github.com/spider-gazelle/ssh2.cr.git + version: 1.5.1 + + stumpy_core: + git: https://github.com/stumpycr/stumpy_core.git + version: 1.9.1 + + stumpy_png: + git: https://github.com/stumpycr/stumpy_png.git + version: 5.0.0 + + tasker: + git: https://github.com/spider-gazelle/tasker.git + version: 2.0.2 + + telnet: + git: https://github.com/spider-gazelle/telnet.cr.git + version: 1.0.1 + + tokenizer: + git: https://github.com/spider-gazelle/tokenizer.git + version: 1.1.0 + + ulid: + git: https://github.com/place-labs/ulid.git + version: 0.1.2 + + yaml_mapping: + git: https://github.com/crystal-lang/yaml_mapping.cr.git + version: 0.1.0 + diff --git a/shard.override.yml b/shard.override.yml new file mode 100644 index 00000000000..6c8a374874c --- /dev/null +++ b/shard.override.yml @@ -0,0 +1,4 @@ +# NOTE:: here because of a shards ambiguous sources error +dependencies: + retriable: + git: https://github.com/sija/retriable.cr.git diff --git a/shard.yml b/shard.yml index f1cea9bcc5b..e5b187179e1 100644 --- a/shard.yml +++ b/shard.yml @@ -1,18 +1,67 @@ -name: app -version: 1.0.0 +name: drivers +version: 2.2.0 + +# compile target +targets: + test-harness: + main: src/app.cr dependencies: action-controller: github: spider-gazelle/action-controller - active-model: - github: spider-gazelle/active-model - # https://github.com/jeromegn/kilt - # Generic template interface for Crystal - kilt: - github: jeromegn/kilt + email: + github: arcage/crystal-email -# compile target -targets: - app: - main: src/app.cr + exec_from: + github: place-labs/exec_from + + jwt: + github: crystal-community/jwt + + pinger: + github: spider-gazelle/pinger + + ntlm: + github: spider-gazelle/ntlm + + link-header: + github: spider-gazelle/link-header + + place_calendar: + github: PlaceOS/calendar + + placeos-compiler: + github: placeos/compiler + version: ~> 3.3 + + placeos-driver: + github: placeos/driver + version: ~> 4.0 + + # The PlaceOS API client + placeos: + github: placeos/crystal-client + version: ~> 2.2 + + rwlock: + github: spider-gazelle/readers-writer + + # Driver deps: + telnet: + github: spider-gazelle/telnet.cr + + pars: + github: place-labs/pars + branch: master + + # For lat lon location indexing + s2_cells: + github: spider-gazelle/s2_cells + + qr-code: + github: spider-gazelle/qr-code + + # For QR .png file export + stumpy_png: + github: stumpycr/stumpy_png diff --git a/spec/build_spec.cr b/spec/build_spec.cr new file mode 100644 index 00000000000..9b93a15690c --- /dev/null +++ b/spec/build_spec.cr @@ -0,0 +1,44 @@ +require "./spec_helper" + +module PlaceOS::Drivers::Api + describe Build do + with_server do + it "should list drivers" do + result = curl("GET", "/build") + drivers = Array(String).from_json(result.body) + (drivers.size > 0).should be_true + drivers.includes?("drivers/place/spec_helper.cr").should be_true + end + + it "should build a driver" do + result = curl("POST", "/build?driver=drivers/place/spec_helper.cr") + result.status_code.should eq(201) + end + + it "should list compiled versions" do + result = curl("GET", "/build/drivers%2Fplace%2Fspec_helper.cr/") + result.status_code.should eq(200) + drivers = Array(String).from_json(result.body) + drivers[0].starts_with?("drivers_place_spec_helper_").should be_true + end + + it "should list possible versions" do + result = curl("GET", "/build/drivers%2Fplace%2Fspec_helper.cr/commits") + + result.status_code.should eq(200) + commits = JSON.parse(result.body) + commits.size.should eq(1) + end + + it "should delete all compiled versions of a driver" do + result = curl("DELETE", "/build/drivers%2Fplace%2Fspec_helper.cr/") + result.status_code.should eq(200) + + result = curl("GET", "/build/drivers%2Fplace%2Fspec_helper.cr/") + result.status_code.should eq(200) + drivers = Array(String).from_json(result.body) + drivers.size.should eq(0) + end + end + end +end diff --git a/spec/compiler_spec.cr b/spec/compiler_spec.cr new file mode 100644 index 00000000000..1de0658d1a6 --- /dev/null +++ b/spec/compiler_spec.cr @@ -0,0 +1,16 @@ +require "./spec_helper" +require "file_utils" + +module PlaceOS::Drivers + describe Compiler do + with_server do + it "should compile a private driver using the build API" do + PlaceOS::Compiler.clone_and_install("private_drivers", "https://github.com/placeos/private-drivers.git") + File.file?(File.expand_path("./repositories/private_drivers/drivers/place/private_helper.cr")).should be_true + + result = curl("POST", "/build?repository=private_drivers&driver=drivers/place/private_helper.cr") + result.status_code.should eq(201) + end + end + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index e008dcfd222..dbeace8f50a 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -1,7 +1,18 @@ require "spec" # Your application config +# If you have a testing environment, replace this with a test config file require "../src/config" # Helper methods for testing controllers (curl, with_server, context) require "../lib/action-controller/spec/curl_context" + +require "placeos-compiler" + +Spec.before_suite do + # Clone the private drivers + PlaceOS::Compiler.clone_and_install( + "private_drivers", + "https://github.com/placeos/private-drivers" + ) +end diff --git a/spec/test_spec.cr b/spec/test_spec.cr new file mode 100644 index 00000000000..694b408d8a5 --- /dev/null +++ b/spec/test_spec.cr @@ -0,0 +1,19 @@ +require "./spec_helper" + +module PlaceOS::Drivers::Api + describe Test do + with_server do + it "should list drivers" do + result = curl("GET", "/test?repository=private_drivers") + drivers = Array(String).from_json(result.body) + (drivers.size > 0).should be_true + drivers.includes?("drivers/place/private_helper_spec.cr").should be_true + end + + it "should build a driver" do + result = curl("POST", "/test?repository=private_drivers&driver=drivers/place/private_helper.cr&spec=drivers/place/private_helper_spec.cr&force=true") + result.status_code.should eq(200) + end + end + end +end diff --git a/spec/welcome_spec.cr b/spec/welcome_spec.cr deleted file mode 100644 index 5666e9ad6e3..00000000000 --- a/spec/welcome_spec.cr +++ /dev/null @@ -1,25 +0,0 @@ -require "./spec_helper" - -describe Welcome do - # ============== - # Unit Testing - # ============== - it "should generate a date string" do - # instantiate the controller you wish to unit test - welcome = Welcome.new(context("GET", "/")) - - # Test the instance methods of the controller - welcome.time_now.should contain("GMT") - end - - # ============== - # Test Responses - # ============== - with_server do - it "should welcome you" do - result = curl("GET", "/") - result.body.should eq("\n\nWelcome\nYou're riding on Spider-Gazelle!\n\n") - result.headers["Date"]?.nil?.should eq(false) - end - end -end diff --git a/src/app.cr b/src/app.cr index 4a6e7bbdbb3..c95bda561ba 100644 --- a/src/app.cr +++ b/src/app.cr @@ -4,14 +4,25 @@ require "./config" # Server defaults port = 3000 host = "127.0.0.1" +cluster = false +process_count = 1 # Command line options -OptionParser.parse! do |parser| +OptionParser.parse(ARGV.dup) do |parser| parser.banner = "Usage: #{PROGRAM_NAME} [arguments]" parser.on("-b HOST", "--bind=HOST", "Specifies the server host") { |h| host = h } parser.on("-p PORT", "--port=PORT", "Specifies the server port") { |p| port = p.to_i } + # There should only ever be a single instance of this process + # as we don't want concurrent access occuring + # (technically git allows concurrent repo access however you may experience unexpected + # results as file revisions are changed while compiling etc) + # parser.on("-w COUNT", "--workers=COUNT", "Specifies the number of processes to handle requests") do |w| + # cluster = true + # process_count = w.to_i + # end + parser.on("-r", "--routes", "List the application routes") do ActionController::Server.print_routes exit 0 @@ -32,15 +43,24 @@ end puts "Launching #{APP_NAME} v#{VERSION}" server = ActionController::Server.new(port, host) -# Detect ctr-c to shutdown gracefully -Signal::INT.trap do +# Start clustering +server.cluster(process_count, "-w", "--workers") if cluster + +terminate = Proc(Signal, Nil).new do |signal| puts " > terminating gracefully" - server.close + spawn { server.close } + signal.ignore end +# Detect ctr-c to shutdown gracefully +Signal::INT.trap &terminate +# Docker containers use the term signal +Signal::TERM.trap &terminate + # Start the server -puts "Listening on tcp://#{host}:#{port}" -server.run +server.run do + puts "Listening on #{server.print_addresses}" +end # Shutdown message puts "#{APP_NAME} leaps through the veldt\n" diff --git a/src/build.cr b/src/build.cr new file mode 100644 index 00000000000..bbbddaf0bf2 --- /dev/null +++ b/src/build.cr @@ -0,0 +1,8 @@ +{% if env("COMPILE_DRIVER").ends_with?("_spec.cr") %} + require "placeos-driver/driver-specs/runner" +{% else %} + require "placeos-driver" +{% end %} + +# Dynamically require the desired driver +{{ ("require \"../" + env("COMPILE_DRIVER") + "\"").id }} diff --git a/src/config.cr b/src/config.cr index f48255a4f35..29da616bb59 100644 --- a/src/config.cr +++ b/src/config.cr @@ -1,21 +1,46 @@ # Application dependencies require "action-controller" -require "active-model" +require "placeos-compiler" # Application code require "./controllers/application" require "./controllers/*" -require "./models/*" # Server required after application controllers require "action-controller/server" +PROD = ENV["SG_ENV"]? == "production" + +# Configure logging +Log.setup "*", :warn, ActionController.default_backend +Log.builder.bind "action-controller.*", :info, ActionController.default_backend + +filters = PROD ? ["bearer_token", "secret", "password"] : [] of String + +# Add handlers that should run before your application +ActionController::Server.before( + HTTP::ErrorHandler.new(PROD), + ActionController::LogHandler.new(filters, ActionController::LogHandler::Event.all), +) + +# Optional support for serving of static assests +static_file_path = ENV["PUBLIC_WWW_PATH"]? || "./www" +if File.directory?(static_file_path) + # Optionally add additional mime types + ::MIME.register(".yaml", "text/yaml") + + # Check for files if no paths matched in your application + ActionController::Server.before( + ::HTTP::StaticFileHandler.new(static_file_path, directory_listing: false) + ) +end + # Configure session cookies -# NOTE:: Change these from defaults -ActionController::Session.configure do - settings.key = "_spider_gazelle_" - settings.secret = "4f74c0b358d5bab4000dd3c75465dc2c" +ActionController::Session.configure do |settings| + settings.key = ENV["COOKIE_SESSION_KEY"]? || "_spider_gazelle_" + settings.secret = ENV["COOKIE_SESSION_SECRET"]? || "4f74c0b358d5bab4000dd3c75465dc2c" + settings.secure = PROD end -APP_NAME = "Spider-Gazelle" -VERSION = "1.0.0" +APP_NAME = "Drivers Test Harness" +VERSION = `shards version` diff --git a/src/controllers/application.cr b/src/controllers/application.cr index 9bdbc79b557..a38cd60a6fe 100644 --- a/src/controllers/application.cr +++ b/src/controllers/application.cr @@ -1,15 +1,19 @@ -# Require kilt for template support -require "kilt" +require "uuid" +require "action-controller" -abstract class Application < ActionController::Base - before_action :set_date_header +module PlaceOS::Drivers::Api + abstract class Application < ActionController::Base + before_action :set_request_id - # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date - def time_now - Time.utc_now.to_s("%a, %d %b %Y %H:%M:%S GMT") - end + # Support request tracking + def set_request_id + Log.context.set(client_ip: client_ip) + response.headers["X-Request-ID"] = Log.context.metadata[:request_id].as_s + end - def set_date_header - response.headers["Date"] = time_now + # Builds and validates the selected repository + def get_repository_path + Compiler::Helper.get_repository_path(params["repository"]?) + end end end diff --git a/src/controllers/build.cr b/src/controllers/build.cr new file mode 100644 index 00000000000..598cbafb6b7 --- /dev/null +++ b/src/controllers/build.cr @@ -0,0 +1,87 @@ +require "./application" + +module PlaceOS::Drivers::Api + class Build < Application + base "/build" + + # list the available files + def index + compiled = params["compiled"]? + if compiled + render json: PlaceOS::Compiler.compiled_drivers + else + result = Dir.cd(get_repository_path) do + Dir.glob("drivers/**/*.cr").reject! do |path| + path.ends_with?("_spec.cr") || !File.read_lines(path).any? &.includes?("< PlaceOS::Driver") + end + end + + render json: result + end + end + + def show + driver_source = URI.decode(params["id"]) + render json: PlaceOS::Compiler.compiled_drivers(driver_source) + end + + # grab the list of available repositories + get "/repositories" do + render json: PlaceOS::Compiler.repositories + end + + # grab the list of available versions of file / which are built + get "/:id/commits" do + driver_source = URI.decode(params["id"]) + count = (params["count"]? || 50).to_i + + render json: PlaceOS::Compiler::GitCommands.commits(driver_source, count, get_repository_path) + end + + # Commits at repo level + get "/repository_commits" do + count = (params["count"]? || 50).to_i + render json: PlaceOS::Compiler::GitCommands.repository_commits(get_repository_path, count) + end + + # build a drvier, optionally based on the version specified + def create + driver = params["driver"] + commit = params["commit"]? || "HEAD" + + result = PlaceOS::Compiler.build_driver(driver, commit, get_repository_path) + + if result[:exit_status] == 0 + render :not_acceptable, text: result[:output] unless File.exists?(result[:executable]) + else + render :not_acceptable, text: result[:output] + end + + response.headers["Location"] = "/build/#{URI.encode_www_form(driver)}" + head :created + end + + # delete a built driver + def destroy + driver_source = URI.decode(params["id"]) + commit = params["commit"]? + + # Check repository to prevent abuse (don't want to delete the wrong thing) + repository = get_repository_path + PlaceOS::Compiler::GitCommands.checkout(driver_source, commit || "HEAD", repository) do + head :not_found unless File.exists?(File.join(repository, driver_source)) + end + + files = if commit + [Compiler.executable_name(driver_source, commit, nil)] + else + PlaceOS::Compiler.compiled_drivers(driver_source) + end + + files.each do |file| + File.delete File.join(PlaceOS::Compiler.bin_dir, file) + end + head :ok + end + end +end diff --git a/src/controllers/test.cr b/src/controllers/test.cr new file mode 100644 index 00000000000..53ea161fe3b --- /dev/null +++ b/src/controllers/test.cr @@ -0,0 +1,144 @@ +require "./application" + +module PlaceOS::Drivers::Api + class Test < Application + base "/test" + + before_action :ensure_driver_compiled, only: [:run_spec, :create] + before_action :ensure_spec_compiled, only: [:run_spec, :create] + @driver_path : String = "" + @spec_path : String = "" + + PLACE_DRIVERS_DIR = "../../#{Dir.current.split("/")[-1]}" + + # Specs available + def index + result = [] of String + Dir.cd(get_repository_path) do + Dir.glob("drivers/**/*_spec.cr") { |file| result << file } + end + render json: result + end + + # grab the list of available versions of the spec file + get "/:id/commits" do + spec = URI.decode(params["id"]) + count = (params["count"]? || 50).to_i + + render json: Compiler::GitCommands.commits(spec, count, get_repository_path) + end + + # Run the spec and return success if the exit status is 0 + def create + debug = params["debug"]? == "true" + + io = IO::Memory.new + exit_code = launch_spec(io, debug) + + render :not_acceptable, text: io.to_s if exit_code != 0 + render text: io.to_s + end + + # WS watch the output from running specs + ws "/run_spec", :run_spec do |socket| + debug = params["debug"]? == "true" + + # Run the spec and pipe all the IO down the websocket + spawn { pipe_spec(socket, debug) } + end + + def pipe_spec(socket, debug) + output, output_writer = IO.pipe + spawn { launch_spec(output_writer, debug) } + + # Read data coming in from the IO and send it down the websocket + raw_data = Bytes.new(1024) + begin + while !output.closed? + bytes_read = output.read(raw_data) + break if bytes_read == 0 # IO was closed + socket.send String.new(raw_data[0, bytes_read]) + end + rescue IO::Error + # Input stream closed. This should only occur on termination + end + + # Once the process exits, close the websocket + socket.close + end + + GDB_SERVER_PORT = ENV["GDB_SERVER_PORT"]? || "4444" + + def launch_spec(io, debug) + io << "\nLaunching spec runner\n" + + if debug + exit_code = Process.run( + "gdbserver", + {"0.0.0.0:#{GDB_SERVER_PORT}", @spec_path}, + {"SPEC_RUN_DRIVER" => @driver_path}, + input: Process::Redirect::Close, + output: io, + error: io + ).exit_code + io << "spec runner exited with #{exit_code}\n" + io.close + exit_code + else + exit_code = Process.run( + @spec_path, + nil, + {"SPEC_RUN_DRIVER" => @driver_path}, + input: Process::Redirect::Close, + output: io, + error: io + ).exit_code + io << "spec runner exited with #{exit_code}\n" + io.close + exit_code + end + end + + def ensure_driver_compiled + driver = params["driver"] + repository = get_repository_path + commit = params["commit"]? || "HEAD" + + driver_path = Compiler.is_built?(driver, commit, repository) + + # Build the driver if has not been compiled yet + debug = params["debug"]? + if driver_path.nil? || params["force"]? || debug + result = Compiler.build_driver(driver, commit, repository, debug: !!debug) + output = result[:output].strip + + render :not_acceptable, text: output if result[:exit_status] != 0 || !File.exists?(result[:executable]) + + driver_path = Compiler.is_built?(driver, commit, repository) + end + + # raise an error if the driver still does not exist + @driver_path = driver_path.not_nil! + end + + def ensure_spec_compiled + spec = params["spec"] + repository = get_repository_path + spec_commit = params["spec_commit"]? || "HEAD" + + spec_path = Compiler.is_built?(spec, spec_commit, repository) + + debug = params["debug"]? + if spec_path.nil? || params["force"]? || debug + result = Compiler.build_driver(spec, spec_commit, repository, debug: !!debug) + output = result[:output].strip + + render :not_acceptable, text: output if result[:exit_status] != 0 || !File.exists?(result[:executable]) + + spec_path = Compiler.is_built?(spec, spec_commit, repository) + end + + @spec_path = spec_path.not_nil! + end + end +end diff --git a/src/controllers/welcome.cr b/src/controllers/welcome.cr index 7695e78845b..ef4543f9117 100644 --- a/src/controllers/welcome.cr +++ b/src/controllers/welcome.cr @@ -1,17 +1,17 @@ -class Welcome < Application - base "/" +require "./application" - def index - welcome_text = "You're riding on Spider-Gazelle!" +module PlaceOS::Drivers::Api + class Welcome < Application + base "/" - respond_with do - html Kilt.render("src/views/welcome.ecr") - text "Welcome, #{welcome_text}" - json({welcome: welcome_text}) - xml do - XML.build(indent: " ") do |xml| - xml.element("welcome") { xml.text welcome_text } - end + STATIC_FILE_PATH = File.join(File.expand_path(ENV["PUBLIC_WWW_PATH"]? || "./www"), "index.html") + + def index + file_path = STATIC_FILE_PATH + response.content_type = MIME.from_filename(file_path, "application/octet-stream") + response.content_length = File.size(file_path) + File.open(file_path) do |file| + IO.copy(file, response) end end end diff --git a/src/views/welcome.ecr b/src/views/welcome.ecr deleted file mode 100644 index 69f2e46c394..00000000000 --- a/src/views/welcome.ecr +++ /dev/null @@ -1,5 +0,0 @@ - - -Welcome -<%= welcome_text %> - diff --git a/www/3rdpartylicenses.txt b/www/3rdpartylicenses.txt new file mode 100644 index 00000000000..2a243cb8031 --- /dev/null +++ b/www/3rdpartylicenses.txt @@ -0,0 +1,374 @@ +@angular/animations +MIT + +@angular/cdk +MIT +The MIT License + +Copyright (c) 2020 Google LLC. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +@angular/common +MIT + +@angular/core +MIT + +@angular/forms +MIT + +@angular/material +MIT +The MIT License + +Copyright (c) 2020 Google LLC. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +@angular/platform-browser +MIT + +@angular/router +MIT + +@angular/service-worker +MIT + +css-loader +MIT +Copyright JS Foundation and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +rxjs +Apache-2.0 + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors + + 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. + + + +string-similarity-js +MIT +Copyright 2018 Stephen Brown + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +tslib +0BSD +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +xterm +MIT +Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js) +Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com) +Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +zone.js +MIT +The MIT License + +Copyright (c) 2010-2020 Google LLC. http://angular.io/license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/www/assets/icons/icon-128x128.png b/www/assets/icons/icon-128x128.png new file mode 100644 index 00000000000..9f9241f0be4 Binary files /dev/null and b/www/assets/icons/icon-128x128.png differ diff --git a/www/assets/icons/icon-144x144.png b/www/assets/icons/icon-144x144.png new file mode 100644 index 00000000000..4a5f8c16389 Binary files /dev/null and b/www/assets/icons/icon-144x144.png differ diff --git a/www/assets/icons/icon-152x152.png b/www/assets/icons/icon-152x152.png new file mode 100644 index 00000000000..34a1a8d6458 Binary files /dev/null and b/www/assets/icons/icon-152x152.png differ diff --git a/www/assets/icons/icon-192x192.png b/www/assets/icons/icon-192x192.png new file mode 100644 index 00000000000..9172e5dd29e Binary files /dev/null and b/www/assets/icons/icon-192x192.png differ diff --git a/www/assets/icons/icon-384x384.png b/www/assets/icons/icon-384x384.png new file mode 100644 index 00000000000..e54e8d3eafe Binary files /dev/null and b/www/assets/icons/icon-384x384.png differ diff --git a/www/assets/icons/icon-512x512.png b/www/assets/icons/icon-512x512.png new file mode 100644 index 00000000000..51ee297df1c Binary files /dev/null and b/www/assets/icons/icon-512x512.png differ diff --git a/www/assets/icons/icon-72x72.png b/www/assets/icons/icon-72x72.png new file mode 100644 index 00000000000..2814a3f30ca Binary files /dev/null and b/www/assets/icons/icon-72x72.png differ diff --git a/www/assets/icons/icon-96x96.png b/www/assets/icons/icon-96x96.png new file mode 100644 index 00000000000..d271025c4f2 Binary files /dev/null and b/www/assets/icons/icon-96x96.png differ diff --git a/www/assets/logo-dark.svg b/www/assets/logo-dark.svg new file mode 100644 index 00000000000..09172c9cfd0 --- /dev/null +++ b/www/assets/logo-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/www/assets/logo-light.svg b/www/assets/logo-light.svg new file mode 100644 index 00000000000..b69582e4f3c --- /dev/null +++ b/www/assets/logo-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/www/favicon.ico b/www/favicon.ico new file mode 100644 index 00000000000..997406ad22c Binary files /dev/null and b/www/favicon.ico differ diff --git a/www/index.html b/www/index.html new file mode 100644 index 00000000000..33dce9035ba --- /dev/null +++ b/www/index.html @@ -0,0 +1,26 @@ + + + + + Driver Test Harness | PlaceOS + + + + + + + + + + + + + diff --git a/www/main.a75b981096ad2a451c1d.js b/www/main.a75b981096ad2a451c1d.js new file mode 100644 index 00000000000..69a0f143be7 --- /dev/null +++ b/www/main.a75b981096ad2a451c1d.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[1],{"/POA":function(e,t,i){window,e.exports=function(e){var t={};function i(r){if(t[r])return t[r].exports;var n=t[r]={i:r,l:!1,exports:{}};return e[r].call(n.exports,n,n.exports,i),n.l=!0,n.exports}return i.m=e,i.c=t,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,(function(t){return e[t]}).bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=34)}([function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.forwardEvent=t.EventEmitter=void 0;var r=function(){function e(){this._listeners=[],this._disposed=!1}return Object.defineProperty(e.prototype,"event",{get:function(){var e=this;return this._event||(this._event=function(t){return e._listeners.push(t),{dispose:function(){if(!e._disposed)for(var i=0;i>22},t.prototype.getChars=function(){return 2097152&this.content?this.combinedData:2097151&this.content?s.stringFromCodePoint(2097151&this.content):""},t.prototype.getCode=function(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content},t.prototype.setFromCharData=function(e){this.fg=e[o.CHAR_DATA_ATTR_INDEX],this.bg=0;var t=!1;if(e[o.CHAR_DATA_CHAR_INDEX].length>2)t=!0;else if(2===e[o.CHAR_DATA_CHAR_INDEX].length){var i=e[o.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=i&&i<=56319){var r=e[o.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=r&&r<=57343?this.content=1024*(i-55296)+r-56320+65536|e[o.CHAR_DATA_WIDTH_INDEX]<<22:t=!0}else t=!0}else this.content=e[o.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|e[o.CHAR_DATA_WIDTH_INDEX]<<22;t&&(this.combinedData=e[o.CHAR_DATA_CHAR_INDEX],this.content=2097152|e[o.CHAR_DATA_WIDTH_INDEX]<<22)},t.prototype.getAsCharData=function(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]},t}(a.AttributeData);t.CellData=l},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ISoundService=t.ISelectionService=t.IRenderService=t.IMouseService=t.ICoreBrowserService=t.ICharSizeService=void 0;var r=i(14);t.ICharSizeService=r.createDecorator("CharSizeService"),t.ICoreBrowserService=r.createDecorator("CoreBrowserService"),t.IMouseService=r.createDecorator("MouseService"),t.IRenderService=r.createDecorator("RenderService"),t.ISelectionService=r.createDecorator("SelectionService"),t.ISoundService=r.createDecorator("SoundService")},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ExtendedAttrs=t.AttributeData=void 0;var r=function(){function e(){this.fg=0,this.bg=0,this.extended=new n}return e.toColorRGB=function(e){return[e>>>16&255,e>>>8&255,255&e]},e.fromColorRGB=function(e){return(255&e[0])<<16|(255&e[1])<<8|255&e[2]},e.prototype.clone=function(){var t=new e;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t},e.prototype.isInverse=function(){return 67108864&this.fg},e.prototype.isBold=function(){return 134217728&this.fg},e.prototype.isUnderline=function(){return 268435456&this.fg},e.prototype.isBlink=function(){return 536870912&this.fg},e.prototype.isInvisible=function(){return 1073741824&this.fg},e.prototype.isItalic=function(){return 67108864&this.bg},e.prototype.isDim=function(){return 134217728&this.bg},e.prototype.getFgColorMode=function(){return 50331648&this.fg},e.prototype.getBgColorMode=function(){return 50331648&this.bg},e.prototype.isFgRGB=function(){return 50331648==(50331648&this.fg)},e.prototype.isBgRGB=function(){return 50331648==(50331648&this.bg)},e.prototype.isFgPalette=function(){return 16777216==(50331648&this.fg)||33554432==(50331648&this.fg)},e.prototype.isBgPalette=function(){return 16777216==(50331648&this.bg)||33554432==(50331648&this.bg)},e.prototype.isFgDefault=function(){return 0==(50331648&this.fg)},e.prototype.isBgDefault=function(){return 0==(50331648&this.bg)},e.prototype.isAttributeDefault=function(){return 0===this.fg&&0===this.bg},e.prototype.getFgColor=function(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}},e.prototype.getBgColor=function(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}},e.prototype.hasExtendedAttrs=function(){return 268435456&this.bg},e.prototype.updateExtended=function(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456},e.prototype.getUnderlineColor=function(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()},e.prototype.getUnderlineColorMode=function(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()},e.prototype.isUnderlineColorRGB=function(){return 268435456&this.bg&&~this.extended.underlineColor?50331648==(50331648&this.extended.underlineColor):this.isFgRGB()},e.prototype.isUnderlineColorPalette=function(){return 268435456&this.bg&&~this.extended.underlineColor?16777216==(50331648&this.extended.underlineColor)||33554432==(50331648&this.extended.underlineColor):this.isFgPalette()},e.prototype.isUnderlineColorDefault=function(){return 268435456&this.bg&&~this.extended.underlineColor?0==(50331648&this.extended.underlineColor):this.isFgDefault()},e.prototype.getUnderlineStyle=function(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0},e}();t.AttributeData=r;var n=function(){function e(e,t){void 0===e&&(e=0),void 0===t&&(t=-1),this.underlineStyle=e,this.underlineColor=t}return e.prototype.clone=function(){return new e(this.underlineStyle,this.underlineColor)},e.prototype.isEmpty=function(){return 0===this.underlineStyle},e}();t.ExtendedAttrs=n},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.addDisposableDomListener=void 0,t.addDisposableDomListener=function(e,t,i,r){e.addEventListener(t,i,r);var n=!1;return{dispose:function(){n||(n=!0,e.removeEventListener(t,i,r))}}}},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Utf8ToUtf32=t.StringToUtf32=t.utf32ToString=t.stringFromCodePoint=void 0,t.stringFromCodePoint=function(e){return e>65535?(e-=65536,String.fromCharCode(55296+(e>>10))+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)},t.utf32ToString=function(e,t,i){void 0===t&&(t=0),void 0===i&&(i=e.length);for(var r="",n=t;n65535?(s-=65536,r+=String.fromCharCode(55296+(s>>10))+String.fromCharCode(s%1024+56320)):r+=String.fromCharCode(s)}return r};var r=function(){function e(){this._interim=0}return e.prototype.clear=function(){this._interim=0},e.prototype.decode=function(e,t){var i=e.length;if(!i)return 0;var r=0,n=0;this._interim&&(56320<=(a=e.charCodeAt(n++))&&a<=57343?t[r++]=1024*(this._interim-55296)+a-56320+65536:(t[r++]=this._interim,t[r++]=a),this._interim=0);for(var s=n;s=i)return this._interim=o,r;var a;56320<=(a=e.charCodeAt(s))&&a<=57343?t[r++]=1024*(o-55296)+a-56320+65536:(t[r++]=o,t[r++]=a)}else t[r++]=o}return r},e}();t.StringToUtf32=r;var n=function(){function e(){this.interim=new Uint8Array(3)}return e.prototype.clear=function(){this.interim.fill(0)},e.prototype.decode=function(e,t){var i=e.length;if(!i)return 0;var r,n,s,o,a=0,l=0,c=0;if(this.interim[0]){var h=!1,u=this.interim[0];u&=192==(224&u)?31:224==(240&u)?15:7;for(var d=0,f=void 0;(f=63&this.interim[++d])&&d<4;)u<<=6,u|=f;for(var p=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,_=p-d;c<_;){if(c>=i)return 0;if(128!=(192&(f=e[c++]))){c--,h=!0;break}this.interim[d++]=f,u<<=6,u|=63&f}h||(2===p?u<128?c--:t[a++]=u:3===p?u<2048||u>=55296&&u<=57343||(t[a++]=u):u<65536||u>1114111||(t[a++]=u)),this.interim.fill(0)}for(var m=i-4,g=c;g=i)return this.interim[0]=r,a;if(128!=(192&(n=e[g++]))){g--;continue}if((l=(31&r)<<6|63&n)<128){g--;continue}t[a++]=l}else if(224==(240&r)){if(g>=i)return this.interim[0]=r,a;if(128!=(192&(n=e[g++]))){g--;continue}if(g>=i)return this.interim[0]=r,this.interim[1]=n,a;if(128!=(192&(s=e[g++]))){g--;continue}if((l=(15&r)<<12|(63&n)<<6|63&s)<2048||l>=55296&&l<=57343)continue;t[a++]=l}else if(240==(248&r)){if(g>=i)return this.interim[0]=r,a;if(128!=(192&(n=e[g++]))){g--;continue}if(g>=i)return this.interim[0]=r,this.interim[1]=n,a;if(128!=(192&(s=e[g++]))){g--;continue}if(g>=i)return this.interim[0]=r,this.interim[1]=n,this.interim[2]=s,a;if(128!=(192&(o=e[g++]))){g--;continue}if((l=(7&r)<<18|(63&n)<<12|(63&s)<<6|63&o)<65536||l>1114111)continue;t[a++]=l}}return a},e}();t.Utf8ToUtf32=n},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CHAR_ATLAS_CELL_SPACING=t.DIM_OPACITY=t.INVERTED_DEFAULT_COLOR=void 0,t.INVERTED_DEFAULT_COLOR=257,t.DIM_OPACITY=.5,t.CHAR_ATLAS_CELL_SPACING=1},function(e,t,i){"use strict";var r,n,s,o;function a(e){var t=e.toString(16);return t.length<2?"0"+t:t}function l(e,t){return e>>0}}(r=t.channels||(t.channels={})),(n=t.color||(t.color={})).blend=function(e,t){var i=(255&t.rgba)/255;if(1===i)return{css:t.css,rgba:t.rgba};var n=t.rgba>>16&255,s=t.rgba>>8&255,o=e.rgba>>24&255,a=e.rgba>>16&255,l=e.rgba>>8&255,c=o+Math.round(((t.rgba>>24&255)-o)*i),h=a+Math.round((n-a)*i),u=l+Math.round((s-l)*i);return{css:r.toCss(c,h,u),rgba:r.toRgba(c,h,u)}},n.isOpaque=function(e){return 255==(255&e.rgba)},n.ensureContrastRatio=function(e,t,i){var r=o.ensureContrastRatio(e.rgba,t.rgba,i);if(r)return o.toColor(r>>24&255,r>>16&255,r>>8&255)},n.opaque=function(e){var t=(255|e.rgba)>>>0,i=o.toChannels(t);return{css:r.toCss(i[0],i[1],i[2]),rgba:t}},n.opacity=function(e,t){var i=Math.round(255*t),n=o.toChannels(e.rgba),s=n[0],a=n[1],l=n[2];return{css:r.toCss(s,a,l,i),rgba:r.toRgba(s,a,l,i)}},(t.css||(t.css={})).toColor=function(e){switch(e.length){case 7:return{css:e,rgba:(parseInt(e.slice(1),16)<<8|255)>>>0};case 9:return{css:e,rgba:parseInt(e.slice(1),16)>>>0}}throw new Error("css.toColor: Unsupported css format")},function(e){function t(e,t,i){var r=e/255,n=t/255,s=i/255;return.2126*(r<=.03928?r/12.92:Math.pow((r+.055)/1.055,2.4))+.7152*(n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4))+.0722*(s<=.03928?s/12.92:Math.pow((s+.055)/1.055,2.4))}e.relativeLuminance=function(e){return t(e>>16&255,e>>8&255,255&e)},e.relativeLuminance2=t}(s=t.rgb||(t.rgb={})),function(e){function t(e,t,i){for(var r=e>>24&255,n=e>>16&255,o=e>>8&255,a=t>>24&255,c=t>>16&255,h=t>>8&255,u=l(s.relativeLuminance2(a,h,c),s.relativeLuminance2(r,n,o));u0||c>0||h>0);)a-=Math.max(0,Math.ceil(.1*a)),c-=Math.max(0,Math.ceil(.1*c)),h-=Math.max(0,Math.ceil(.1*h)),u=l(s.relativeLuminance2(a,h,c),s.relativeLuminance2(r,n,o));return(a<<24|c<<16|h<<8|255)>>>0}function i(e,t,i){for(var r=e>>24&255,n=e>>16&255,o=e>>8&255,a=t>>24&255,c=t>>16&255,h=t>>8&255,u=l(s.relativeLuminance2(a,h,c),s.relativeLuminance2(r,n,o));u>>0}e.ensureContrastRatio=function(e,r,n){var o=s.relativeLuminance(e>>8),a=s.relativeLuminance(r>>8);if(l(o,a)>24&255,e>>16&255,e>>8&255,255&e]},e.toColor=function(e,t,i){return{css:r.toCss(e,t,i),rgba:r.toRgba(e,t,i)}}}(o=t.rgba||(t.rgba={})),t.toPaddedHex=a,t.contrastRatio=l},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isLinux=t.isWindows=t.isIphone=t.isIpad=t.isMac=t.isSafari=t.isFirefox=void 0;var r="undefined"==typeof navigator,n=r?"node":navigator.userAgent,s=r?"node":navigator.platform;function o(e,t){return e.indexOf(t)>=0}t.isFirefox=!!~n.indexOf("Firefox"),t.isSafari=/^((?!chrome|android).)*safari/i.test(n),t.isMac=o(["Macintosh","MacIntel","MacPPC","Mac68K"],s),t.isIpad="iPad"===s,t.isIphone="iPhone"===s,t.isWindows=o(["Windows","Win16","Win32","WinCE"],s),t.isLinux=s.indexOf("Linux")>=0},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.C1=t.C0=void 0,function(e){e.NUL="\0",e.SOH="\x01",e.STX="\x02",e.ETX="\x03",e.EOT="\x04",e.ENQ="\x05",e.ACK="\x06",e.BEL="\x07",e.BS="\b",e.HT="\t",e.LF="\n",e.VT="\v",e.FF="\f",e.CR="\r",e.SO="\x0e",e.SI="\x0f",e.DLE="\x10",e.DC1="\x11",e.DC2="\x12",e.DC3="\x13",e.DC4="\x14",e.NAK="\x15",e.SYN="\x16",e.ETB="\x17",e.CAN="\x18",e.EM="\x19",e.SUB="\x1a",e.ESC="\x1b",e.FS="\x1c",e.GS="\x1d",e.RS="\x1e",e.US="\x1f",e.SP=" ",e.DEL="\x7f"}(t.C0||(t.C0={})),function(e){e.PAD="\x80",e.HOP="\x81",e.BPH="\x82",e.NBH="\x83",e.IND="\x84",e.NEL="\x85",e.SSA="\x86",e.ESA="\x87",e.HTS="\x88",e.HTJ="\x89",e.VTS="\x8a",e.PLD="\x8b",e.PLU="\x8c",e.RI="\x8d",e.SS2="\x8e",e.SS3="\x8f",e.DCS="\x90",e.PU1="\x91",e.PU2="\x92",e.STS="\x93",e.CCH="\x94",e.MW="\x95",e.SPA="\x96",e.EPA="\x97",e.SOS="\x98",e.SGCI="\x99",e.SCI="\x9a",e.CSI="\x9b",e.ST="\x9c",e.OSC="\x9d",e.PM="\x9e",e.APC="\x9f"}(t.C1||(t.C1={}))},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BaseRenderLayer=void 0;var r=i(3),n=i(9),s=i(25),o=i(6),a=i(28),l=i(10),c=i(17),h=function(){function e(e,t,i,r,n,s,o,a){this._container=e,this._alpha=r,this._colors=n,this._rendererId=s,this._bufferService=o,this._optionsService=a,this._scaledCharWidth=0,this._scaledCharHeight=0,this._scaledCellWidth=0,this._scaledCellHeight=0,this._scaledCharLeft=0,this._scaledCharTop=0,this._currentGlyphIdentifier={chars:"",code:0,bg:0,fg:0,bold:!1,dim:!1,italic:!1},this._canvas=document.createElement("canvas"),this._canvas.classList.add("xterm-"+t+"-layer"),this._canvas.style.zIndex=i.toString(),this._initCanvas(),this._container.appendChild(this._canvas)}return e.prototype.dispose=function(){var e;c.removeElementFromParent(this._canvas),null===(e=this._charAtlas)||void 0===e||e.dispose()},e.prototype._initCanvas=function(){this._ctx=a.throwIfFalsy(this._canvas.getContext("2d",{alpha:this._alpha})),this._alpha||this._clearAll()},e.prototype.onOptionsChanged=function(){},e.prototype.onBlur=function(){},e.prototype.onFocus=function(){},e.prototype.onCursorMove=function(){},e.prototype.onGridChanged=function(e,t){},e.prototype.onSelectionChanged=function(e,t,i){void 0===i&&(i=!1)},e.prototype.setColors=function(e){this._refreshCharAtlas(e)},e.prototype._setTransparency=function(e){if(e!==this._alpha){var t=this._canvas;this._alpha=e,this._canvas=this._canvas.cloneNode(),this._initCanvas(),this._container.replaceChild(this._canvas,t),this._refreshCharAtlas(this._colors),this.onGridChanged(0,this._bufferService.rows-1)}},e.prototype._refreshCharAtlas=function(e){this._scaledCharWidth<=0&&this._scaledCharHeight<=0||(this._charAtlas=s.acquireCharAtlas(this._optionsService.options,this._rendererId,e,this._scaledCharWidth,this._scaledCharHeight),this._charAtlas.warmUp())},e.prototype.resize=function(e){this._scaledCellWidth=e.scaledCellWidth,this._scaledCellHeight=e.scaledCellHeight,this._scaledCharWidth=e.scaledCharWidth,this._scaledCharHeight=e.scaledCharHeight,this._scaledCharLeft=e.scaledCharLeft,this._scaledCharTop=e.scaledCharTop,this._canvas.width=e.scaledCanvasWidth,this._canvas.height=e.scaledCanvasHeight,this._canvas.style.width=e.canvasWidth+"px",this._canvas.style.height=e.canvasHeight+"px",this._alpha||this._clearAll(),this._refreshCharAtlas(this._colors)},e.prototype._fillCells=function(e,t,i,r){this._ctx.fillRect(e*this._scaledCellWidth,t*this._scaledCellHeight,i*this._scaledCellWidth,r*this._scaledCellHeight)},e.prototype._fillBottomLineAtCells=function(e,t,i){void 0===i&&(i=1),this._ctx.fillRect(e*this._scaledCellWidth,(t+1)*this._scaledCellHeight-window.devicePixelRatio-1,i*this._scaledCellWidth,window.devicePixelRatio)},e.prototype._fillLeftLineAtCell=function(e,t,i){this._ctx.fillRect(e*this._scaledCellWidth,t*this._scaledCellHeight,window.devicePixelRatio*i,this._scaledCellHeight)},e.prototype._strokeRectAtCell=function(e,t,i,r){this._ctx.lineWidth=window.devicePixelRatio,this._ctx.strokeRect(e*this._scaledCellWidth+window.devicePixelRatio/2,t*this._scaledCellHeight+window.devicePixelRatio/2,i*this._scaledCellWidth-window.devicePixelRatio,r*this._scaledCellHeight-window.devicePixelRatio)},e.prototype._clearAll=function(){this._alpha?this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height):(this._ctx.fillStyle=this._colors.background.css,this._ctx.fillRect(0,0,this._canvas.width,this._canvas.height))},e.prototype._clearCells=function(e,t,i,r){this._alpha?this._ctx.clearRect(e*this._scaledCellWidth,t*this._scaledCellHeight,i*this._scaledCellWidth,r*this._scaledCellHeight):(this._ctx.fillStyle=this._colors.background.css,this._ctx.fillRect(e*this._scaledCellWidth,t*this._scaledCellHeight,i*this._scaledCellWidth,r*this._scaledCellHeight))},e.prototype._fillCharTrueColor=function(e,t,i){this._ctx.font=this._getFont(!1,!1),this._ctx.textBaseline="middle",this._clipRow(i),this._ctx.fillText(e.getChars(),t*this._scaledCellWidth+this._scaledCharLeft,i*this._scaledCellHeight+this._scaledCharTop+this._scaledCharHeight/2)},e.prototype._drawChars=function(e,t,i){var s,o,a=this._getContrastColor(e);a||e.isFgRGB()||e.isBgRGB()?this._drawUncachedChars(e,t,i,a):(e.isInverse()?(s=e.isBgDefault()?n.INVERTED_DEFAULT_COLOR:e.getBgColor(),o=e.isFgDefault()?n.INVERTED_DEFAULT_COLOR:e.getFgColor()):(o=e.isBgDefault()?r.DEFAULT_COLOR:e.getBgColor(),s=e.isFgDefault()?r.DEFAULT_COLOR:e.getFgColor()),s+=this._optionsService.options.drawBoldTextInBrightColors&&e.isBold()&&s<8?8:0,this._currentGlyphIdentifier.chars=e.getChars()||r.WHITESPACE_CELL_CHAR,this._currentGlyphIdentifier.code=e.getCode()||r.WHITESPACE_CELL_CODE,this._currentGlyphIdentifier.bg=o,this._currentGlyphIdentifier.fg=s,this._currentGlyphIdentifier.bold=!!e.isBold(),this._currentGlyphIdentifier.dim=!!e.isDim(),this._currentGlyphIdentifier.italic=!!e.isItalic(),this._charAtlas&&this._charAtlas.draw(this._ctx,this._currentGlyphIdentifier,t*this._scaledCellWidth+this._scaledCharLeft,i*this._scaledCellHeight+this._scaledCharTop)||this._drawUncachedChars(e,t,i))},e.prototype._drawUncachedChars=function(e,t,i,r){if(this._ctx.save(),this._ctx.font=this._getFont(!!e.isBold(),!!e.isItalic()),this._ctx.textBaseline="middle",e.isInverse())if(r)this._ctx.fillStyle=r.css;else if(e.isBgDefault())this._ctx.fillStyle=l.color.opaque(this._colors.background).css;else if(e.isBgRGB())this._ctx.fillStyle="rgb("+o.AttributeData.toColorRGB(e.getBgColor()).join(",")+")";else{var s=e.getBgColor();this._optionsService.options.drawBoldTextInBrightColors&&e.isBold()&&s<8&&(s+=8),this._ctx.fillStyle=this._colors.ansi[s].css}else if(r)this._ctx.fillStyle=r.css;else if(e.isFgDefault())this._ctx.fillStyle=this._colors.foreground.css;else if(e.isFgRGB())this._ctx.fillStyle="rgb("+o.AttributeData.toColorRGB(e.getFgColor()).join(",")+")";else{var a=e.getFgColor();this._optionsService.options.drawBoldTextInBrightColors&&e.isBold()&&a<8&&(a+=8),this._ctx.fillStyle=this._colors.ansi[a].css}this._clipRow(i),e.isDim()&&(this._ctx.globalAlpha=n.DIM_OPACITY),this._ctx.fillText(e.getChars(),t*this._scaledCellWidth+this._scaledCharLeft,i*this._scaledCellHeight+this._scaledCharTop+this._scaledCharHeight/2),this._ctx.restore()},e.prototype._clipRow=function(e){this._ctx.beginPath(),this._ctx.rect(0,e*this._scaledCellHeight,this._bufferService.cols*this._scaledCellWidth,this._scaledCellHeight),this._ctx.clip()},e.prototype._getFont=function(e,t){return(t?"italic":"")+" "+(e?this._optionsService.options.fontWeightBold:this._optionsService.options.fontWeight)+" "+this._optionsService.options.fontSize*window.devicePixelRatio+"px "+this._optionsService.options.fontFamily},e.prototype._getContrastColor=function(e){if(1!==this._optionsService.options.minimumContrastRatio){var t=this._colors.contrastCache.getColor(e.bg,e.fg);if(void 0!==t)return t||void 0;var i=e.getFgColor(),r=e.getFgColorMode(),n=e.getBgColor(),s=e.getBgColorMode(),o=!!e.isInverse(),a=!!e.isInverse();if(o){var c=i;i=n,n=c;var h=r;r=s,s=h}var u=this._resolveBackgroundRgba(s,n,o),d=this._resolveForegroundRgba(r,i,o,a),f=l.rgba.ensureContrastRatio(u,d,this._optionsService.options.minimumContrastRatio);if(f){var p={css:l.channels.toCss(f>>24&255,f>>16&255,f>>8&255),rgba:f};return this._colors.contrastCache.setColor(e.bg,e.fg,p),p}this._colors.contrastCache.setColor(e.bg,e.fg,null)}},e.prototype._resolveBackgroundRgba=function(e,t,i){switch(e){case 16777216:case 33554432:return this._colors.ansi[t].rgba;case 50331648:return t<<8;case 0:default:return i?this._colors.foreground.rgba:this._colors.background.rgba}},e.prototype._resolveForegroundRgba=function(e,t,i,r){switch(e){case 16777216:case 33554432:return this._optionsService.options.drawBoldTextInBrightColors&&r&&t<8&&(t+=8),this._colors.ansi[t].rgba;case 50331648:return t<<8;case 0:default:return i?this._colors.background.rgba:this._colors.foreground.rgba}},e}();t.BaseRenderLayer=h},function(e,t,i){"use strict";function r(e,t,i){t.di$target===t?t.di$dependencies.push({id:e,index:i}):(t.di$dependencies=[{id:e,index:i}],t.di$target=t)}Object.defineProperty(t,"__esModule",{value:!0}),t.createDecorator=t.getServiceDependencies=t.serviceRegistry=void 0,t.serviceRegistry=new Map,t.getServiceDependencies=function(e){return e.di$dependencies||[]},t.createDecorator=function(e){if(t.serviceRegistry.has(e))return t.serviceRegistry.get(e);var i=function(e,t,n){if(3!==arguments.length)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");r(i,e,n)};return i.toString=function(){return e},t.serviceRegistry.set(e,i),i}},function(e,t,i){"use strict";function r(e,t,i,r){if(void 0===i&&(i=0),void 0===r&&(r=e.length),i>=e.length)return e;r=r>=e.length?e.length:(e.length+r)%e.length;for(var n=i=(e.length+i)%e.length;n>22,2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):i]},e.prototype.set=function(e,t){this._data[3*e+1]=t[n.CHAR_DATA_ATTR_INDEX],t[n.CHAR_DATA_CHAR_INDEX].length>1?(this._combined[e]=t[1],this._data[3*e+0]=2097152|e|t[n.CHAR_DATA_WIDTH_INDEX]<<22):this._data[3*e+0]=t[n.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|t[n.CHAR_DATA_WIDTH_INDEX]<<22},e.prototype.getWidth=function(e){return this._data[3*e+0]>>22},e.prototype.hasWidth=function(e){return 12582912&this._data[3*e+0]},e.prototype.getFg=function(e){return this._data[3*e+1]},e.prototype.getBg=function(e){return this._data[3*e+2]},e.prototype.hasContent=function(e){return 4194303&this._data[3*e+0]},e.prototype.getCodePoint=function(e){var t=this._data[3*e+0];return 2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):2097151&t},e.prototype.isCombined=function(e){return 2097152&this._data[3*e+0]},e.prototype.getString=function(e){var t=this._data[3*e+0];return 2097152&t?this._combined[e]:2097151&t?r.stringFromCodePoint(2097151&t):""},e.prototype.loadCell=function(e,t){var i=3*e;return t.content=this._data[i+0],t.fg=this._data[i+1],t.bg=this._data[i+2],2097152&t.content&&(t.combinedData=this._combined[e]),268435456&t.bg&&(t.extended=this._extendedAttrs[e]),t},e.prototype.setCell=function(e,t){2097152&t.content&&(this._combined[e]=t.combinedData),268435456&t.bg&&(this._extendedAttrs[e]=t.extended),this._data[3*e+0]=t.content,this._data[3*e+1]=t.fg,this._data[3*e+2]=t.bg},e.prototype.setCellFromCodePoint=function(e,t,i,r,n,s){268435456&n&&(this._extendedAttrs[e]=s),this._data[3*e+0]=t|i<<22,this._data[3*e+1]=r,this._data[3*e+2]=n},e.prototype.addCodepointToCell=function(e,t){var i=this._data[3*e+0];2097152&i?this._combined[e]+=r.stringFromCodePoint(t):(2097151&i?(this._combined[e]=r.stringFromCodePoint(2097151&i)+r.stringFromCodePoint(t),i&=-2097152,i|=2097152):i=t|1<<22,this._data[3*e+0]=i)},e.prototype.insertCells=function(e,t,i,r){if((e%=this.length)&&2===this.getWidth(e-1)&&this.setCellFromCodePoint(e-1,0,1,(null==r?void 0:r.fg)||0,(null==r?void 0:r.bg)||0,(null==r?void 0:r.extended)||new o.ExtendedAttrs),t=0;--a)this.setCell(e+t+a,this.loadCell(e+a,n));for(a=0;athis.length){var i=new Uint32Array(3*e);this.length&&i.set(3*e=e&&delete this._combined[s]}}else this._data=new Uint32Array(0),this._combined={};this.length=e}},e.prototype.fill=function(e){this._combined={},this._extendedAttrs={};for(var t=0;t=0;--e)if(4194303&this._data[3*e+0])return e+(this._data[3*e+0]>>22);return 0},e.prototype.copyCellsFrom=function(e,t,i,r,n){var s=e._data;if(n)for(var o=r-1;o>=0;o--)for(var a=0;a<3;a++)this._data[3*(i+o)+a]=s[3*(t+o)+a];else for(o=0;o=t&&(this._combined[c-t+i]=e._combined[c])}},e.prototype.translateToString=function(e,t,i){void 0===e&&(e=!1),void 0===t&&(t=0),void 0===i&&(i=this.length),e&&(i=Math.min(i,this.getTrimmedLength()));for(var s="";t>22||1}return s},e}();t.BufferLine=a},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.removeElementFromParent=void 0,t.removeElementFromParent=function(){for(var e,t=[],i=0;i24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}!function(e){e[e.GET_WIN_SIZE_PIXELS=0]="GET_WIN_SIZE_PIXELS",e[e.GET_CELL_SIZE_PIXELS=1]="GET_CELL_SIZE_PIXELS"}(s=t.WindowsOptionsReportType||(t.WindowsOptionsReportType={}));var w=function(){function e(e,t,i,r){this._bufferService=e,this._coreService=t,this._logService=i,this._optionsService=r,this._data=new Uint32Array(0)}return e.prototype.hook=function(e){this._data=new Uint32Array(0)},e.prototype.put=function(e,t,i){this._data=h.concat(this._data,e.subarray(t,i))},e.prototype.unhook=function(e){if(e){var t=u.utf32ToString(this._data);switch(this._data=new Uint32Array(0),t){case'"q':return this._coreService.triggerDataEvent(o.C0.ESC+'P1$r0"q'+o.C0.ESC+"\\");case'"p':return this._coreService.triggerDataEvent(o.C0.ESC+'P1$r61;1"p'+o.C0.ESC+"\\");case"r":return this._coreService.triggerDataEvent(o.C0.ESC+"P1$r"+(this._bufferService.buffer.scrollTop+1)+";"+(this._bufferService.buffer.scrollBottom+1)+"r"+o.C0.ESC+"\\");case"m":return this._coreService.triggerDataEvent(o.C0.ESC+"P1$r0m"+o.C0.ESC+"\\");case" q":var i={block:2,underline:4,bar:6}[this._optionsService.options.cursorStyle];return this._coreService.triggerDataEvent(o.C0.ESC+"P1$r"+(i-=this._optionsService.options.cursorBlink?1:0)+" q"+o.C0.ESC+"\\");default:this._logService.debug("Unknown DCS $q %s",t),this._coreService.triggerDataEvent(o.C0.ESC+"P0$r"+o.C0.ESC+"\\")}}else this._data=new Uint32Array(0)},e}(),C=function(e){function t(t,i,r,n,s,c,h,p,m){void 0===m&&(m=new l.EscapeSequenceParser);var v=e.call(this)||this;v._bufferService=t,v._charsetService=i,v._coreService=r,v._dirtyRowService=n,v._logService=s,v._optionsService=c,v._coreMouseService=h,v._unicodeService=p,v._parser=m,v._parseBuffer=new Uint32Array(4096),v._stringDecoder=new u.StringToUtf32,v._utf8Decoder=new u.Utf8ToUtf32,v._workCell=new _.CellData,v._windowTitle="",v._iconName="",v._windowTitleStack=[],v._iconNameStack=[],v._curAttrData=d.DEFAULT_ATTR_DATA.clone(),v._eraseAttrDataInternal=d.DEFAULT_ATTR_DATA.clone(),v._onRequestBell=new f.EventEmitter,v._onRequestRefreshRows=new f.EventEmitter,v._onRequestReset=new f.EventEmitter,v._onRequestScroll=new f.EventEmitter,v._onRequestSyncScrollBar=new f.EventEmitter,v._onRequestWindowsOptionsReport=new f.EventEmitter,v._onA11yChar=new f.EventEmitter,v._onA11yTab=new f.EventEmitter,v._onCursorMove=new f.EventEmitter,v._onLineFeed=new f.EventEmitter,v._onScroll=new f.EventEmitter,v._onTitleChange=new f.EventEmitter,v.register(v._parser),v._parser.setCsiHandlerFallback((function(e,t){v._logService.debug("Unknown CSI code: ",{identifier:v._parser.identToString(e),params:t.toArray()})})),v._parser.setEscHandlerFallback((function(e){v._logService.debug("Unknown ESC code: ",{identifier:v._parser.identToString(e)})})),v._parser.setExecuteHandlerFallback((function(e){v._logService.debug("Unknown EXECUTE code: ",{code:e})})),v._parser.setOscHandlerFallback((function(e,t,i){v._logService.debug("Unknown OSC code: ",{identifier:e,action:t,data:i})})),v._parser.setDcsHandlerFallback((function(e,t,i){"HOOK"===t&&(i=i.toArray()),v._logService.debug("Unknown DCS code: ",{identifier:v._parser.identToString(e),action:t,payload:i})})),v._parser.setPrintHandler((function(e,t,i){return v.print(e,t,i)})),v._parser.setCsiHandler({final:"@"},(function(e){return v.insertChars(e)})),v._parser.setCsiHandler({intermediates:" ",final:"@"},(function(e){return v.scrollLeft(e)})),v._parser.setCsiHandler({final:"A"},(function(e){return v.cursorUp(e)})),v._parser.setCsiHandler({intermediates:" ",final:"A"},(function(e){return v.scrollRight(e)})),v._parser.setCsiHandler({final:"B"},(function(e){return v.cursorDown(e)})),v._parser.setCsiHandler({final:"C"},(function(e){return v.cursorForward(e)})),v._parser.setCsiHandler({final:"D"},(function(e){return v.cursorBackward(e)})),v._parser.setCsiHandler({final:"E"},(function(e){return v.cursorNextLine(e)})),v._parser.setCsiHandler({final:"F"},(function(e){return v.cursorPrecedingLine(e)})),v._parser.setCsiHandler({final:"G"},(function(e){return v.cursorCharAbsolute(e)})),v._parser.setCsiHandler({final:"H"},(function(e){return v.cursorPosition(e)})),v._parser.setCsiHandler({final:"I"},(function(e){return v.cursorForwardTab(e)})),v._parser.setCsiHandler({final:"J"},(function(e){return v.eraseInDisplay(e)})),v._parser.setCsiHandler({prefix:"?",final:"J"},(function(e){return v.eraseInDisplay(e)})),v._parser.setCsiHandler({final:"K"},(function(e){return v.eraseInLine(e)})),v._parser.setCsiHandler({prefix:"?",final:"K"},(function(e){return v.eraseInLine(e)})),v._parser.setCsiHandler({final:"L"},(function(e){return v.insertLines(e)})),v._parser.setCsiHandler({final:"M"},(function(e){return v.deleteLines(e)})),v._parser.setCsiHandler({final:"P"},(function(e){return v.deleteChars(e)})),v._parser.setCsiHandler({final:"S"},(function(e){return v.scrollUp(e)})),v._parser.setCsiHandler({final:"T"},(function(e){return v.scrollDown(e)})),v._parser.setCsiHandler({final:"X"},(function(e){return v.eraseChars(e)})),v._parser.setCsiHandler({final:"Z"},(function(e){return v.cursorBackwardTab(e)})),v._parser.setCsiHandler({final:"`"},(function(e){return v.charPosAbsolute(e)})),v._parser.setCsiHandler({final:"a"},(function(e){return v.hPositionRelative(e)})),v._parser.setCsiHandler({final:"b"},(function(e){return v.repeatPrecedingCharacter(e)})),v._parser.setCsiHandler({final:"c"},(function(e){return v.sendDeviceAttributesPrimary(e)})),v._parser.setCsiHandler({prefix:">",final:"c"},(function(e){return v.sendDeviceAttributesSecondary(e)})),v._parser.setCsiHandler({final:"d"},(function(e){return v.linePosAbsolute(e)})),v._parser.setCsiHandler({final:"e"},(function(e){return v.vPositionRelative(e)})),v._parser.setCsiHandler({final:"f"},(function(e){return v.hVPosition(e)})),v._parser.setCsiHandler({final:"g"},(function(e){return v.tabClear(e)})),v._parser.setCsiHandler({final:"h"},(function(e){return v.setMode(e)})),v._parser.setCsiHandler({prefix:"?",final:"h"},(function(e){return v.setModePrivate(e)})),v._parser.setCsiHandler({final:"l"},(function(e){return v.resetMode(e)})),v._parser.setCsiHandler({prefix:"?",final:"l"},(function(e){return v.resetModePrivate(e)})),v._parser.setCsiHandler({final:"m"},(function(e){return v.charAttributes(e)})),v._parser.setCsiHandler({final:"n"},(function(e){return v.deviceStatus(e)})),v._parser.setCsiHandler({prefix:"?",final:"n"},(function(e){return v.deviceStatusPrivate(e)})),v._parser.setCsiHandler({intermediates:"!",final:"p"},(function(e){return v.softReset(e)})),v._parser.setCsiHandler({intermediates:" ",final:"q"},(function(e){return v.setCursorStyle(e)})),v._parser.setCsiHandler({final:"r"},(function(e){return v.setScrollRegion(e)})),v._parser.setCsiHandler({final:"s"},(function(e){return v.saveCursor(e)})),v._parser.setCsiHandler({final:"t"},(function(e){return v.windowOptions(e)})),v._parser.setCsiHandler({final:"u"},(function(e){return v.restoreCursor(e)})),v._parser.setCsiHandler({intermediates:"'",final:"}"},(function(e){return v.insertColumns(e)})),v._parser.setCsiHandler({intermediates:"'",final:"~"},(function(e){return v.deleteColumns(e)})),v._parser.setExecuteHandler(o.C0.BEL,(function(){return v.bell()})),v._parser.setExecuteHandler(o.C0.LF,(function(){return v.lineFeed()})),v._parser.setExecuteHandler(o.C0.VT,(function(){return v.lineFeed()})),v._parser.setExecuteHandler(o.C0.FF,(function(){return v.lineFeed()})),v._parser.setExecuteHandler(o.C0.CR,(function(){return v.carriageReturn()})),v._parser.setExecuteHandler(o.C0.BS,(function(){return v.backspace()})),v._parser.setExecuteHandler(o.C0.HT,(function(){return v.tab()})),v._parser.setExecuteHandler(o.C0.SO,(function(){return v.shiftOut()})),v._parser.setExecuteHandler(o.C0.SI,(function(){return v.shiftIn()})),v._parser.setExecuteHandler(o.C1.IND,(function(){return v.index()})),v._parser.setExecuteHandler(o.C1.NEL,(function(){return v.nextLine()})),v._parser.setExecuteHandler(o.C1.HTS,(function(){return v.tabSet()})),v._parser.setOscHandler(0,new g.OscHandler((function(e){v.setTitle(e),v.setIconName(e)}))),v._parser.setOscHandler(1,new g.OscHandler((function(e){return v.setIconName(e)}))),v._parser.setOscHandler(2,new g.OscHandler((function(e){return v.setTitle(e)}))),v._parser.setEscHandler({final:"7"},(function(){return v.saveCursor()})),v._parser.setEscHandler({final:"8"},(function(){return v.restoreCursor()})),v._parser.setEscHandler({final:"D"},(function(){return v.index()})),v._parser.setEscHandler({final:"E"},(function(){return v.nextLine()})),v._parser.setEscHandler({final:"H"},(function(){return v.tabSet()})),v._parser.setEscHandler({final:"M"},(function(){return v.reverseIndex()})),v._parser.setEscHandler({final:"="},(function(){return v.keypadApplicationMode()})),v._parser.setEscHandler({final:">"},(function(){return v.keypadNumericMode()})),v._parser.setEscHandler({final:"c"},(function(){return v.fullReset()})),v._parser.setEscHandler({final:"n"},(function(){return v.setgLevel(2)})),v._parser.setEscHandler({final:"o"},(function(){return v.setgLevel(3)})),v._parser.setEscHandler({final:"|"},(function(){return v.setgLevel(3)})),v._parser.setEscHandler({final:"}"},(function(){return v.setgLevel(2)})),v._parser.setEscHandler({final:"~"},(function(){return v.setgLevel(1)})),v._parser.setEscHandler({intermediates:"%",final:"@"},(function(){return v.selectDefaultCharset()})),v._parser.setEscHandler({intermediates:"%",final:"G"},(function(){return v.selectDefaultCharset()}));var y=function(e){b._parser.setEscHandler({intermediates:"(",final:e},(function(){return v.selectCharset("("+e)})),b._parser.setEscHandler({intermediates:")",final:e},(function(){return v.selectCharset(")"+e)})),b._parser.setEscHandler({intermediates:"*",final:e},(function(){return v.selectCharset("*"+e)})),b._parser.setEscHandler({intermediates:"+",final:e},(function(){return v.selectCharset("+"+e)})),b._parser.setEscHandler({intermediates:"-",final:e},(function(){return v.selectCharset("-"+e)})),b._parser.setEscHandler({intermediates:".",final:e},(function(){return v.selectCharset("."+e)})),b._parser.setEscHandler({intermediates:"/",final:e},(function(){return v.selectCharset("/"+e)}))},b=this;for(var C in a.CHARSETS)y(C);return v._parser.setEscHandler({intermediates:"#",final:"8"},(function(){return v.screenAlignmentPattern()})),v._parser.setErrorHandler((function(e){return v._logService.error("Parsing error: ",e),e})),v._parser.setDcsHandler({intermediates:"$",final:"q"},new w(v._bufferService,v._coreService,v._logService,v._optionsService)),v}return n(t,e),Object.defineProperty(t.prototype,"onRequestBell",{get:function(){return this._onRequestBell.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestRefreshRows",{get:function(){return this._onRequestRefreshRows.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestReset",{get:function(){return this._onRequestReset.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestScroll",{get:function(){return this._onRequestScroll.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestSyncScrollBar",{get:function(){return this._onRequestSyncScrollBar.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestWindowsOptionsReport",{get:function(){return this._onRequestWindowsOptionsReport.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onA11yChar",{get:function(){return this._onA11yChar.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onA11yTab",{get:function(){return this._onA11yTab.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onCursorMove",{get:function(){return this._onCursorMove.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onLineFeed",{get:function(){return this._onLineFeed.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onScroll",{get:function(){return this._onScroll.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onTitleChange",{get:function(){return this._onTitleChange.event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){e.prototype.dispose.call(this)},t.prototype.parse=function(e){var t=this._bufferService.buffer,i=t.x,r=t.y;if(this._logService.debug("parsing data",e),this._parseBuffer.length131072)for(var n=0;n0&&2===f.getWidth(s.x-1)&&f.setCellFromCodePoint(s.x-1,0,1,d.fg,d.bg,d.extended);for(var _=t;_=l)if(c){for(;s.x=this._bufferService.rows&&(s.y=this._bufferService.rows-1),s.lines.get(s.ybase+s.y).isWrapped=!0),f=s.lines.get(s.ybase+s.y)}else if(s.x=l-1,2===n)continue;if(h&&(f.insertCells(s.x,n,s.getNullCell(d),d),2===f.getWidth(l-1)&&f.setCellFromCodePoint(l-1,p.NULL_CELL_CODE,p.NULL_CELL_WIDTH,d.fg,d.bg,d.extended)),f.setCellFromCodePoint(s.x++,r,n,d.fg,d.bg,d.extended),n>0)for(;--n;)f.setCellFromCodePoint(s.x++,0,0,d.fg,d.bg,d.extended)}else f.getWidth(s.x-1)?f.addCodepointToCell(s.x-1,r):f.addCodepointToCell(s.x-2,r)}i-t>0&&(f.loadCell(s.x-1,this._workCell),this._parser.precedingCodepoint=2===this._workCell.getWidth()||this._workCell.getCode()>65535?0:this._workCell.isCombined()?this._workCell.getChars().charCodeAt(0):this._workCell.content),s.x0&&0===f.getWidth(s.x)&&!f.hasContent(s.x)&&f.setCellFromCodePoint(s.x,0,1,d.fg,d.bg,d.extended),this._dirtyRowService.markDirty(s.y)},t.prototype.addCsiHandler=function(e,t){var i=this;return this._parser.addCsiHandler(e,"t"!==e.final||e.prefix||e.intermediates?t:function(e){return!b(e.params[0],i._optionsService.options.windowOptions)||t(e)})},t.prototype.addDcsHandler=function(e,t){return this._parser.addDcsHandler(e,new v.DcsHandler(t))},t.prototype.addEscHandler=function(e,t){return this._parser.addEscHandler(e,t)},t.prototype.addOscHandler=function(e,t){return this._parser.addOscHandler(e,new g.OscHandler(t))},t.prototype.bell=function(){this._onRequestBell.fire()},t.prototype.lineFeed=function(){var e=this._bufferService.buffer;this._dirtyRowService.markDirty(e.y),this._optionsService.options.convertEol&&(e.x=0),e.y++,e.y===e.scrollBottom+1?(e.y--,this._onRequestScroll.fire(this._eraseAttrData())):e.y>=this._bufferService.rows&&(e.y=this._bufferService.rows-1),e.x>=this._bufferService.cols&&e.x--,this._dirtyRowService.markDirty(e.y),this._onLineFeed.fire()},t.prototype.carriageReturn=function(){this._bufferService.buffer.x=0},t.prototype.backspace=function(){var e,t=this._bufferService.buffer;if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),void(t.x>0&&t.x--);if(this._restrictCursor(this._bufferService.cols),t.x>0)t.x--;else if(0===t.x&&t.y>t.scrollTop&&t.y<=t.scrollBottom&&(null===(e=t.lines.get(t.ybase+t.y))||void 0===e?void 0:e.isWrapped)){t.lines.get(t.ybase+t.y).isWrapped=!1,t.y--,t.x=this._bufferService.cols-1;var i=t.lines.get(t.ybase+t.y);i.hasWidth(t.x)&&!i.hasContent(t.x)&&t.x--}this._restrictCursor()},t.prototype.tab=function(){if(!(this._bufferService.buffer.x>=this._bufferService.cols)){var e=this._bufferService.buffer.x;this._bufferService.buffer.x=this._bufferService.buffer.nextStop(),this._optionsService.options.screenReaderMode&&this._onA11yTab.fire(this._bufferService.buffer.x-e)}},t.prototype.shiftOut=function(){this._charsetService.setgLevel(1)},t.prototype.shiftIn=function(){this._charsetService.setgLevel(0)},t.prototype._restrictCursor=function(e){void 0===e&&(e=this._bufferService.cols-1),this._bufferService.buffer.x=Math.min(e,Math.max(0,this._bufferService.buffer.x)),this._bufferService.buffer.y=this._coreService.decPrivateModes.origin?Math.min(this._bufferService.buffer.scrollBottom,Math.max(this._bufferService.buffer.scrollTop,this._bufferService.buffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._bufferService.buffer.y)),this._dirtyRowService.markDirty(this._bufferService.buffer.y)},t.prototype._setCursor=function(e,t){this._dirtyRowService.markDirty(this._bufferService.buffer.y),this._coreService.decPrivateModes.origin?(this._bufferService.buffer.x=e,this._bufferService.buffer.y=this._bufferService.buffer.scrollTop+t):(this._bufferService.buffer.x=e,this._bufferService.buffer.y=t),this._restrictCursor(),this._dirtyRowService.markDirty(this._bufferService.buffer.y)},t.prototype._moveCursor=function(e,t){this._restrictCursor(),this._setCursor(this._bufferService.buffer.x+e,this._bufferService.buffer.y+t)},t.prototype.cursorUp=function(e){var t=this._bufferService.buffer.y-this._bufferService.buffer.scrollTop;this._moveCursor(0,t>=0?-Math.min(t,e.params[0]||1):-(e.params[0]||1))},t.prototype.cursorDown=function(e){var t=this._bufferService.buffer.scrollBottom-this._bufferService.buffer.y;this._moveCursor(0,t>=0?Math.min(t,e.params[0]||1):e.params[0]||1)},t.prototype.cursorForward=function(e){this._moveCursor(e.params[0]||1,0)},t.prototype.cursorBackward=function(e){this._moveCursor(-(e.params[0]||1),0)},t.prototype.cursorNextLine=function(e){this.cursorDown(e),this._bufferService.buffer.x=0},t.prototype.cursorPrecedingLine=function(e){this.cursorUp(e),this._bufferService.buffer.x=0},t.prototype.cursorCharAbsolute=function(e){this._setCursor((e.params[0]||1)-1,this._bufferService.buffer.y)},t.prototype.cursorPosition=function(e){this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1)},t.prototype.charPosAbsolute=function(e){this._setCursor((e.params[0]||1)-1,this._bufferService.buffer.y)},t.prototype.hPositionRelative=function(e){this._moveCursor(e.params[0]||1,0)},t.prototype.linePosAbsolute=function(e){this._setCursor(this._bufferService.buffer.x,(e.params[0]||1)-1)},t.prototype.vPositionRelative=function(e){this._moveCursor(0,e.params[0]||1)},t.prototype.hVPosition=function(e){this.cursorPosition(e)},t.prototype.tabClear=function(e){var t=e.params[0];0===t?delete this._bufferService.buffer.tabs[this._bufferService.buffer.x]:3===t&&(this._bufferService.buffer.tabs={})},t.prototype.cursorForwardTab=function(e){if(!(this._bufferService.buffer.x>=this._bufferService.cols))for(var t=e.params[0]||1;t--;)this._bufferService.buffer.x=this._bufferService.buffer.nextStop()},t.prototype.cursorBackwardTab=function(e){if(!(this._bufferService.buffer.x>=this._bufferService.cols))for(var t=e.params[0]||1,i=this._bufferService.buffer;t--;)i.x=i.prevStop()},t.prototype._eraseInBufferLine=function(e,t,i,r){void 0===r&&(r=!1);var n=this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase+e);n.replaceCells(t,i,this._bufferService.buffer.getNullCell(this._eraseAttrData()),this._eraseAttrData()),r&&(n.isWrapped=!1)},t.prototype._resetBufferLine=function(e){var t=this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase+e);t.fill(this._bufferService.buffer.getNullCell(this._eraseAttrData())),t.isWrapped=!1},t.prototype.eraseInDisplay=function(e){var t;switch(this._restrictCursor(),e.params[0]){case 0:for(this._dirtyRowService.markDirty(t=this._bufferService.buffer.y),this._eraseInBufferLine(t++,this._bufferService.buffer.x,this._bufferService.cols,0===this._bufferService.buffer.x);t=this._bufferService.cols&&(this._bufferService.buffer.lines.get(t+1).isWrapped=!1);t--;)this._resetBufferLine(t);this._dirtyRowService.markDirty(0);break;case 2:for(this._dirtyRowService.markDirty((t=this._bufferService.rows)-1);t--;)this._resetBufferLine(t);this._dirtyRowService.markDirty(0);break;case 3:var i=this._bufferService.buffer.lines.length-this._bufferService.rows;i>0&&(this._bufferService.buffer.lines.trimStart(i),this._bufferService.buffer.ybase=Math.max(this._bufferService.buffer.ybase-i,0),this._bufferService.buffer.ydisp=Math.max(this._bufferService.buffer.ydisp-i,0),this._onScroll.fire(0))}},t.prototype.eraseInLine=function(e){switch(this._restrictCursor(),e.params[0]){case 0:this._eraseInBufferLine(this._bufferService.buffer.y,this._bufferService.buffer.x,this._bufferService.cols);break;case 1:this._eraseInBufferLine(this._bufferService.buffer.y,0,this._bufferService.buffer.x+1);break;case 2:this._eraseInBufferLine(this._bufferService.buffer.y,0,this._bufferService.cols)}this._dirtyRowService.markDirty(this._bufferService.buffer.y)},t.prototype.insertLines=function(e){this._restrictCursor();var t=e.params[0]||1,i=this._bufferService.buffer;if(!(i.y>i.scrollBottom||i.yi.scrollBottom||i.yt.scrollBottom||t.yt.scrollBottom||t.yt.scrollBottom||t.yt.scrollBottom||t.y0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent(o.C0.ESC+"[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent(o.C0.ESC+"[?6c"))},t.prototype.sendDeviceAttributesSecondary=function(e){e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent(o.C0.ESC+"[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent(o.C0.ESC+"[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent(o.C0.ESC+"[>83;40003;0c"))},t.prototype._is=function(e){return 0===(this._optionsService.options.termName+"").indexOf(e)},t.prototype.setMode=function(e){for(var t=0;t=2||2===r[1]&&s+n>=5)break;r[1]&&(n=1)}while(++s+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,0===e&&(t.fg&=-268435457),t.updateExtended()},t.prototype.charAttributes=function(e){if(1===e.length&&0===e.params[0])return this._curAttrData.fg=d.DEFAULT_ATTR_DATA.fg,void(this._curAttrData.bg=d.DEFAULT_ATTR_DATA.bg);for(var t,i=e.length,r=this._curAttrData,n=0;n=30&&t<=37?(r.fg&=-50331904,r.fg|=16777216|t-30):t>=40&&t<=47?(r.bg&=-50331904,r.bg|=16777216|t-40):t>=90&&t<=97?(r.fg&=-50331904,r.fg|=16777224|t-90):t>=100&&t<=107?(r.bg&=-50331904,r.bg|=16777224|t-100):0===t?(r.fg=d.DEFAULT_ATTR_DATA.fg,r.bg=d.DEFAULT_ATTR_DATA.bg):1===t?r.fg|=134217728:3===t?r.bg|=67108864:4===t?(r.fg|=268435456,this._processUnderline(e.hasSubParams(n)?e.getSubParams(n)[0]:1,r)):5===t?r.fg|=536870912:7===t?r.fg|=67108864:8===t?r.fg|=1073741824:2===t?r.bg|=134217728:21===t?this._processUnderline(2,r):22===t?(r.fg&=-134217729,r.bg&=-134217729):23===t?r.bg&=-67108865:24===t?r.fg&=-268435457:25===t?r.fg&=-536870913:27===t?r.fg&=-67108865:28===t?r.fg&=-1073741825:39===t?(r.fg&=-67108864,r.fg|=16777215&d.DEFAULT_ATTR_DATA.fg):49===t?(r.bg&=-67108864,r.bg|=16777215&d.DEFAULT_ATTR_DATA.bg):38===t||48===t||58===t?n+=this._extractColor(e,n,r):59===t?(r.extended=r.extended.clone(),r.extended.underlineColor=-1,r.updateExtended()):100===t?(r.fg&=-67108864,r.fg|=16777215&d.DEFAULT_ATTR_DATA.fg,r.bg&=-67108864,r.bg|=16777215&d.DEFAULT_ATTR_DATA.bg):this._logService.debug("Unknown SGR attribute: %d.",t)},t.prototype.deviceStatus=function(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent(o.C0.ESC+"[0n");break;case 6:this._coreService.triggerDataEvent(o.C0.ESC+"["+(this._bufferService.buffer.y+1)+";"+(this._bufferService.buffer.x+1)+"R")}},t.prototype.deviceStatusPrivate=function(e){switch(e.params[0]){case 6:this._coreService.triggerDataEvent(o.C0.ESC+"[?"+(this._bufferService.buffer.y+1)+";"+(this._bufferService.buffer.x+1)+"R")}},t.prototype.softReset=function(e){this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._bufferService.buffer.scrollTop=0,this._bufferService.buffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=d.DEFAULT_ATTR_DATA.clone(),this._coreService.reset(),this._charsetService.reset(),this._bufferService.buffer.savedX=0,this._bufferService.buffer.savedY=this._bufferService.buffer.ybase,this._bufferService.buffer.savedCurAttrData.fg=this._curAttrData.fg,this._bufferService.buffer.savedCurAttrData.bg=this._curAttrData.bg,this._bufferService.buffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1},t.prototype.setCursorStyle=function(e){var t=e.params[0]||1;switch(t){case 1:case 2:this._optionsService.options.cursorStyle="block";break;case 3:case 4:this._optionsService.options.cursorStyle="underline";break;case 5:case 6:this._optionsService.options.cursorStyle="bar"}this._optionsService.options.cursorBlink=t%2==1},t.prototype.setScrollRegion=function(e){var t,i=e.params[0]||1;(e.length<2||(t=e.params[1])>this._bufferService.rows||0===t)&&(t=this._bufferService.rows),t>i&&(this._bufferService.buffer.scrollTop=i-1,this._bufferService.buffer.scrollBottom=t-1,this._setCursor(0,0))},t.prototype.windowOptions=function(e){if(b(e.params[0],this._optionsService.options.windowOptions)){var t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:2!==t&&this._onRequestWindowsOptionsReport.fire(s.GET_WIN_SIZE_PIXELS);break;case 16:this._onRequestWindowsOptionsReport.fire(s.GET_CELL_SIZE_PIXELS);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(o.C0.ESC+"[8;"+this._bufferService.rows+";"+this._bufferService.cols+"t");break;case 22:0!==t&&2!==t||(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>10&&this._windowTitleStack.shift()),0!==t&&1!==t||(this._iconNameStack.push(this._iconName),this._iconNameStack.length>10&&this._iconNameStack.shift());break;case 23:0!==t&&2!==t||this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),0!==t&&1!==t||this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop())}}},t.prototype.saveCursor=function(e){this._bufferService.buffer.savedX=this._bufferService.buffer.x,this._bufferService.buffer.savedY=this._bufferService.buffer.ybase+this._bufferService.buffer.y,this._bufferService.buffer.savedCurAttrData.fg=this._curAttrData.fg,this._bufferService.buffer.savedCurAttrData.bg=this._curAttrData.bg,this._bufferService.buffer.savedCharset=this._charsetService.charset},t.prototype.restoreCursor=function(e){this._bufferService.buffer.x=this._bufferService.buffer.savedX||0,this._bufferService.buffer.y=Math.max(this._bufferService.buffer.savedY-this._bufferService.buffer.ybase,0),this._curAttrData.fg=this._bufferService.buffer.savedCurAttrData.fg,this._curAttrData.bg=this._bufferService.buffer.savedCurAttrData.bg,this._charsetService.charset=this._savedCharset,this._bufferService.buffer.savedCharset&&(this._charsetService.charset=this._bufferService.buffer.savedCharset),this._restrictCursor()},t.prototype.setTitle=function(e){this._windowTitle=e,this._onTitleChange.fire(e)},t.prototype.setIconName=function(e){this._iconName=e},t.prototype.nextLine=function(){this._bufferService.buffer.x=0,this.index()},t.prototype.keypadApplicationMode=function(){this._logService.debug("Serial port requested application keypad."),this._coreService.decPrivateModes.applicationKeypad=!0,this._onRequestSyncScrollBar.fire()},t.prototype.keypadNumericMode=function(){this._logService.debug("Switching back to normal keypad."),this._coreService.decPrivateModes.applicationKeypad=!1,this._onRequestSyncScrollBar.fire()},t.prototype.selectDefaultCharset=function(){this._charsetService.setgLevel(0),this._charsetService.setgCharset(0,a.DEFAULT_CHARSET)},t.prototype.selectCharset=function(e){2===e.length?"/"!==e[0]&&this._charsetService.setgCharset(y[e[0]],a.CHARSETS[e[1]]||a.DEFAULT_CHARSET):this.selectDefaultCharset()},t.prototype.index=function(){this._restrictCursor();var e=this._bufferService.buffer;this._bufferService.buffer.y++,e.y===e.scrollBottom+1?(e.y--,this._onRequestScroll.fire(this._eraseAttrData())):e.y>=this._bufferService.rows&&(e.y=this._bufferService.rows-1),this._restrictCursor()},t.prototype.tabSet=function(){this._bufferService.buffer.tabs[this._bufferService.buffer.x]=!0},t.prototype.reverseIndex=function(){this._restrictCursor();var e=this._bufferService.buffer;e.y===e.scrollTop?(e.lines.shiftElements(e.ybase+e.y,e.scrollBottom-e.scrollTop,1),e.lines.set(e.ybase+e.y,e.getBlankLine(this._eraseAttrData())),this._dirtyRowService.markRangeDirty(e.scrollTop,e.scrollBottom)):(e.y--,this._restrictCursor())},t.prototype.fullReset=function(){this._parser.reset(),this._onRequestReset.fire()},t.prototype.reset=function(){this._curAttrData=d.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=d.DEFAULT_ATTR_DATA.clone()},t.prototype._eraseAttrData=function(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=67108863&this._curAttrData.bg,this._eraseAttrDataInternal},t.prototype.setgLevel=function(e){this._charsetService.setgLevel(e)},t.prototype.screenAlignmentPattern=function(){var e=new _.CellData;e.content=1<<22|"E".charCodeAt(0),e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg;var t=this._bufferService.buffer;this._setCursor(0,0);for(var i=0;i256)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(e),this.length=0,this._subParams=new Int32Array(t),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(e),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}return e.fromArray=function(t){var i=new e;if(!t.length)return i;for(var r=t[0]instanceof Array?1:0;r>8,r=255&this._subParamsIdx[t];r-i>0&&e.push(Array.prototype.slice.call(this._subParams,i,r))}return e},e.prototype.reset=function(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1},e.prototype.addParam=function(e){if(this._digitIsSub=!1,this.length>=this.maxLength)this._rejectDigits=!0;else{if(e<-1)throw new Error("values lesser than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=e>2147483647?2147483647:e}},e.prototype.addSubParam=function(e){if(this._digitIsSub=!0,this.length)if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength)this._rejectSubDigits=!0;else{if(e<-1)throw new Error("values lesser than -1 are not allowed");this._subParams[this._subParamsLength++]=e>2147483647?2147483647:e,this._subParamsIdx[this.length-1]++}},e.prototype.hasSubParams=function(e){return(255&this._subParamsIdx[e])-(this._subParamsIdx[e]>>8)>0},e.prototype.getSubParams=function(e){var t=this._subParamsIdx[e]>>8,i=255&this._subParamsIdx[e];return i-t>0?this._subParams.subarray(t,i):null},e.prototype.getSubParamsAll=function(){for(var e={},t=0;t>8,r=255&this._subParamsIdx[t];r-i>0&&(e[t]=this._subParams.slice(i,r))}return e},e.prototype.addDigit=function(e){var t;if(!(this._rejectDigits||!(t=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)){var i=this._digitIsSub?this._subParams:this.params,r=i[t-1];i[t-1]=~r?Math.min(10*r+e,2147483647):e}},e}();t.Params=r},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OscHandler=t.OscParser=void 0;var r=i(23),n=i(8),s=function(){function e(){this._state=0,this._id=-1,this._handlers=Object.create(null),this._handlerFb=function(){}}return e.prototype.addHandler=function(e,t){void 0===this._handlers[e]&&(this._handlers[e]=[]);var i=this._handlers[e];return i.push(t),{dispose:function(){var e=i.indexOf(t);-1!==e&&i.splice(e,1)}}},e.prototype.setHandler=function(e,t){this._handlers[e]=[t]},e.prototype.clearHandler=function(e){this._handlers[e]&&delete this._handlers[e]},e.prototype.setHandlerFallback=function(e){this._handlerFb=e},e.prototype.dispose=function(){this._handlers=Object.create(null),this._handlerFb=function(){}},e.prototype.reset=function(){2===this._state&&this.end(!1),this._id=-1,this._state=0},e.prototype._start=function(){var e=this._handlers[this._id];if(e)for(var t=e.length-1;t>=0;t--)e[t].start();else this._handlerFb(this._id,"START")},e.prototype._put=function(e,t,i){var r=this._handlers[this._id];if(r)for(var s=r.length-1;s>=0;s--)r[s].put(e,t,i);else this._handlerFb(this._id,"PUT",n.utf32ToString(e,t,i))},e.prototype._end=function(e){var t=this._handlers[this._id];if(t){for(var i=t.length-1;i>=0&&!1===t[i].end(e);i--);for(i--;i>=0;i--)t[i].end(!1)}else this._handlerFb(this._id,"END",e)},e.prototype.start=function(){this.reset(),this._id=-1,this._state=1},e.prototype.put=function(e,t,i){if(3!==this._state){if(1===this._state)for(;t0&&this._put(e,t,i)}},e.prototype.end=function(e){0!==this._state&&(3!==this._state&&(1===this._state&&this._start(),this._end(e)),this._id=-1,this._state=0)},e}();t.OscParser=s;var o=function(){function e(e){this._handler=e,this._data="",this._hitLimit=!1}return e.prototype.start=function(){this._data="",this._hitLimit=!1},e.prototype.put=function(e,t,i){this._hitLimit||(this._data+=n.utf32ToString(e,t,i),this._data.length>r.PAYLOAD_LIMIT&&(this._data="",this._hitLimit=!0))},e.prototype.end=function(e){var t;return this._hitLimit?t=!1:e&&(t=this._handler(this._data)),this._data="",this._hitLimit=!1,t},e}();t.OscHandler=o},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PAYLOAD_LIMIT=void 0,t.PAYLOAD_LIMIT=1e7},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.DcsHandler=t.DcsParser=void 0;var r=i(8),n=i(21),s=i(23),o=[],a=function(){function e(){this._handlers=Object.create(null),this._active=o,this._ident=0,this._handlerFb=function(){}}return e.prototype.dispose=function(){this._handlers=Object.create(null),this._handlerFb=function(){}},e.prototype.addHandler=function(e,t){void 0===this._handlers[e]&&(this._handlers[e]=[]);var i=this._handlers[e];return i.push(t),{dispose:function(){var e=i.indexOf(t);-1!==e&&i.splice(e,1)}}},e.prototype.setHandler=function(e,t){this._handlers[e]=[t]},e.prototype.clearHandler=function(e){this._handlers[e]&&delete this._handlers[e]},e.prototype.setHandlerFallback=function(e){this._handlerFb=e},e.prototype.reset=function(){this._active.length&&this.unhook(!1),this._active=o,this._ident=0},e.prototype.hook=function(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||o,this._active.length)for(var i=this._active.length-1;i>=0;i--)this._active[i].hook(t);else this._handlerFb(this._ident,"HOOK",t)},e.prototype.put=function(e,t,i){if(this._active.length)for(var n=this._active.length-1;n>=0;n--)this._active[n].put(e,t,i);else this._handlerFb(this._ident,"PUT",r.utf32ToString(e,t,i))},e.prototype.unhook=function(e){if(this._active.length){for(var t=this._active.length-1;t>=0&&!1===this._active[t].unhook(e);t--);for(t--;t>=0;t--)this._active[t].unhook(!1)}else this._handlerFb(this._ident,"UNHOOK",e);this._active=o,this._ident=0},e}();t.DcsParser=a;var l=function(){function e(e){this._handler=e,this._data="",this._hitLimit=!1}return e.prototype.hook=function(e){this._params=e.clone(),this._data="",this._hitLimit=!1},e.prototype.put=function(e,t,i){this._hitLimit||(this._data+=r.utf32ToString(e,t,i),this._data.length>s.PAYLOAD_LIMIT&&(this._data="",this._hitLimit=!0))},e.prototype.unhook=function(e){var t;return this._hitLimit?t=!1:e&&(t=this._handler(this._data,this._params||new n.Params)),this._params=void 0,this._data="",this._hitLimit=!1,t},e}();t.DcsHandler=l},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.removeTerminalFromCache=t.acquireCharAtlas=void 0;var r=i(26),n=i(43),s=[];t.acquireCharAtlas=function(e,t,i,o,a){for(var l=r.generateConfig(o,a,e,i),c=0;c=0){if(r.configEquals(u.config,l))return u.atlas;1===u.ownedBy.length?(u.atlas.dispose(),s.splice(c,1)):u.ownedBy.splice(h,1);break}}for(c=0;c1)for(var u=this._getJoinedRanges(r,a,s,t,n),d=0;d1)for(u=this._getJoinedRanges(r,a,s,t,n),d=0;d=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new r.CellData)},e.prototype.translateToString=function(e,t,i){return this._line.translateToString(e,t,i)},e}(),d=function(){function e(e){this._core=e}return e.prototype.registerCsiHandler=function(e,t){return this._core.addCsiHandler(e,(function(e){return t(e.toArray())}))},e.prototype.addCsiHandler=function(e,t){return this.registerCsiHandler(e,t)},e.prototype.registerDcsHandler=function(e,t){return this._core.addDcsHandler(e,(function(e,i){return t(e,i.toArray())}))},e.prototype.addDcsHandler=function(e,t){return this.registerDcsHandler(e,t)},e.prototype.registerEscHandler=function(e,t){return this._core.addEscHandler(e,t)},e.prototype.addEscHandler=function(e,t){return this.registerEscHandler(e,t)},e.prototype.registerOscHandler=function(e,t){return this._core.addOscHandler(e,t)},e.prototype.addOscHandler=function(e,t){return this.registerOscHandler(e,t)},e}(),f=function(){function e(e){this._core=e}return e.prototype.register=function(e){this._core.unicodeService.register(e)},Object.defineProperty(e.prototype,"versions",{get:function(){return this._core.unicodeService.versions},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"activeVersion",{get:function(){return this._core.unicodeService.activeVersion},set:function(e){this._core.unicodeService.activeVersion=e},enumerable:!1,configurable:!0}),e}()},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)});Object.defineProperty(t,"__esModule",{value:!0}),t.Terminal=void 0;var s=i(36),o=i(37),a=i(38),l=i(12),c=i(19),h=i(40),u=i(50),d=i(51),f=i(11),p=i(7),_=i(18),m=i(54),g=i(55),v=i(56),y=i(57),b=i(59),w=i(0),C=i(16),S=i(27),k=i(60),x=i(5),E=i(61),A=i(62),O=i(63),T=i(64),R=i(65),L="undefined"!=typeof window?window.document:null,P=function(e){function t(t){void 0===t&&(t={});var i=e.call(this,t)||this;return i.browser=f,i._keyDownHandled=!1,i._onCursorMove=new w.EventEmitter,i._onKey=new w.EventEmitter,i._onRender=new w.EventEmitter,i._onSelectionChange=new w.EventEmitter,i._onTitleChange=new w.EventEmitter,i._onFocus=new w.EventEmitter,i._onBlur=new w.EventEmitter,i._onA11yCharEmitter=new w.EventEmitter,i._onA11yTabEmitter=new w.EventEmitter,i._setup(),i.linkifier=i._instantiationService.createInstance(u.Linkifier),i.linkifier2=i.register(i._instantiationService.createInstance(O.Linkifier2)),i.register(i._inputHandler.onRequestBell((function(){return i.bell()}))),i.register(i._inputHandler.onRequestRefreshRows((function(e,t){return i.refresh(e,t)}))),i.register(i._inputHandler.onRequestReset((function(){return i.reset()}))),i.register(i._inputHandler.onRequestScroll((function(e,t){return i.scroll(e,t||void 0)}))),i.register(i._inputHandler.onRequestWindowsOptionsReport((function(e){return i._reportWindowsOptions(e)}))),i.register(w.forwardEvent(i._inputHandler.onCursorMove,i._onCursorMove)),i.register(w.forwardEvent(i._inputHandler.onTitleChange,i._onTitleChange)),i.register(w.forwardEvent(i._inputHandler.onA11yChar,i._onA11yCharEmitter)),i.register(w.forwardEvent(i._inputHandler.onA11yTab,i._onA11yTabEmitter)),i.register(i._bufferService.onResize((function(e){return i._afterResize(e.cols,e.rows)}))),i}return n(t,e),Object.defineProperty(t.prototype,"options",{get:function(){return this.optionsService.options},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onCursorMove",{get:function(){return this._onCursorMove.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onKey",{get:function(){return this._onKey.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRender",{get:function(){return this._onRender.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onSelectionChange",{get:function(){return this._onSelectionChange.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onTitleChange",{get:function(){return this._onTitleChange.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onFocus",{get:function(){return this._onFocus.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onBlur",{get:function(){return this._onBlur.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onA11yChar",{get:function(){return this._onA11yCharEmitter.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onA11yTab",{get:function(){return this._onA11yTabEmitter.event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){var t,i,r;this._isDisposed||(e.prototype.dispose.call(this),null===(t=this._renderService)||void 0===t||t.dispose(),this._customKeyEventHandler=void 0,this.write=function(){},null===(r=null===(i=this.element)||void 0===i?void 0:i.parentNode)||void 0===r||r.removeChild(this.element))},t.prototype._setup=function(){e.prototype._setup.call(this),this._customKeyEventHandler=void 0},Object.defineProperty(t.prototype,"buffer",{get:function(){return this.buffers.active},enumerable:!1,configurable:!0}),t.prototype.focus=function(){this.textarea&&this.textarea.focus({preventScroll:!0})},t.prototype._updateOptions=function(t){var i,r,n,s;switch(e.prototype._updateOptions.call(this,t),t){case"fontFamily":case"fontSize":null===(i=this._renderService)||void 0===i||i.clear(),null===(r=this._charSizeService)||void 0===r||r.measure();break;case"cursorBlink":case"cursorStyle":this.refresh(this.buffer.y,this.buffer.y);break;case"drawBoldTextInBrightColors":case"letterSpacing":case"lineHeight":case"fontWeight":case"fontWeightBold":case"minimumContrastRatio":this._renderService&&(this._renderService.clear(),this._renderService.onResize(this.cols,this.rows),this.refresh(0,this.rows-1));break;case"rendererType":this._renderService&&(this._renderService.setRenderer(this._createRenderer()),this._renderService.onResize(this.cols,this.rows));break;case"scrollback":null===(n=this.viewport)||void 0===n||n.syncScrollArea();break;case"screenReaderMode":this.optionsService.options.screenReaderMode?!this._accessibilityManager&&this._renderService&&(this._accessibilityManager=new v.AccessibilityManager(this,this._renderService)):(null===(s=this._accessibilityManager)||void 0===s||s.dispose(),this._accessibilityManager=void 0);break;case"tabStopWidth":this.buffers.setupTabStops();break;case"theme":this._setTheme(this.optionsService.options.theme)}},t.prototype._onTextAreaFocus=function(e){this._coreService.decPrivateModes.sendFocus&&this._coreService.triggerDataEvent(l.C0.ESC+"[I"),this.updateCursorStyle(e),this.element.classList.add("focus"),this._showCursor(),this._onFocus.fire()},t.prototype.blur=function(){var e;return null===(e=this.textarea)||void 0===e?void 0:e.blur()},t.prototype._onTextAreaBlur=function(){this.textarea.value="",this.refresh(this.buffer.y,this.buffer.y),this._coreService.decPrivateModes.sendFocus&&this._coreService.triggerDataEvent(l.C0.ESC+"[O"),this.element.classList.remove("focus"),this._onBlur.fire()},t.prototype._syncTextArea=function(){if(this.textarea&&this.buffer.isCursorInViewport&&!this._compositionHelper.isComposing){var e=Math.ceil(this._charSizeService.height*this.optionsService.options.lineHeight),t=this._bufferService.buffer.y*e;this.textarea.style.left=this._bufferService.buffer.x*this._charSizeService.width+"px",this.textarea.style.top=t+"px",this.textarea.style.width=this._charSizeService.width+"px",this.textarea.style.height=e+"px",this.textarea.style.lineHeight=e+"px",this.textarea.style.zIndex="-5"}},t.prototype._initGlobal=function(){var e=this;this._bindKeys(),this.register(p.addDisposableDomListener(this.element,"copy",(function(t){e.hasSelection()&&a.copyHandler(t,e._selectionService)})));var t=function(t){return a.handlePasteEvent(t,e.textarea,e._coreService)};this.register(p.addDisposableDomListener(this.textarea,"paste",t)),this.register(p.addDisposableDomListener(this.element,"paste",t)),this.register(f.isFirefox?p.addDisposableDomListener(this.element,"mousedown",(function(t){2===t.button&&a.rightClickHandler(t,e.textarea,e.screenElement,e._selectionService,e.options.rightClickSelectsWord)})):p.addDisposableDomListener(this.element,"contextmenu",(function(t){a.rightClickHandler(t,e.textarea,e.screenElement,e._selectionService,e.options.rightClickSelectsWord)}))),f.isLinux&&this.register(p.addDisposableDomListener(this.element,"auxclick",(function(t){1===t.button&&a.moveTextAreaUnderMouseCursor(t,e.textarea,e.screenElement)})))},t.prototype._bindKeys=function(){var e=this;this.register(p.addDisposableDomListener(this.textarea,"keyup",(function(t){return e._keyUp(t)}),!0)),this.register(p.addDisposableDomListener(this.textarea,"keydown",(function(t){return e._keyDown(t)}),!0)),this.register(p.addDisposableDomListener(this.textarea,"keypress",(function(t){return e._keyPress(t)}),!0)),this.register(p.addDisposableDomListener(this.textarea,"compositionstart",(function(){return e._compositionHelper.compositionstart()}))),this.register(p.addDisposableDomListener(this.textarea,"compositionupdate",(function(t){return e._compositionHelper.compositionupdate(t)}))),this.register(p.addDisposableDomListener(this.textarea,"compositionend",(function(){return e._compositionHelper.compositionend()}))),this.register(this.onRender((function(){return e._compositionHelper.updateCompositionElements()}))),this.register(this.onRender((function(t){return e._queueLinkification(t.start,t.end)})))},t.prototype.open=function(e){var t=this;if(!e)throw new Error("Terminal requires a parent element.");L.body.contains(e)||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this._document=e.ownerDocument,this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.setAttribute("tabindex","0"),e.appendChild(this.element);var i=L.createDocumentFragment();this._viewportElement=L.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),i.appendChild(this._viewportElement),this._viewportScrollArea=L.createElement("div"),this._viewportScrollArea.classList.add("xterm-scroll-area"),this._viewportElement.appendChild(this._viewportScrollArea),this.screenElement=L.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._helperContainer=L.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),i.appendChild(this.screenElement),this.textarea=L.createElement("textarea"),this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",_.promptLabel),this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this.register(p.addDisposableDomListener(this.textarea,"focus",(function(e){return t._onTextAreaFocus(e)}))),this.register(p.addDisposableDomListener(this.textarea,"blur",(function(){return t._onTextAreaBlur()}))),this._helperContainer.appendChild(this.textarea);var r=this._instantiationService.createInstance(T.CoreBrowserService,this.textarea);this._instantiationService.setService(x.ICoreBrowserService,r),this._charSizeService=this._instantiationService.createInstance(E.CharSizeService,this._document,this._helperContainer),this._instantiationService.setService(x.ICharSizeService,this._charSizeService),this._compositionView=L.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(s.CompositionHelper,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this.element.appendChild(i),this._theme=this.options.theme||this._theme,this._colorManager=new S.ColorManager(L,this.options.allowTransparency),this.register(this.optionsService.onOptionChange((function(e){return t._colorManager.onOptionsChange(e)}))),this._colorManager.setTheme(this._theme);var n=this._createRenderer();this._renderService=this.register(this._instantiationService.createInstance(k.RenderService,n,this.rows,this.screenElement)),this._instantiationService.setService(x.IRenderService,this._renderService),this.register(this._renderService.onRenderedBufferChange((function(e){return t._onRender.fire(e)}))),this.onResize((function(e){return t._renderService.resize(e.cols,e.rows)})),this._soundService=this._instantiationService.createInstance(m.SoundService),this._instantiationService.setService(x.ISoundService,this._soundService),this._mouseService=this._instantiationService.createInstance(A.MouseService),this._instantiationService.setService(x.IMouseService,this._mouseService),this.viewport=this._instantiationService.createInstance(o.Viewport,(function(e,i){return t.scrollLines(e,i)}),this._viewportElement,this._viewportScrollArea),this.viewport.onThemeChange(this._colorManager.colors),this.register(this._inputHandler.onRequestSyncScrollBar((function(){return t.viewport.syncScrollArea()}))),this.register(this.viewport),this.register(this.onCursorMove((function(){t._renderService.onCursorMove(),t._syncTextArea()}))),this.register(this.onResize((function(){return t._renderService.onResize(t.cols,t.rows)}))),this.register(this.onBlur((function(){return t._renderService.onBlur()}))),this.register(this.onFocus((function(){return t._renderService.onFocus()}))),this.register(this._renderService.onDimensionsChange((function(){return t.viewport.syncScrollArea()}))),this._selectionService=this.register(this._instantiationService.createInstance(d.SelectionService,this.element,this.screenElement)),this._instantiationService.setService(x.ISelectionService,this._selectionService),this.register(this._selectionService.onRequestScrollLines((function(e){return t.scrollLines(e.amount,e.suppressScrollEvent)}))),this.register(this._selectionService.onSelectionChange((function(){return t._onSelectionChange.fire()}))),this.register(this._selectionService.onRequestRedraw((function(e){return t._renderService.onSelectionChanged(e.start,e.end,e.columnSelectMode)}))),this.register(this._selectionService.onLinuxMouseSelection((function(e){t.textarea.value=e,t.textarea.focus(),t.textarea.select()}))),this.register(this.onScroll((function(){t.viewport.syncScrollArea(),t._selectionService.refresh()}))),this.register(p.addDisposableDomListener(this._viewportElement,"scroll",(function(){return t._selectionService.refresh()}))),this._mouseZoneManager=this._instantiationService.createInstance(g.MouseZoneManager,this.element,this.screenElement),this.register(this._mouseZoneManager),this.register(this.onScroll((function(){return t._mouseZoneManager.clearAll()}))),this.linkifier.attachToDom(this.element,this._mouseZoneManager),this.linkifier2.attachToDom(this.element,this._mouseService,this._renderService),this.register(p.addDisposableDomListener(this.element,"mousedown",(function(e){return t._selectionService.onMouseDown(e)}))),this._coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager=new v.AccessibilityManager(this,this._renderService)),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()},t.prototype._createRenderer=function(){switch(this.options.rendererType){case"canvas":return this._instantiationService.createInstance(h.Renderer,this._colorManager.colors,this.screenElement,this.linkifier,this.linkifier2);case"dom":return this._instantiationService.createInstance(y.DomRenderer,this._colorManager.colors,this.element,this.screenElement,this._viewportElement,this.linkifier,this.linkifier2);default:throw new Error('Unrecognized rendererType "'+this.options.rendererType+'"')}},t.prototype._setTheme=function(e){var t,i,r;this._theme=e,null===(t=this._colorManager)||void 0===t||t.setTheme(e),null===(i=this._renderService)||void 0===i||i.setColors(this._colorManager.colors),null===(r=this.viewport)||void 0===r||r.onThemeChange(this._colorManager.colors)},t.prototype.bindMouse=function(){var e=this,t=this,i=this.element;function r(e){var i,r,n=t._mouseService.getRawByteCoords(e,t.screenElement,t.cols,t.rows);if(!n)return!1;switch(e.overrideType||e.type){case"mousemove":r=32,void 0===e.buttons?(i=3,void 0!==e.button&&(i=e.button<3?e.button:3)):i=1&e.buttons?0:4&e.buttons?1:2&e.buttons?2:3;break;case"mouseup":r=0,i=e.button<3?e.button:3;break;case"mousedown":r=1,i=e.button<3?e.button:3;break;case"wheel":0!==e.deltaY&&(r=e.deltaY<0?0:1),i=4;break;default:return!1}return!(void 0===r||void 0===i||i>4)&&t._coreMouseService.triggerMouseEvent({col:n.x-33,row:n.y-33,button:i,action:r,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey})}var n={mouseup:null,wheel:null,mousedrag:null,mousemove:null},s=function(t){return r(t),t.buttons||(e._document.removeEventListener("mouseup",n.mouseup),n.mousedrag&&e._document.removeEventListener("mousemove",n.mousedrag)),e.cancel(t)},o=function(t){return r(t),t.preventDefault(),e.cancel(t)},a=function(e){e.buttons&&r(e)},c=function(e){e.buttons||r(e)};this.register(this._coreMouseService.onProtocolChange((function(t){t?("debug"===e.optionsService.options.logLevel&&e._logService.debug("Binding to mouse events:",e._coreMouseService.explainEvents(t)),e.element.classList.add("enable-mouse-events"),e._selectionService.disable()):(e._logService.debug("Unbinding from mouse events."),e.element.classList.remove("enable-mouse-events"),e._selectionService.enable()),8&t?n.mousemove||(i.addEventListener("mousemove",c),n.mousemove=c):(i.removeEventListener("mousemove",n.mousemove),n.mousemove=null),16&t?n.wheel||(i.addEventListener("wheel",o,{passive:!1}),n.wheel=o):(i.removeEventListener("wheel",n.wheel),n.wheel=null),2&t?n.mouseup||(n.mouseup=s):(e._document.removeEventListener("mouseup",n.mouseup),n.mouseup=null),4&t?n.mousedrag||(n.mousedrag=a):(e._document.removeEventListener("mousemove",n.mousedrag),n.mousedrag=null)}))),this._coreMouseService.activeProtocol=this._coreMouseService.activeProtocol,this.register(p.addDisposableDomListener(i,"mousedown",(function(t){if(t.preventDefault(),e.focus(),e._coreMouseService.areMouseEventsActive&&!e._selectionService.shouldForceSelection(t))return r(t),n.mouseup&&e._document.addEventListener("mouseup",n.mouseup),n.mousedrag&&e._document.addEventListener("mousemove",n.mousedrag),e.cancel(t)}))),this.register(p.addDisposableDomListener(i,"wheel",(function(t){if(n.wheel);else if(!e.buffer.hasScrollback){var i=e.viewport.getLinesScrolled(t);if(0===i)return;for(var r=l.C0.ESC+(e._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(t.deltaY<0?"A":"B"),s="",o=0;o47)},t.prototype._keyUp=function(e){this._customKeyEventHandler&&!1===this._customKeyEventHandler(e)||(function(e){return 16===e.keyCode||17===e.keyCode||18===e.keyCode}(e)||this.focus(),this.updateCursorStyle(e))},t.prototype._keyPress=function(e){var t;if(this._keyDownHandled)return!1;if(this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;if(this.cancel(e),e.charCode)t=e.charCode;else if(null==e.which)t=e.keyCode;else{if(0===e.which||0===e.charCode)return!1;t=e.which}return!(!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)||(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this._coreService.triggerDataEvent(t,!0),0))},t.prototype.bell=function(){this._soundBell()&&this._soundService.playBellSound()},t.prototype.resize=function(t,i){t!==this.cols||i!==this.rows?e.prototype.resize.call(this,t,i):this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure()},t.prototype._afterResize=function(e,t){var i,r;null===(i=this._charSizeService)||void 0===i||i.measure(),null===(r=this.viewport)||void 0===r||r.syncScrollArea(!0)},t.prototype.clear=function(){if(0!==this.buffer.ybase||0!==this.buffer.y){this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(var e=1;e=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},n=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CompositionHelper=void 0;var s=i(5),o=i(1),a=function(){function e(e,t,i,r,n,s){this._textarea=e,this._compositionView=t,this._bufferService=i,this._optionsService=r,this._charSizeService=n,this._coreService=s,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0}}return Object.defineProperty(e.prototype,"isComposing",{get:function(){return this._isComposing},enumerable:!1,configurable:!0}),e.prototype.compositionstart=function(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent="",this._compositionView.classList.add("active")},e.prototype.compositionupdate=function(e){var t=this;this._compositionView.textContent=e.data,this.updateCompositionElements(),setTimeout((function(){t._compositionPosition.end=t._textarea.value.length}),0)},e.prototype.compositionend=function(){this._finalizeComposition(!0)},e.prototype.keydown=function(e){if(this._isComposing||this._isSendingComposition){if(229===e.keyCode)return!1;if(16===e.keyCode||17===e.keyCode||18===e.keyCode)return!1;this._finalizeComposition(!1)}return 229!==e.keyCode||(this._handleAnyTextareaChanges(),!1)},e.prototype._finalizeComposition=function(e){var t=this;if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){var i={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout((function(){var e;t._isSendingComposition&&(t._isSendingComposition=!1,e=t._isComposing?t._textarea.value.substring(i.start,i.end):t._textarea.value.substring(i.start),t._coreService.triggerDataEvent(e,!0))}),0)}else{this._isSendingComposition=!1;var r=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(r,!0)}},e.prototype._handleAnyTextareaChanges=function(){var e=this,t=this._textarea.value;setTimeout((function(){if(!e._isComposing){var i=e._textarea.value.replace(t,"");i.length>0&&e._coreService.triggerDataEvent(i,!0)}}),0)},e.prototype.updateCompositionElements=function(e){var t=this;if(this._isComposing){if(this._bufferService.buffer.isCursorInViewport){var i=Math.ceil(this._charSizeService.height*this._optionsService.options.lineHeight),r=this._bufferService.buffer.y*i,n=this._bufferService.buffer.x*this._charSizeService.width;this._compositionView.style.left=n+"px",this._compositionView.style.top=r+"px",this._compositionView.style.height=i+"px",this._compositionView.style.lineHeight=i+"px",this._compositionView.style.fontFamily=this._optionsService.options.fontFamily,this._compositionView.style.fontSize=this._optionsService.options.fontSize+"px";var s=this._compositionView.getBoundingClientRect();this._textarea.style.left=n+"px",this._textarea.style.top=r+"px",this._textarea.style.width=s.width+"px",this._textarea.style.height=s.height+"px",this._textarea.style.lineHeight=s.height+"px"}e||setTimeout((function(){return t.updateCompositionElements(!0)}),0)}},r([n(2,o.IBufferService),n(3,o.IOptionsService),n(4,s.ICharSizeService),n(5,o.ICoreService)],e)}();t.CompositionHelper=a},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),s=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},o=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Viewport=void 0;var a=i(2),l=i(7),c=i(5),h=i(1),u=function(e){function t(t,i,r,n,s,o,a){var c=e.call(this)||this;return c._scrollLines=t,c._viewportElement=i,c._scrollArea=r,c._bufferService=n,c._optionsService=s,c._charSizeService=o,c._renderService=a,c.scrollBarWidth=0,c._currentRowHeight=0,c._lastRecordedBufferLength=0,c._lastRecordedViewportHeight=0,c._lastRecordedBufferHeight=0,c._lastTouchY=0,c._lastScrollTop=0,c._wheelPartialScroll=0,c._refreshAnimationFrame=null,c._ignoreNextScrollEvent=!1,c.scrollBarWidth=c._viewportElement.offsetWidth-c._scrollArea.offsetWidth||15,c.register(l.addDisposableDomListener(c._viewportElement,"scroll",c._onScroll.bind(c))),setTimeout((function(){return c.syncScrollArea()}),0),c}return n(t,e),t.prototype.onThemeChange=function(e){this._viewportElement.style.backgroundColor=e.background.css},t.prototype._refresh=function(e){var t=this;if(e)return this._innerRefresh(),void(null!==this._refreshAnimationFrame&&cancelAnimationFrame(this._refreshAnimationFrame));null===this._refreshAnimationFrame&&(this._refreshAnimationFrame=requestAnimationFrame((function(){return t._innerRefresh()})))},t.prototype._innerRefresh=function(){if(this._charSizeService.height>0){this._currentRowHeight=this._renderService.dimensions.scaledCellHeight/window.devicePixelRatio,this._lastRecordedViewportHeight=this._viewportElement.offsetHeight;var e=Math.round(this._currentRowHeight*this._lastRecordedBufferLength)+(this._lastRecordedViewportHeight-this._renderService.dimensions.canvasHeight);this._lastRecordedBufferHeight!==e&&(this._lastRecordedBufferHeight=e,this._scrollArea.style.height=this._lastRecordedBufferHeight+"px")}var t=this._bufferService.buffer.ydisp*this._currentRowHeight;this._viewportElement.scrollTop!==t&&(this._ignoreNextScrollEvent=!0,this._viewportElement.scrollTop=t),this._refreshAnimationFrame=null},t.prototype.syncScrollArea=function(e){if(void 0===e&&(e=!1),this._lastRecordedBufferLength!==this._bufferService.buffer.lines.length)return this._lastRecordedBufferLength=this._bufferService.buffer.lines.length,void this._refresh(e);this._lastRecordedViewportHeight===this._renderService.dimensions.canvasHeight&&this._lastScrollTop===this._bufferService.buffer.ydisp*this._currentRowHeight&&this._lastScrollTop===this._viewportElement.scrollTop&&this._renderService.dimensions.scaledCellHeight/window.devicePixelRatio===this._currentRowHeight||this._refresh(e)},t.prototype._onScroll=function(e){if(this._lastScrollTop=this._viewportElement.scrollTop,this._viewportElement.offsetParent)if(this._ignoreNextScrollEvent)this._ignoreNextScrollEvent=!1;else{var t=Math.round(this._lastScrollTop/this._currentRowHeight)-this._bufferService.buffer.ydisp;this._scrollLines(t,!0)}},t.prototype._bubbleScroll=function(e,t){return!(t<0&&0!==this._viewportElement.scrollTop||t>0&&this._viewportElement.scrollTop+this._lastRecordedViewportHeight0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(t*=this._bufferService.rows),t},t.prototype._applyScrollModifier=function(e,t){var i=this._optionsService.options.fastScrollModifier;return"alt"===i&&t.altKey||"ctrl"===i&&t.ctrlKey||"shift"===i&&t.shiftKey?e*this._optionsService.options.fastScrollSensitivity*this._optionsService.options.scrollSensitivity:e*this._optionsService.options.scrollSensitivity},t.prototype.onTouchStart=function(e){this._lastTouchY=e.touches[0].pageY},t.prototype.onTouchMove=function(e){var t=this._lastTouchY-e.touches[0].pageY;return this._lastTouchY=e.touches[0].pageY,0!==t&&(this._viewportElement.scrollTop+=t,this._bubbleScroll(e,t))},s([o(3,h.IBufferService),o(4,h.IOptionsService),o(5,c.ICharSizeService),o(6,c.IRenderService)],t)}(a.Disposable);t.Viewport=u},function(e,t,i){"use strict";function r(e){return e.replace(/\r?\n/g,"\r")}function n(e,t){return t?"\x1b[200~"+e+"\x1b[201~":e}function s(e,t,i){e=n(e=r(e),i.decPrivateModes.bracketedPasteMode),i.triggerDataEvent(e,!0),t.value=""}function o(e,t,i){var r=i.getBoundingClientRect(),n=e.clientX-r.left-10,s=e.clientY-r.top-10;t.style.width="20px",t.style.height="20px",t.style.left=n+"px",t.style.top=s+"px",t.style.zIndex="1000",t.focus()}Object.defineProperty(t,"__esModule",{value:!0}),t.rightClickHandler=t.moveTextAreaUnderMouseCursor=t.paste=t.handlePasteEvent=t.copyHandler=t.bracketTextForPaste=t.prepareTextForTerminal=void 0,t.prepareTextForTerminal=r,t.bracketTextForPaste=n,t.copyHandler=function(e,t){e.clipboardData&&e.clipboardData.setData("text/plain",t.selectionText),e.preventDefault()},t.handlePasteEvent=function(e,t,i){e.stopPropagation(),e.clipboardData&&s(e.clipboardData.getData("text/plain"),t,i)},t.paste=s,t.moveTextAreaUnderMouseCursor=o,t.rightClickHandler=function(e,t,i,r,n){o(e,t,i),n&&!r.isClickInSelection(e)&&r.selectWordAtCursor(e),t.value=r.selectionText,t.select()}},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)});Object.defineProperty(t,"__esModule",{value:!0}),t.EscapeSequenceParser=t.VT500_TRANSITION_TABLE=t.TransitionTable=void 0;var s=i(2),o=i(15),a=i(21),l=i(22),c=i(24),h=function(){function e(e){this.table=new Uint8Array(e)}return e.prototype.setDefault=function(e,t){o.fill(this.table,e<<4|t)},e.prototype.add=function(e,t,i,r){this.table[t<<8|e]=i<<4|r},e.prototype.addMany=function(e,t,i,r){for(var n=0;n1)throw new Error("only one byte as prefix supported");if((i=e.prefix.charCodeAt(0))&&60>i||i>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(var r=0;rn||n>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");i<<=8,i|=n}}if(1!==e.final.length)throw new Error("final must be a single byte");var s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error("final must be in range "+t[0]+" .. "+t[1]);return(i<<=8)|s},i.prototype.identToString=function(e){for(var t=[];e;)t.push(String.fromCharCode(255&e)),e>>=8;return t.reverse().join("")},i.prototype.dispose=function(){this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null),this._oscParser.dispose(),this._dcsParser.dispose()},i.prototype.setPrintHandler=function(e){this._printHandler=e},i.prototype.clearPrintHandler=function(){this._printHandler=this._printHandlerFb},i.prototype.addEscHandler=function(e,t){var i=this._identifier(e,[48,126]);void 0===this._escHandlers[i]&&(this._escHandlers[i]=[]);var r=this._escHandlers[i];return r.push(t),{dispose:function(){var e=r.indexOf(t);-1!==e&&r.splice(e,1)}}},i.prototype.setEscHandler=function(e,t){this._escHandlers[this._identifier(e,[48,126])]=[t]},i.prototype.clearEscHandler=function(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]},i.prototype.setEscHandlerFallback=function(e){this._escHandlerFb=e},i.prototype.setExecuteHandler=function(e,t){this._executeHandlers[e.charCodeAt(0)]=t},i.prototype.clearExecuteHandler=function(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]},i.prototype.setExecuteHandlerFallback=function(e){this._executeHandlerFb=e},i.prototype.addCsiHandler=function(e,t){var i=this._identifier(e);void 0===this._csiHandlers[i]&&(this._csiHandlers[i]=[]);var r=this._csiHandlers[i];return r.push(t),{dispose:function(){var e=r.indexOf(t);-1!==e&&r.splice(e,1)}}},i.prototype.setCsiHandler=function(e,t){this._csiHandlers[this._identifier(e)]=[t]},i.prototype.clearCsiHandler=function(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]},i.prototype.setCsiHandlerFallback=function(e){this._csiHandlerFb=e},i.prototype.addDcsHandler=function(e,t){return this._dcsParser.addHandler(this._identifier(e),t)},i.prototype.setDcsHandler=function(e,t){this._dcsParser.setHandler(this._identifier(e),t)},i.prototype.clearDcsHandler=function(e){this._dcsParser.clearHandler(this._identifier(e))},i.prototype.setDcsHandlerFallback=function(e){this._dcsParser.setHandlerFallback(e)},i.prototype.addOscHandler=function(e,t){return this._oscParser.addHandler(e,t)},i.prototype.setOscHandler=function(e,t){this._oscParser.setHandler(e,t)},i.prototype.clearOscHandler=function(e){this._oscParser.clearHandler(e)},i.prototype.setOscHandlerFallback=function(e){this._oscParser.setHandlerFallback(e)},i.prototype.setErrorHandler=function(e){this._errorHandler=e},i.prototype.clearErrorHandler=function(){this._errorHandler=this._errorHandlerFb},i.prototype.reset=function(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingCodepoint=0},i.prototype.parse=function(e,t){for(var i=0,r=0,n=this.currentState,s=this._oscParser,o=this._dcsParser,a=this._collect,l=this._params,c=this._transitions.table,h=0;h>4){case 2:for(var u=h+1;;++u){if(u>=t||(i=e[u])<32||i>126&&i<160){this._printHandler(e,h,u),h=u-1;break}if(++u>=t||(i=e[u])<32||i>126&&i<160){this._printHandler(e,h,u),h=u-1;break}if(++u>=t||(i=e[u])<32||i>126&&i<160){this._printHandler(e,h,u),h=u-1;break}if(++u>=t||(i=e[u])<32||i>126&&i<160){this._printHandler(e,h,u),h=u-1;break}}break;case 3:this._executeHandlers[i]?this._executeHandlers[i]():this._executeHandlerFb(i),this.precedingCodepoint=0;break;case 0:break;case 1:if(this._errorHandler({position:h,code:i,currentState:n,collect:a,params:l,abort:!1}).abort)return;break;case 7:for(var d=this._csiHandlers[a<<8|i],f=d?d.length-1:-1;f>=0&&!1===d[f](l);f--);f<0&&this._csiHandlerFb(a<<8|i,l),this.precedingCodepoint=0;break;case 8:do{switch(i){case 59:l.addParam(0);break;case 58:l.addSubParam(-1);break;default:l.addDigit(i-48)}}while(++h47&&i<60);h--;break;case 9:a<<=8,a|=i;break;case 10:for(var p=this._escHandlers[a<<8|i],_=p?p.length-1:-1;_>=0&&!1===p[_]();_--);_<0&&this._escHandlerFb(a<<8|i),this.precedingCodepoint=0;break;case 11:l.reset(),l.addParam(0),a=0;break;case 12:o.hook(a<<8|i,l);break;case 13:for(var m=h+1;;++m)if(m>=t||24===(i=e[m])||26===i||27===i||i>127&&i<160){o.put(e,h,m),h=m-1;break}break;case 14:o.unhook(24!==i&&26!==i),27===i&&(r|=1),l.reset(),l.addParam(0),a=0,this.precedingCodepoint=0;break;case 4:s.start();break;case 5:for(var g=h+1;;g++)if(g>=t||(i=e[g])<32||i>127&&i<=159){s.put(e,h,g),h=g-1;break}break;case 6:s.end(24!==i&&26!==i),27===i&&(r|=1),l.reset(),l.addParam(0),a=0,this.precedingCodepoint=0}n=15&r}this._collect=a,this.currentState=n},i}(s.Disposable);t.EscapeSequenceParser=u},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),s=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},o=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Renderer=void 0;var a=i(41),l=i(47),c=i(48),h=i(49),u=i(29),d=i(2),f=i(5),p=i(1),_=i(25),m=i(0),g=1,v=function(e){function t(t,i,r,n,s,o,d,f,p){var _=e.call(this)||this;_._colors=t,_._screenElement=i,_._bufferService=s,_._charSizeService=o,_._optionsService=d,_._id=g++,_._onRequestRedraw=new m.EventEmitter;var v=_._optionsService.options.allowTransparency;return _._characterJoinerRegistry=new u.CharacterJoinerRegistry(_._bufferService),_._renderLayers=[new a.TextRenderLayer(_._screenElement,0,_._colors,_._characterJoinerRegistry,v,_._id,_._bufferService,d),new l.SelectionRenderLayer(_._screenElement,1,_._colors,_._id,_._bufferService,d),new h.LinkRenderLayer(_._screenElement,2,_._colors,_._id,r,n,_._bufferService,d),new c.CursorRenderLayer(_._screenElement,3,_._colors,_._id,_._onRequestRedraw,_._bufferService,d,f,p)],_.dimensions={scaledCharWidth:0,scaledCharHeight:0,scaledCellWidth:0,scaledCellHeight:0,scaledCharLeft:0,scaledCharTop:0,scaledCanvasWidth:0,scaledCanvasHeight:0,canvasWidth:0,canvasHeight:0,actualCellWidth:0,actualCellHeight:0},_._devicePixelRatio=window.devicePixelRatio,_._updateDimensions(),_.onOptionsChanged(),_}return n(t,e),Object.defineProperty(t.prototype,"onRequestRedraw",{get:function(){return this._onRequestRedraw.event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){for(var t=0,i=this._renderLayers;t0&&h===a[0][0]){d=!0;var p=a.shift();u=new c.JoinedCellData(this._workCell,o.translateToString(!0,p[0],p[1]),p[1]-p[0]),f=p[1]-1}!d&&this._isOverlapping(u)&&fthis._characterWidth;return this._ctx.restore(),this._characterOverlapCache[t]=i,i},t}(o.BaseRenderLayer);t.TextRenderLayer=u},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.GridCache=void 0;var r=function(){function e(){this.cache=[]}return e.prototype.resize=function(e,t){for(var i=0;i>>24,n=t.rgba>>>16&255,s=t.rgba>>>8&255,o=0;o=this.capacity)this._unlinkNode(i=this._head),delete this._map[i.key],i.key=e,i.value=t,this._map[e]=i;else{var r=this._nodePool;r.length>0?((i=r.pop()).key=e,i.value=t):i={prev:null,next:null,key:e,value:t},this._map[e]=i,this.size++}this._appendNode(i)},e}();t.LRUMap=r},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)});Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionRenderLayer=void 0;var s=function(e){function t(t,i,r,n,s,o){var a=e.call(this,t,"selection",i,!0,r,n,s,o)||this;return a._clearState(),a}return n(t,e),t.prototype._clearState=function(){this._state={start:void 0,end:void 0,columnSelectMode:void 0,ydisp:void 0}},t.prototype.resize=function(t){e.prototype.resize.call(this,t),this._clearState()},t.prototype.reset=function(){this._state.start&&this._state.end&&(this._clearState(),this._clearAll())},t.prototype.onSelectionChanged=function(e,t,i){if(this._didStateChange(e,t,i,this._bufferService.buffer.ydisp))if(this._clearAll(),e&&t){var r=e[1]-this._bufferService.buffer.ydisp,n=t[1]-this._bufferService.buffer.ydisp,s=Math.max(r,0),o=Math.min(n,this._bufferService.rows-1);if(s>=this._bufferService.rows||o<0)this._state.ydisp=this._bufferService.buffer.ydisp;else{if(this._ctx.fillStyle=this._colors.selectionTransparent.css,i){var a=e[0];this._fillCells(a,s,t[0]-a,o-s+1)}else{this._fillCells(a=r===s?e[0]:0,s,(s===n?t[0]:this._bufferService.cols)-a,1);var l=Math.max(o-s-1,0);this._fillCells(0,s+1,this._bufferService.cols,l),s!==o&&this._fillCells(0,o,n===o?t[0]:this._bufferService.cols,1)}this._state.start=[e[0],e[1]],this._state.end=[t[0],t[1]],this._state.columnSelectMode=i,this._state.ydisp=this._bufferService.buffer.ydisp}}else this._clearState()},t.prototype._didStateChange=function(e,t,i,r){return!this._areCoordinatesEqual(e,this._state.start)||!this._areCoordinatesEqual(t,this._state.end)||i!==this._state.columnSelectMode||r!==this._state.ydisp},t.prototype._areCoordinatesEqual=function(e,t){return!(!e||!t)&&e[0]===t[0]&&e[1]===t[1]},t}(i(13).BaseRenderLayer);t.SelectionRenderLayer=s},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)});Object.defineProperty(t,"__esModule",{value:!0}),t.CursorRenderLayer=void 0;var s=i(13),o=i(4),a=function(e){function t(t,i,r,n,s,a,l,c,h){var u=e.call(this,t,"cursor",i,!0,r,n,a,l)||this;return u._onRequestRedraw=s,u._coreService=c,u._coreBrowserService=h,u._cell=new o.CellData,u._state={x:0,y:0,isFocused:!1,style:"",width:0},u._cursorRenderers={bar:u._renderBarCursor.bind(u),block:u._renderBlockCursor.bind(u),underline:u._renderUnderlineCursor.bind(u)},u}return n(t,e),t.prototype.resize=function(t){e.prototype.resize.call(this,t),this._state={x:0,y:0,isFocused:!1,style:"",width:0}},t.prototype.reset=function(){this._clearCursor(),this._cursorBlinkStateManager&&(this._cursorBlinkStateManager.dispose(),this._cursorBlinkStateManager=void 0,this.onOptionsChanged())},t.prototype.onBlur=function(){this._cursorBlinkStateManager&&this._cursorBlinkStateManager.pause(),this._onRequestRedraw.fire({start:this._bufferService.buffer.y,end:this._bufferService.buffer.y})},t.prototype.onFocus=function(){this._cursorBlinkStateManager?this._cursorBlinkStateManager.resume():this._onRequestRedraw.fire({start:this._bufferService.buffer.y,end:this._bufferService.buffer.y})},t.prototype.onOptionsChanged=function(){var e,t=this;this._optionsService.options.cursorBlink?this._cursorBlinkStateManager||(this._cursorBlinkStateManager=new l(this._coreBrowserService.isFocused,(function(){t._render(!0)}))):(null===(e=this._cursorBlinkStateManager)||void 0===e||e.dispose(),this._cursorBlinkStateManager=void 0),this._onRequestRedraw.fire({start:this._bufferService.buffer.y,end:this._bufferService.buffer.y})},t.prototype.onCursorMove=function(){this._cursorBlinkStateManager&&this._cursorBlinkStateManager.restartBlinkAnimation()},t.prototype.onGridChanged=function(e,t){!this._cursorBlinkStateManager||this._cursorBlinkStateManager.isPaused?this._render(!1):this._cursorBlinkStateManager.restartBlinkAnimation()},t.prototype._render=function(e){if(this._coreService.isCursorInitialized&&!this._coreService.isCursorHidden){var t=this._bufferService.buffer.ybase+this._bufferService.buffer.y,i=t-this._bufferService.buffer.ydisp;if(i<0||i>=this._bufferService.rows)this._clearCursor();else{var r=Math.min(this._bufferService.buffer.x,this._bufferService.cols-1);if(this._bufferService.buffer.lines.get(t).loadCell(r,this._cell),void 0!==this._cell.content){if(!this._coreBrowserService.isFocused){this._clearCursor(),this._ctx.save(),this._ctx.fillStyle=this._colors.cursor.css;var n=this._optionsService.options.cursorStyle;return n&&"block"!==n?this._cursorRenderers[n](r,i,this._cell):this._renderBlurCursor(r,i,this._cell),this._ctx.restore(),this._state.x=r,this._state.y=i,this._state.isFocused=!1,this._state.style=n,void(this._state.width=this._cell.getWidth())}if(!this._cursorBlinkStateManager||this._cursorBlinkStateManager.isCursorVisible){if(this._state){if(this._state.x===r&&this._state.y===i&&this._state.isFocused===this._coreBrowserService.isFocused&&this._state.style===this._optionsService.options.cursorStyle&&this._state.width===this._cell.getWidth())return;this._clearCursor()}this._ctx.save(),this._cursorRenderers[this._optionsService.options.cursorStyle||"block"](r,i,this._cell),this._ctx.restore(),this._state.x=r,this._state.y=i,this._state.isFocused=!1,this._state.style=this._optionsService.options.cursorStyle,this._state.width=this._cell.getWidth()}else this._clearCursor()}}}else this._clearCursor()},t.prototype._clearCursor=function(){this._state&&(this._clearCells(this._state.x,this._state.y,this._state.width,1),this._state={x:0,y:0,isFocused:!1,style:"",width:0})},t.prototype._renderBarCursor=function(e,t,i){this._ctx.save(),this._ctx.fillStyle=this._colors.cursor.css,this._fillLeftLineAtCell(e,t,this._optionsService.options.cursorWidth),this._ctx.restore()},t.prototype._renderBlockCursor=function(e,t,i){this._ctx.save(),this._ctx.fillStyle=this._colors.cursor.css,this._fillCells(e,t,i.getWidth(),1),this._ctx.fillStyle=this._colors.cursorAccent.css,this._fillCharTrueColor(i,e,t),this._ctx.restore()},t.prototype._renderUnderlineCursor=function(e,t,i){this._ctx.save(),this._ctx.fillStyle=this._colors.cursor.css,this._fillBottomLineAtCells(e,t),this._ctx.restore()},t.prototype._renderBlurCursor=function(e,t,i){this._ctx.save(),this._ctx.strokeStyle=this._colors.cursor.css,this._strokeRectAtCell(e,t,i.getWidth(),1),this._ctx.restore()},t}(s.BaseRenderLayer);t.CursorRenderLayer=a;var l=function(){function e(e,t){this._renderCallback=t,this.isCursorVisible=!0,e&&this._restartInterval()}return Object.defineProperty(e.prototype,"isPaused",{get:function(){return!(this._blinkStartTimeout||this._blinkInterval)},enumerable:!1,configurable:!0}),e.prototype.dispose=function(){this._blinkInterval&&(window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)},e.prototype.restartBlinkAnimation=function(){var e=this;this.isPaused||(this._animationTimeRestarted=Date.now(),this.isCursorVisible=!0,this._animationFrame||(this._animationFrame=window.requestAnimationFrame((function(){e._renderCallback(),e._animationFrame=void 0}))))},e.prototype._restartInterval=function(e){var t=this;void 0===e&&(e=600),this._blinkInterval&&window.clearInterval(this._blinkInterval),this._blinkStartTimeout=window.setTimeout((function(){if(t._animationTimeRestarted){var e=600-(Date.now()-t._animationTimeRestarted);if(t._animationTimeRestarted=void 0,e>0)return void t._restartInterval(e)}t.isCursorVisible=!1,t._animationFrame=window.requestAnimationFrame((function(){t._renderCallback(),t._animationFrame=void 0})),t._blinkInterval=window.setInterval((function(){if(t._animationTimeRestarted){var e=600-(Date.now()-t._animationTimeRestarted);return t._animationTimeRestarted=void 0,void t._restartInterval(e)}t.isCursorVisible=!t.isCursorVisible,t._animationFrame=window.requestAnimationFrame((function(){t._renderCallback(),t._animationFrame=void 0}))}),600)}),e)},e.prototype.pause=function(){this.isCursorVisible=!0,this._blinkInterval&&(window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)},e.prototype.resume=function(){this.pause(),this._animationTimeRestarted=void 0,this._restartInterval(),this.restartBlinkAnimation()},e}()},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)});Object.defineProperty(t,"__esModule",{value:!0}),t.LinkRenderLayer=void 0;var s=i(13),o=i(9),a=i(26),l=function(e){function t(t,i,r,n,s,o,a,l){var c=e.call(this,t,"link",i,!0,r,n,a,l)||this;return s.onShowLinkUnderline((function(e){return c._onShowLinkUnderline(e)})),s.onHideLinkUnderline((function(e){return c._onHideLinkUnderline(e)})),o.onShowLinkUnderline((function(e){return c._onShowLinkUnderline(e)})),o.onHideLinkUnderline((function(e){return c._onHideLinkUnderline(e)})),c}return n(t,e),t.prototype.resize=function(t){e.prototype.resize.call(this,t),this._state=void 0},t.prototype.reset=function(){this._clearCurrentLink()},t.prototype._clearCurrentLink=function(){if(this._state){this._clearCells(this._state.x1,this._state.y1,this._state.cols-this._state.x1,1);var e=this._state.y2-this._state.y1-1;e>0&&this._clearCells(0,this._state.y1+1,this._state.cols,e),this._clearCells(0,this._state.y2,this._state.x2,1),this._state=void 0}},t.prototype._onShowLinkUnderline=function(e){if(this._ctx.fillStyle=e.fg===o.INVERTED_DEFAULT_COLOR?this._colors.background.css:e.fg&&a.is256Color(e.fg)?this._colors.ansi[e.fg].css:this._colors.foreground.css,e.y1===e.y2)this._fillBottomLineAtCells(e.x1,e.y1,e.x2-e.x1);else{this._fillBottomLineAtCells(e.x1,e.y1,e.cols-e.x1);for(var t=e.y1+1;t=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},n=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseZone=t.Linkifier=void 0;var s=i(0),o=i(1),a=function(){function e(e,t,i){this._bufferService=e,this._logService=t,this._unicodeService=i,this._linkMatchers=[],this._nextLinkMatcherId=0,this._onShowLinkUnderline=new s.EventEmitter,this._onHideLinkUnderline=new s.EventEmitter,this._onLinkTooltip=new s.EventEmitter,this._rowsToLinkify={start:void 0,end:void 0}}return Object.defineProperty(e.prototype,"onShowLinkUnderline",{get:function(){return this._onShowLinkUnderline.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onHideLinkUnderline",{get:function(){return this._onHideLinkUnderline.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onLinkTooltip",{get:function(){return this._onLinkTooltip.event},enumerable:!1,configurable:!0}),e.prototype.attachToDom=function(e,t){this._element=e,this._mouseZoneManager=t},e.prototype.linkifyRows=function(t,i){var r=this;this._mouseZoneManager&&(void 0===this._rowsToLinkify.start||void 0===this._rowsToLinkify.end?(this._rowsToLinkify.start=t,this._rowsToLinkify.end=i):(this._rowsToLinkify.start=Math.min(this._rowsToLinkify.start,t),this._rowsToLinkify.end=Math.max(this._rowsToLinkify.end,i)),this._mouseZoneManager.clearAll(t,i),this._rowsTimeoutId&&clearTimeout(this._rowsTimeoutId),this._rowsTimeoutId=setTimeout((function(){return r._linkifyRows()}),e._timeBeforeLatency))},e.prototype._linkifyRows=function(){this._rowsTimeoutId=void 0;var e=this._bufferService.buffer;if(void 0!==this._rowsToLinkify.start&&void 0!==this._rowsToLinkify.end){var t=e.ydisp+this._rowsToLinkify.start;if(!(t>=e.lines.length)){for(var i=e.ydisp+Math.min(this._rowsToLinkify.end,this._bufferService.rows)+1,r=Math.ceil(2e3/this._bufferService.cols),n=this._bufferService.buffer.iterator(!1,t,i,r,r);n.hasNext();)for(var s=n.next(),o=0;o=0;t--)if(e.priority<=this._linkMatchers[t].priority)return void this._linkMatchers.splice(t+1,0,e);this._linkMatchers.splice(0,0,e)}else this._linkMatchers.push(e)},e.prototype.deregisterLinkMatcher=function(e){for(var t=0;t>9&511:void 0;i.validationCallback?i.validationCallback(a,(function(e){n._rowsTimeoutId||e&&n._addLink(c[1],c[0]-n._bufferService.buffer.ydisp,a,i,d)})):l._addLink(c[1],c[0]-l._bufferService.buffer.ydisp,a,i,d)},l=this;null!==(r=s.exec(t))&&"break"!==a(););},e.prototype._addLink=function(e,t,i,r,n){var s=this;if(this._mouseZoneManager&&this._element){var o=this._unicodeService.getStringCellWidth(i),a=e%this._bufferService.cols,c=t+Math.floor(e/this._bufferService.cols),h=(a+o)%this._bufferService.cols,u=c+Math.floor((a+o)/this._bufferService.cols);0===h&&(h=this._bufferService.cols,u--),this._mouseZoneManager.add(new l(a+1,c+1,h+1,u+1,(function(e){if(r.handler)return r.handler(e,i);var t=window.open();t?(t.opener=null,t.location.href=i):console.warn("Opening link blocked as opener could not be cleared")}),(function(){s._onShowLinkUnderline.fire(s._createLinkHoverEvent(a,c,h,u,n)),s._element.classList.add("xterm-cursor-pointer")}),(function(e){s._onLinkTooltip.fire(s._createLinkHoverEvent(a,c,h,u,n)),r.hoverTooltipCallback&&r.hoverTooltipCallback(e,i,{start:{x:a,y:c},end:{x:h,y:u}})}),(function(){s._onHideLinkUnderline.fire(s._createLinkHoverEvent(a,c,h,u,n)),s._element.classList.remove("xterm-cursor-pointer"),r.hoverLeaveCallback&&r.hoverLeaveCallback()}),(function(e){return!r.willLinkActivate||r.willLinkActivate(e,i)})))}},e.prototype._createLinkHoverEvent=function(e,t,i,r,n){return{x1:e,y1:t,x2:i,y2:r,cols:this._bufferService.cols,fg:n}},e._timeBeforeLatency=200,e=r([n(0,o.IBufferService),n(1,o.ILogService),n(2,o.IUnicodeService)],e)}();t.Linkifier=a;var l=function(e,t,i,r,n,s,o,a,l){this.x1=e,this.y1=t,this.x2=i,this.y2=r,this.clickCallback=n,this.hoverCallback=s,this.tooltipCallback=o,this.leaveCallback=a,this.willLinkActivate=l};t.MouseZone=l},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),s=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},o=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionService=void 0;var a=i(11),l=i(52),c=i(4),h=i(0),u=i(5),d=i(1),f=i(30),p=i(53),_=i(2),m=String.fromCharCode(160),g=new RegExp(m,"g"),v=function(e){function t(t,i,r,n,s,o,a){var u=e.call(this)||this;return u._element=t,u._screenElement=i,u._bufferService=r,u._coreService=n,u._mouseService=s,u._optionsService=o,u._renderService=a,u._dragScrollAmount=0,u._enabled=!0,u._workCell=new c.CellData,u._mouseDownTimeStamp=0,u._onLinuxMouseSelection=u.register(new h.EventEmitter),u._onRedrawRequest=u.register(new h.EventEmitter),u._onSelectionChange=u.register(new h.EventEmitter),u._onRequestScrollLines=u.register(new h.EventEmitter),u._mouseMoveListener=function(e){return u._onMouseMove(e)},u._mouseUpListener=function(e){return u._onMouseUp(e)},u._coreService.onUserInput((function(){u.hasSelection&&u.clearSelection()})),u._trimListener=u._bufferService.buffer.lines.onTrim((function(e){return u._onTrim(e)})),u.register(u._bufferService.buffers.onBufferActivate((function(e){return u._onBufferActivate(e)}))),u.enable(),u._model=new l.SelectionModel(u._bufferService),u._activeSelectionMode=0,u}return n(t,e),Object.defineProperty(t.prototype,"onLinuxMouseSelection",{get:function(){return this._onLinuxMouseSelection.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestRedraw",{get:function(){return this._onRedrawRequest.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onSelectionChange",{get:function(){return this._onSelectionChange.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestScrollLines",{get:function(){return this._onRequestScrollLines.event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){this._removeMouseDownListeners()},t.prototype.reset=function(){this.clearSelection()},t.prototype.disable=function(){this.clearSelection(),this._enabled=!1},t.prototype.enable=function(){this._enabled=!0},Object.defineProperty(t.prototype,"selectionStart",{get:function(){return this._model.finalSelectionStart},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"selectionEnd",{get:function(){return this._model.finalSelectionEnd},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"hasSelection",{get:function(){var e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!(!e||!t||e[0]===t[0]&&e[1]===t[1])},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"selectionText",{get:function(){var e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";var i=this._bufferService.buffer,r=[];if(3===this._activeSelectionMode){if(e[0]===t[0])return"";for(var n=e[1];n<=t[1];n++){var s=i.translateBufferLineToString(n,!0,e[0],t[0]);r.push(s)}}else{for(r.push(i.translateBufferLineToString(e[1],!0,e[0],e[1]===t[1]?t[0]:void 0)),n=e[1]+1;n<=t[1]-1;n++){var o=i.lines.get(n);s=i.translateBufferLineToString(n,!0),o&&o.isWrapped?r[r.length-1]+=s:r.push(s)}e[1]!==t[1]&&(o=i.lines.get(t[1]),s=i.translateBufferLineToString(t[1],!0,0,t[0]),o&&o.isWrapped?r[r.length-1]+=s:r.push(s))}return r.map((function(e){return e.replace(g," ")})).join(a.isWindows?"\r\n":"\n")},enumerable:!1,configurable:!0}),t.prototype.clearSelection=function(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()},t.prototype.refresh=function(e){var t=this;this._refreshAnimationFrame||(this._refreshAnimationFrame=window.requestAnimationFrame((function(){return t._refresh()}))),a.isLinux&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)},t.prototype._refresh=function(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:3===this._activeSelectionMode})},t.prototype.isClickInSelection=function(e){var t=this._getMouseBufferCoords(e),i=this._model.finalSelectionStart,r=this._model.finalSelectionEnd;return!!(i&&r&&t)&&this._areCoordsInSelection(t,i,r)},t.prototype._areCoordsInSelection=function(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]},t.prototype.selectWordAtCursor=function(e){var t=this._getMouseBufferCoords(e);t&&(this._selectWordAt(t,!1),this._model.selectionEnd=void 0,this.refresh(!0))},t.prototype.selectAll=function(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()},t.prototype.selectLines=function(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()},t.prototype._onTrim=function(e){this._model.onTrim(e)&&this.refresh()},t.prototype._getMouseBufferCoords=function(e){var t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t},t.prototype._getMouseEventScrollAmount=function(e){var t=f.getCoordsRelativeToElement(e,this._screenElement)[1],i=this._renderService.dimensions.canvasHeight;return t>=0&&t<=i?0:(t>i&&(t-=i),t=Math.min(Math.max(t,-50),50),(t/=50)/Math.abs(t)+Math.round(14*t))},t.prototype.shouldForceSelection=function(e){return a.isMac?e.altKey&&this._optionsService.options.macOptionClickForcesSelection:e.shiftKey},t.prototype.onMouseDown=function(e){if(this._mouseDownTimeStamp=e.timeStamp,(2!==e.button||!this.hasSelection)&&0===e.button){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._onIncrementalClick(e):1===e.detail?this._onSingleClick(e):2===e.detail?this._onDoubleClick(e):3===e.detail&&this._onTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}},t.prototype._addMouseDownListeners=function(){var e=this;this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=window.setInterval((function(){return e._dragScroll()}),50)},t.prototype._removeMouseDownListeners=function(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0},t.prototype._onIncrementalClick=function(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))},t.prototype._onSingleClick=function(e){if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),this._model.selectionStart){this._model.selectionEnd=void 0;var t=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);t&&t.length!==this._model.selectionStart[0]&&0===t.hasWidth(this._model.selectionStart[0])&&this._model.selectionStart[0]++}},t.prototype._onDoubleClick=function(e){var t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=1,this._selectWordAt(t,!0))},t.prototype._onTripleClick=function(e){var t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))},t.prototype.shouldColumnSelect=function(e){return e.altKey&&!(a.isMac&&this._optionsService.options.macOptionClickForcesSelection)},t.prototype._onMouseMove=function(e){if(e.stopImmediatePropagation(),this._model.selectionStart){var t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),this._model.selectionEnd){2===this._activeSelectionMode?this._model.selectionEnd[0]=this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));var i=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows,e.lines.length-1)):(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}},t.prototype._onMouseUp=function(e){var t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&t<500&&e.altKey){if(this._bufferService.buffer.ybase===this._bufferService.buffer.ydisp){var i=this._mouseService.getCoords(e,this._element,this._bufferService.cols,this._bufferService.rows,!1);if(i&&void 0!==i[0]&&void 0!==i[1]){var r=p.moveToCellSequence(i[0]-1,i[1]-1,this._bufferService,this._coreService.decPrivateModes.applicationCursorKeys);this._coreService.triggerDataEvent(r,!0)}}}else this.hasSelection&&this._onSelectionChange.fire()},t.prototype._onBufferActivate=function(e){var t=this;this.clearSelection(),this._trimListener.dispose(),this._trimListener=e.activeBuffer.lines.onTrim((function(e){return t._onTrim(e)}))},t.prototype._convertViewportColToCharacterIndex=function(e,t){for(var i=t[0],r=0;t[0]>=r;r++){var n=e.loadCell(r,this._workCell).getChars().length;0===this._workCell.getWidth()?i--:n>1&&t[0]!==r&&(i+=n-1)}return i},t.prototype.setSelection=function(e,t,i){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=i,this.refresh()},t.prototype._getWordAt=function(e,t,i,r){if(void 0===i&&(i=!0),void 0===r&&(r=!0),!(e[0]>=this._bufferService.cols)){var n=this._bufferService.buffer,s=n.lines.get(e[1]);if(s){var o=n.translateBufferLineToString(e[1],!1),a=this._convertViewportColToCharacterIndex(s,e),l=a,c=e[0]-a,h=0,u=0,d=0,f=0;if(" "===o.charAt(a)){for(;a>0&&" "===o.charAt(a-1);)a--;for(;l1&&(f+=m-1,l+=m-1);p>0&&a>0&&!this._isCharWordSeparator(s.loadCell(p-1,this._workCell));){s.loadCell(p-1,this._workCell);var g=this._workCell.getChars().length;0===this._workCell.getWidth()?(h++,p--):g>1&&(d+=g-1,a-=g-1),a--,p--}for(;_1&&(f+=v-1,l+=v-1),l++,_++}}l++;var y=a+c-h+d,b=Math.min(this._bufferService.cols,l-a+h+u-d-f);if(t||""!==o.slice(a,l).trim()){if(i&&0===y&&32!==s.getCodePoint(0)){var w=n.lines.get(e[1]-1);if(w&&s.isWrapped&&32!==w.getCodePoint(this._bufferService.cols-1)){var C=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(C){var S=this._bufferService.cols-C.start;y-=S,b+=S}}}if(r&&y+b===this._bufferService.cols&&32!==s.getCodePoint(this._bufferService.cols-1)){var k=n.lines.get(e[1]+1);if(k&&k.isWrapped&&32!==k.getCodePoint(0)){var x=this._getWordAt([0,e[1]+1],!1,!1,!0);x&&(b+=x.length)}}return{start:y,length:b}}}}},t.prototype._selectWordAt=function(e,t){var i=this._getWordAt(e,t);if(i){for(;i.start<0;)i.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[i.start,e[1]],this._model.selectionStartLength=i.length}},t.prototype._selectToWordAt=function(e){var t=this._getWordAt(e,!0);if(t){for(var i=e[1];t.start<0;)t.start+=this._bufferService.cols,i--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,i++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,i]}},t.prototype._isCharWordSeparator=function(e){return 0!==e.getWidth()&&this._optionsService.options.wordSeparator.indexOf(e.getChars())>=0},t.prototype._selectLineAt=function(e){var t=this._bufferService.buffer.getWrappedRangeForLine(e);this._model.selectionStart=[0,t.first],this._model.selectionEnd=[this._bufferService.cols,t.last],this._model.selectionStartLength=0},s([o(2,d.IBufferService),o(3,d.ICoreService),o(4,u.IMouseService),o(5,d.IOptionsService),o(6,u.IRenderService)],t)}(_.Disposable);t.SelectionService=v},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionModel=void 0;var r=function(){function e(e){this._bufferService=e,this.isSelectAllActive=!1,this.selectionStartLength=0}return e.prototype.clearSelection=function(){this.selectionStart=void 0,this.selectionEnd=void 0,this.isSelectAllActive=!1,this.selectionStartLength=0},Object.defineProperty(e.prototype,"finalSelectionStart",{get:function(){return this.isSelectAllActive?[0,0]:this.selectionEnd&&this.selectionStart&&this.areSelectionValuesReversed()?this.selectionEnd:this.selectionStart},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"finalSelectionEnd",{get:function(){if(this.isSelectAllActive)return[this._bufferService.cols,this._bufferService.buffer.ybase+this._bufferService.rows-1];if(this.selectionStart){if(!this.selectionEnd||this.areSelectionValuesReversed()){var e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}return this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]?[Math.max(this.selectionStart[0]+this.selectionStartLength,this.selectionEnd[0]),this.selectionEnd[1]]:this.selectionEnd}},enumerable:!1,configurable:!0}),e.prototype.areSelectionValuesReversed=function(){var e=this.selectionStart,t=this.selectionEnd;return!(!e||!t)&&(e[1]>t[1]||e[1]===t[1]&&e[0]>t[0])},e.prototype.onTrim=function(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)},e}();t.SelectionModel=r},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.moveToCellSequence=void 0;var r=i(12);function n(e,t,i,r){var n=e-s(i,e),a=t-s(i,t);return c(Math.abs(n-a)-function(e,t,i){for(var r=0,n=e-s(i,e),a=t-s(i,t),l=0;l=0&&tt?"A":"B"}function a(e,t,i,r,n,s){for(var o=e,a=t,l="";o!==i||a!==r;)o+=n?1:-1,n&&o>s.cols-1?(l+=s.buffer.translateBufferLineToString(a,!1,e,o),o=0,e=0,a++):!n&&o<0&&(l+=s.buffer.translateBufferLineToString(a,!1,0,e+1),e=o=s.cols-1,a--);return l+s.buffer.translateBufferLineToString(a,!1,e,o)}function l(e,t){return r.C0.ESC+(t?"O":"[")+e}function c(e,t){e=Math.floor(e);for(var i="",r=0;r0?r-s(o,r):t;var d=r,f=function(e,t,i,r,o,a){var l;return l=n(i,r,o,a).length>0?r-s(o,r):t,e=i&&le?"D":"C",c(Math.abs(h-e),l(o,r));o=u>t?"D":"C";var d=Math.abs(u-t);return c(function(e,t){return t.cols-e}(u>t?e:h,i)+(d-1)*i.cols+1+((u>t?h:e)-1),l(o,r))}},function(e,t,i){"use strict";var r=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},n=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.SoundService=void 0;var s=i(1),o=function(){function e(e){this._optionsService=e}return Object.defineProperty(e,"audioContext",{get:function(){if(!e._audioContext){var t=window.AudioContext||window.webkitAudioContext;if(!t)return console.warn("Web Audio API is not supported by this browser. Consider upgrading to the latest version"),null;e._audioContext=new t}return e._audioContext},enumerable:!1,configurable:!0}),e.prototype.playBellSound=function(){var t=e.audioContext;if(t){var i=t.createBufferSource();t.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.options.bellSound)),(function(e){i.buffer=e,i.connect(t.destination),i.start(0)}))}},e.prototype._base64ToArrayBuffer=function(e){for(var t=window.atob(e),i=t.length,r=new Uint8Array(i),n=0;n=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},o=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseZoneManager=void 0;var a=i(2),l=i(7),c=i(5),h=i(1),u=function(e){function t(t,i,r,n,s,o){var a=e.call(this)||this;return a._element=t,a._screenElement=i,a._bufferService=r,a._mouseService=n,a._selectionService=s,a._optionsService=o,a._zones=[],a._areZonesActive=!1,a._lastHoverCoords=[void 0,void 0],a._initialSelectionLength=0,a.register(l.addDisposableDomListener(a._element,"mousedown",(function(e){return a._onMouseDown(e)}))),a._mouseMoveListener=function(e){return a._onMouseMove(e)},a._mouseLeaveListener=function(e){return a._onMouseLeave(e)},a._clickListener=function(e){return a._onClick(e)},a}return n(t,e),t.prototype.dispose=function(){e.prototype.dispose.call(this),this._deactivate()},t.prototype.add=function(e){this._zones.push(e),1===this._zones.length&&this._activate()},t.prototype.clearAll=function(e,t){if(0!==this._zones.length){e&&t||(e=0,t=this._bufferService.rows-1);for(var i=0;ie&&r.y1<=t+1||r.y2>e&&r.y2<=t+1||r.y1t+1)&&(this._currentZone&&this._currentZone===r&&(this._currentZone.leaveCallback(),this._currentZone=void 0),this._zones.splice(i--,1))}0===this._zones.length&&this._deactivate()}},t.prototype._activate=function(){this._areZonesActive||(this._areZonesActive=!0,this._element.addEventListener("mousemove",this._mouseMoveListener),this._element.addEventListener("mouseleave",this._mouseLeaveListener),this._element.addEventListener("click",this._clickListener))},t.prototype._deactivate=function(){this._areZonesActive&&(this._areZonesActive=!1,this._element.removeEventListener("mousemove",this._mouseMoveListener),this._element.removeEventListener("mouseleave",this._mouseLeaveListener),this._element.removeEventListener("click",this._clickListener))},t.prototype._onMouseMove=function(e){this._lastHoverCoords[0]===e.pageX&&this._lastHoverCoords[1]===e.pageY||(this._onHover(e),this._lastHoverCoords=[e.pageX,e.pageY])},t.prototype._onHover=function(e){var t=this,i=this._findZoneEventAt(e);i!==this._currentZone&&(this._currentZone&&(this._currentZone.leaveCallback(),this._currentZone=void 0,this._tooltipTimeout&&clearTimeout(this._tooltipTimeout)),i&&(this._currentZone=i,i.hoverCallback&&i.hoverCallback(e),this._tooltipTimeout=window.setTimeout((function(){return t._onTooltip(e)}),this._optionsService.options.linkTooltipHoverDuration)))},t.prototype._onTooltip=function(e){this._tooltipTimeout=void 0;var t=this._findZoneEventAt(e);t&&t.tooltipCallback&&t.tooltipCallback(e)},t.prototype._onMouseDown=function(e){if(this._initialSelectionLength=this._getSelectionLength(),this._areZonesActive){var t=this._findZoneEventAt(e);(null==t?void 0:t.willLinkActivate(e))&&(e.preventDefault(),e.stopImmediatePropagation())}},t.prototype._onMouseLeave=function(e){this._currentZone&&(this._currentZone.leaveCallback(),this._currentZone=void 0,this._tooltipTimeout&&clearTimeout(this._tooltipTimeout))},t.prototype._onClick=function(e){var t=this._findZoneEventAt(e),i=this._getSelectionLength();t&&i===this._initialSelectionLength&&(t.clickCallback(e),e.preventDefault(),e.stopImmediatePropagation())},t.prototype._getSelectionLength=function(){var e=this._selectionService.selectionText;return e?e.length:0},t.prototype._findZoneEventAt=function(e){var t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows);if(t)for(var i=t[0],r=t[1],n=0;n=s.x1&&i=s.x1||r===s.y2&&is.y1&&re;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()},t.prototype._createAccessibilityTreeNode=function(){var e=document.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e},t.prototype._onTab=function(e){for(var t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,"\n"===e&&(this._liveRegionLineCount++,21===this._liveRegionLineCount&&(this._liveRegion.textContent+=s.tooMuchOutput)),o.isMac&&this._liveRegion.textContent&&this._liveRegion.textContent.length>0&&!this._liveRegion.parentNode&&setTimeout((function(){t._accessibilityTreeRoot.appendChild(t._liveRegion)}),0))},t.prototype._clearLiveRegion=function(){this._liveRegion.textContent="",this._liveRegionLineCount=0,o.isMac&&u.removeElementFromParent(this._liveRegion)},t.prototype._onKey=function(e){this._clearLiveRegion(),this._charsToConsume.push(e)},t.prototype._refreshRows=function(e,t){this._renderRowsDebouncer.refresh(e,t,this._terminal.rows)},t.prototype._renderRows=function(e,t){for(var i=this._terminal.buffer,r=i.lines.length.toString(),n=e;n<=t;n++){var s=i.translateBufferLineToString(i.ydisp+n,!0),o=(i.ydisp+n+1).toString(),a=this._rowElements[n];a&&(0===s.length?a.innerHTML=" ":a.textContent=s,a.setAttribute("aria-posinset",o),a.setAttribute("aria-setsize",r))}this._announceCharacters()},t.prototype._refreshRowsDimensions=function(){if(this._renderService.dimensions.actualCellHeight){this._rowElements.length!==this._terminal.rows&&this._onResize(this._terminal.rows);for(var e=0;e=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},o=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRenderer=void 0;var a=i(58),l=i(9),c=i(2),h=i(5),u=i(1),d=i(0),f=i(10),p=i(17),_=1,m=function(e){function t(t,i,r,n,s,o,l,c,h){var u=e.call(this)||this;return u._colors=t,u._element=i,u._screenElement=r,u._viewportElement=n,u._linkifier=s,u._linkifier2=o,u._charSizeService=l,u._optionsService=c,u._bufferService=h,u._terminalClass=_++,u._rowElements=[],u._rowContainer=document.createElement("div"),u._rowContainer.classList.add("xterm-rows"),u._rowContainer.style.lineHeight="normal",u._rowContainer.setAttribute("aria-hidden","true"),u._refreshRowElements(u._bufferService.cols,u._bufferService.rows),u._selectionContainer=document.createElement("div"),u._selectionContainer.classList.add("xterm-selection"),u._selectionContainer.setAttribute("aria-hidden","true"),u.dimensions={scaledCharWidth:0,scaledCharHeight:0,scaledCellWidth:0,scaledCellHeight:0,scaledCharLeft:0,scaledCharTop:0,scaledCanvasWidth:0,scaledCanvasHeight:0,canvasWidth:0,canvasHeight:0,actualCellWidth:0,actualCellHeight:0},u._updateDimensions(),u._injectCss(),u._rowFactory=new a.DomRendererRowFactory(document,u._optionsService,u._colors),u._element.classList.add("xterm-dom-renderer-owner-"+u._terminalClass),u._screenElement.appendChild(u._rowContainer),u._screenElement.appendChild(u._selectionContainer),u._linkifier.onShowLinkUnderline((function(e){return u._onLinkHover(e)})),u._linkifier.onHideLinkUnderline((function(e){return u._onLinkLeave(e)})),u._linkifier2.onShowLinkUnderline((function(e){return u._onLinkHover(e)})),u._linkifier2.onHideLinkUnderline((function(e){return u._onLinkLeave(e)})),u}return n(t,e),Object.defineProperty(t.prototype,"onRequestRedraw",{get:function(){return(new d.EventEmitter).event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){this._element.classList.remove("xterm-dom-renderer-owner-"+this._terminalClass),p.removeElementFromParent(this._rowContainer,this._selectionContainer,this._themeStyleElement,this._dimensionsStyleElement),e.prototype.dispose.call(this)},t.prototype._updateDimensions=function(){this.dimensions.scaledCharWidth=this._charSizeService.width*window.devicePixelRatio,this.dimensions.scaledCharHeight=Math.ceil(this._charSizeService.height*window.devicePixelRatio),this.dimensions.scaledCellWidth=this.dimensions.scaledCharWidth+Math.round(this._optionsService.options.letterSpacing),this.dimensions.scaledCellHeight=Math.floor(this.dimensions.scaledCharHeight*this._optionsService.options.lineHeight),this.dimensions.scaledCharLeft=0,this.dimensions.scaledCharTop=0,this.dimensions.scaledCanvasWidth=this.dimensions.scaledCellWidth*this._bufferService.cols,this.dimensions.scaledCanvasHeight=this.dimensions.scaledCellHeight*this._bufferService.rows,this.dimensions.canvasWidth=Math.round(this.dimensions.scaledCanvasWidth/window.devicePixelRatio),this.dimensions.canvasHeight=Math.round(this.dimensions.scaledCanvasHeight/window.devicePixelRatio),this.dimensions.actualCellWidth=this.dimensions.canvasWidth/this._bufferService.cols,this.dimensions.actualCellHeight=this.dimensions.canvasHeight/this._bufferService.rows;for(var e=0,t=this._rowElements;et;)this._rowContainer.removeChild(this._rowElements.pop())},t.prototype.onResize=function(e,t){this._refreshRowElements(e,t),this._updateDimensions()},t.prototype.onCharSizeChanged=function(){this._updateDimensions()},t.prototype.onBlur=function(){this._rowContainer.classList.remove("xterm-focus")},t.prototype.onFocus=function(){this._rowContainer.classList.add("xterm-focus")},t.prototype.onSelectionChanged=function(e,t,i){for(;this._selectionContainer.children.length;)this._selectionContainer.removeChild(this._selectionContainer.children[0]);if(e&&t){var r=e[1]-this._bufferService.buffer.ydisp,n=t[1]-this._bufferService.buffer.ydisp,s=Math.max(r,0),o=Math.min(n,this._bufferService.rows-1);if(!(s>=this._bufferService.rows||o<0)){var a=document.createDocumentFragment();i?a.appendChild(this._createSelectionElement(s,e[0],t[0],o-s+1)):(a.appendChild(this._createSelectionElement(s,r===s?e[0]:0,s===n?t[0]:this._bufferService.cols)),a.appendChild(this._createSelectionElement(s+1,0,this._bufferService.cols,o-s-1)),s!==o&&a.appendChild(this._createSelectionElement(o,0,n===o?t[0]:this._bufferService.cols))),this._selectionContainer.appendChild(a)}}},t.prototype._createSelectionElement=function(e,t,i,r){void 0===r&&(r=1);var n=document.createElement("div");return n.style.height=r*this.dimensions.actualCellHeight+"px",n.style.top=e*this.dimensions.actualCellHeight+"px",n.style.left=t*this.dimensions.actualCellWidth+"px",n.style.width=this.dimensions.actualCellWidth*(i-t)+"px",n},t.prototype.onCursorMove=function(){},t.prototype.onOptionsChanged=function(){this._updateDimensions(),this._injectCss()},t.prototype.clear=function(){for(var e=0,t=this._rowElements;e=n&&(e=0,i++)}},s([o(6,h.ICharSizeService),o(7,u.IOptionsService),o(8,u.IBufferService)],t)}(c.Disposable);t.DomRenderer=m},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.DomRendererRowFactory=t.CURSOR_STYLE_UNDERLINE_CLASS=t.CURSOR_STYLE_BAR_CLASS=t.CURSOR_STYLE_BLOCK_CLASS=t.CURSOR_BLINK_CLASS=t.CURSOR_CLASS=t.UNDERLINE_CLASS=t.ITALIC_CLASS=t.DIM_CLASS=t.BOLD_CLASS=void 0;var r=i(9),n=i(3),s=i(4),o=i(10);t.BOLD_CLASS="xterm-bold",t.DIM_CLASS="xterm-dim",t.ITALIC_CLASS="xterm-italic",t.UNDERLINE_CLASS="xterm-underline",t.CURSOR_CLASS="xterm-cursor",t.CURSOR_BLINK_CLASS="xterm-cursor-blink",t.CURSOR_STYLE_BLOCK_CLASS="xterm-cursor-block",t.CURSOR_STYLE_BAR_CLASS="xterm-cursor-bar",t.CURSOR_STYLE_UNDERLINE_CLASS="xterm-cursor-underline";var a=function(){function e(e,t,i){this._document=e,this._optionsService=t,this._colors=i,this._workCell=new s.CellData}return e.prototype.setColors=function(e){this._colors=e},e.prototype.createRow=function(e,i,s,a,c,h,u){for(var d=this._document.createDocumentFragment(),f=0,p=Math.min(e.length,u)-1;p>=0;p--)if(e.loadCell(p,this._workCell).getCode()!==n.NULL_CELL_CODE||i&&p===a){f=p+1;break}for(p=0;p1&&(m.style.width=h*_+"px"),i&&p===a)switch(m.classList.add(t.CURSOR_CLASS),c&&m.classList.add(t.CURSOR_BLINK_CLASS),s){case"bar":m.classList.add(t.CURSOR_STYLE_BAR_CLASS);break;case"underline":m.classList.add(t.CURSOR_STYLE_UNDERLINE_CLASS);break;default:m.classList.add(t.CURSOR_STYLE_BLOCK_CLASS)}this._workCell.isBold()&&m.classList.add(t.BOLD_CLASS),this._workCell.isItalic()&&m.classList.add(t.ITALIC_CLASS),this._workCell.isDim()&&m.classList.add(t.DIM_CLASS),this._workCell.isUnderline()&&m.classList.add(t.UNDERLINE_CLASS),m.textContent=this._workCell.isInvisible()?n.WHITESPACE_CELL_CHAR:this._workCell.getChars()||n.WHITESPACE_CELL_CHAR;var g=this._workCell.getFgColor(),v=this._workCell.getFgColorMode(),y=this._workCell.getBgColor(),b=this._workCell.getBgColorMode(),w=!!this._workCell.isInverse();if(w){var C=g;g=y,y=C;var S=v;v=b,b=S}switch(v){case 16777216:case 33554432:this._workCell.isBold()&&g<8&&this._optionsService.options.drawBoldTextInBrightColors&&(g+=8),this._applyMinimumContrast(m,this._colors.background,this._colors.ansi[g])||m.classList.add("xterm-fg-"+g);break;case 50331648:var k=o.rgba.toColor(g>>16&255,g>>8&255,255&g);this._applyMinimumContrast(m,this._colors.background,k)||this._addStyle(m,"color:#"+l(g.toString(16),"0",6));break;case 0:default:this._applyMinimumContrast(m,this._colors.background,this._colors.foreground)||w&&m.classList.add("xterm-fg-"+r.INVERTED_DEFAULT_COLOR)}switch(b){case 16777216:case 33554432:m.classList.add("xterm-bg-"+y);break;case 50331648:this._addStyle(m,"background-color:#"+l(y.toString(16),"0",6));break;case 0:default:w&&m.classList.add("xterm-bg-"+r.INVERTED_DEFAULT_COLOR)}d.appendChild(m)}}return d},e.prototype._applyMinimumContrast=function(e,t,i){if(1===this._optionsService.options.minimumContrastRatio)return!1;var r=this._colors.contrastCache.getColor(this._workCell.bg,this._workCell.fg);return void 0===r&&(r=o.color.ensureContrastRatio(t,i,this._optionsService.options.minimumContrastRatio),this._colors.contrastCache.setColor(this._workCell.bg,this._workCell.fg,null!=r?r:null)),!!r&&(this._addStyle(e,"color:"+r.css),!0)},e.prototype._addStyle=function(e,t){e.setAttribute("style",""+(e.getAttribute("style")||"")+t+";")},e}();function l(e,t,i){for(;e.length"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']};t.evaluateKeyboardEvent=function(e,t,i,s){var o={type:0,cancel:!1,key:void 0},a=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:"UIKeyInputUpArrow"===e.key?o.key=t?r.C0.ESC+"OA":r.C0.ESC+"[A":"UIKeyInputLeftArrow"===e.key?o.key=t?r.C0.ESC+"OD":r.C0.ESC+"[D":"UIKeyInputRightArrow"===e.key?o.key=t?r.C0.ESC+"OC":r.C0.ESC+"[C":"UIKeyInputDownArrow"===e.key&&(o.key=t?r.C0.ESC+"OB":r.C0.ESC+"[B");break;case 8:if(e.shiftKey){o.key=r.C0.BS;break}if(e.altKey){o.key=r.C0.ESC+r.C0.DEL;break}o.key=r.C0.DEL;break;case 9:if(e.shiftKey){o.key=r.C0.ESC+"[Z";break}o.key=r.C0.HT,o.cancel=!0;break;case 13:o.key=e.altKey?r.C0.ESC+r.C0.CR:r.C0.CR,o.cancel=!0;break;case 27:o.key=r.C0.ESC,e.altKey&&(o.key=r.C0.ESC+r.C0.ESC),o.cancel=!0;break;case 37:if(e.metaKey)break;a?(o.key=r.C0.ESC+"[1;"+(a+1)+"D",o.key===r.C0.ESC+"[1;3D"&&(o.key=r.C0.ESC+(i?"b":"[1;5D"))):o.key=t?r.C0.ESC+"OD":r.C0.ESC+"[D";break;case 39:if(e.metaKey)break;a?(o.key=r.C0.ESC+"[1;"+(a+1)+"C",o.key===r.C0.ESC+"[1;3C"&&(o.key=r.C0.ESC+(i?"f":"[1;5C"))):o.key=t?r.C0.ESC+"OC":r.C0.ESC+"[C";break;case 38:if(e.metaKey)break;a?(o.key=r.C0.ESC+"[1;"+(a+1)+"A",i||o.key!==r.C0.ESC+"[1;3A"||(o.key=r.C0.ESC+"[1;5A")):o.key=t?r.C0.ESC+"OA":r.C0.ESC+"[A";break;case 40:if(e.metaKey)break;a?(o.key=r.C0.ESC+"[1;"+(a+1)+"B",i||o.key!==r.C0.ESC+"[1;3B"||(o.key=r.C0.ESC+"[1;5B")):o.key=t?r.C0.ESC+"OB":r.C0.ESC+"[B";break;case 45:e.shiftKey||e.ctrlKey||(o.key=r.C0.ESC+"[2~");break;case 46:o.key=a?r.C0.ESC+"[3;"+(a+1)+"~":r.C0.ESC+"[3~";break;case 36:o.key=a?r.C0.ESC+"[1;"+(a+1)+"H":t?r.C0.ESC+"OH":r.C0.ESC+"[H";break;case 35:o.key=a?r.C0.ESC+"[1;"+(a+1)+"F":t?r.C0.ESC+"OF":r.C0.ESC+"[F";break;case 33:e.shiftKey?o.type=2:o.key=r.C0.ESC+"[5~";break;case 34:e.shiftKey?o.type=3:o.key=r.C0.ESC+"[6~";break;case 112:o.key=a?r.C0.ESC+"[1;"+(a+1)+"P":r.C0.ESC+"OP";break;case 113:o.key=a?r.C0.ESC+"[1;"+(a+1)+"Q":r.C0.ESC+"OQ";break;case 114:o.key=a?r.C0.ESC+"[1;"+(a+1)+"R":r.C0.ESC+"OR";break;case 115:o.key=a?r.C0.ESC+"[1;"+(a+1)+"S":r.C0.ESC+"OS";break;case 116:o.key=a?r.C0.ESC+"[15;"+(a+1)+"~":r.C0.ESC+"[15~";break;case 117:o.key=a?r.C0.ESC+"[17;"+(a+1)+"~":r.C0.ESC+"[17~";break;case 118:o.key=a?r.C0.ESC+"[18;"+(a+1)+"~":r.C0.ESC+"[18~";break;case 119:o.key=a?r.C0.ESC+"[19;"+(a+1)+"~":r.C0.ESC+"[19~";break;case 120:o.key=a?r.C0.ESC+"[20;"+(a+1)+"~":r.C0.ESC+"[20~";break;case 121:o.key=a?r.C0.ESC+"[21;"+(a+1)+"~":r.C0.ESC+"[21~";break;case 122:o.key=a?r.C0.ESC+"[23;"+(a+1)+"~":r.C0.ESC+"[23~";break;case 123:o.key=a?r.C0.ESC+"[24;"+(a+1)+"~":r.C0.ESC+"[24~";break;default:if(!e.ctrlKey||e.shiftKey||e.altKey||e.metaKey)if(i&&!s||!e.altKey||e.metaKey)i&&!e.altKey&&!e.ctrlKey&&e.metaKey?65===e.keyCode&&(o.type=1):e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&1===e.key.length?o.key=e.key:e.key&&e.ctrlKey&&"_"===e.key&&(o.key=r.C0.US);else{var l=n[e.keyCode],c=l&&l[e.shiftKey?1:0];c?o.key=r.C0.ESC+c:e.keyCode>=65&&e.keyCode<=90&&(o.key=r.C0.ESC+String.fromCharCode(e.ctrlKey?e.keyCode-64:e.keyCode+32))}else e.keyCode>=65&&e.keyCode<=90?o.key=String.fromCharCode(e.keyCode-64):32===e.keyCode?o.key=r.C0.NUL:e.keyCode>=51&&e.keyCode<=55?o.key=String.fromCharCode(e.keyCode-51+27):56===e.keyCode?o.key=r.C0.DEL:219===e.keyCode?o.key=r.C0.ESC:220===e.keyCode?o.key=r.C0.FS:221===e.keyCode&&(o.key=r.C0.GS)}return o}},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),s=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},o=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.RenderService=void 0;var a=i(31),l=i(0),c=i(2),h=i(32),u=i(7),d=i(1),f=i(5),p=function(e){function t(t,i,r,n,s,o){var c=e.call(this)||this;if(c._renderer=t,c._rowCount=i,c._isPaused=!1,c._needsFullRefresh=!1,c._isNextRenderRedrawOnly=!0,c._needsSelectionRefresh=!1,c._canvasWidth=0,c._canvasHeight=0,c._selectionState={start:void 0,end:void 0,columnSelectMode:!1},c._onDimensionsChange=new l.EventEmitter,c._onRender=new l.EventEmitter,c._onRefreshRequest=new l.EventEmitter,c.register({dispose:function(){return c._renderer.dispose()}}),c._renderDebouncer=new a.RenderDebouncer((function(e,t){return c._renderRows(e,t)})),c.register(c._renderDebouncer),c._screenDprMonitor=new h.ScreenDprMonitor,c._screenDprMonitor.setListener((function(){return c.onDevicePixelRatioChange()})),c.register(c._screenDprMonitor),c.register(o.onResize((function(e){return c._fullRefresh()}))),c.register(n.onOptionChange((function(){return c._renderer.onOptionsChanged()}))),c.register(s.onCharSizeChange((function(){return c.onCharSizeChanged()}))),c._renderer.onRequestRedraw((function(e){return c.refreshRows(e.start,e.end,!0)})),c.register(u.addDisposableDomListener(window,"resize",(function(){return c.onDevicePixelRatioChange()}))),"IntersectionObserver"in window){var d=new IntersectionObserver((function(e){return c._onIntersectionChange(e[e.length-1])}),{threshold:0});d.observe(r),c.register({dispose:function(){return d.disconnect()}})}return c}return n(t,e),Object.defineProperty(t.prototype,"onDimensionsChange",{get:function(){return this._onDimensionsChange.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRenderedBufferChange",{get:function(){return this._onRender.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRefreshRequest",{get:function(){return this._onRefreshRequest.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"dimensions",{get:function(){return this._renderer.dimensions},enumerable:!1,configurable:!0}),t.prototype._onIntersectionChange=function(e){this._isPaused=void 0===e.isIntersecting?0===e.intersectionRatio:!e.isIntersecting,!this._isPaused&&this._needsFullRefresh&&(this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)},t.prototype.refreshRows=function(e,t,i){void 0===i&&(i=!1),this._isPaused?this._needsFullRefresh=!0:(i||(this._isNextRenderRedrawOnly=!1),this._renderDebouncer.refresh(e,t,this._rowCount))},t.prototype._renderRows=function(e,t){this._renderer.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.onSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0},t.prototype.resize=function(e,t){this._rowCount=t,this._fireOnCanvasResize()},t.prototype.changeOptions=function(){this._renderer.onOptionsChanged(),this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize()},t.prototype._fireOnCanvasResize=function(){this._renderer.dimensions.canvasWidth===this._canvasWidth&&this._renderer.dimensions.canvasHeight===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.dimensions)},t.prototype.dispose=function(){e.prototype.dispose.call(this)},t.prototype.setRenderer=function(e){var t=this;this._renderer.dispose(),this._renderer=e,this._renderer.onRequestRedraw((function(e){return t.refreshRows(e.start,e.end,!0)})),this._needsSelectionRefresh=!0,this._fullRefresh()},t.prototype._fullRefresh=function(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)},t.prototype.setColors=function(e){this._renderer.setColors(e),this._fullRefresh()},t.prototype.onDevicePixelRatioChange=function(){this._renderer.onDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1)},t.prototype.onResize=function(e,t){this._renderer.onResize(e,t),this._fullRefresh()},t.prototype.onCharSizeChanged=function(){this._renderer.onCharSizeChanged()},t.prototype.onBlur=function(){this._renderer.onBlur()},t.prototype.onFocus=function(){this._renderer.onFocus()},t.prototype.onSelectionChanged=function(e,t,i){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=i,this._renderer.onSelectionChanged(e,t,i)},t.prototype.onCursorMove=function(){this._renderer.onCursorMove()},t.prototype.clear=function(){this._renderer.clear()},t.prototype.registerCharacterJoiner=function(e){return this._renderer.registerCharacterJoiner(e)},t.prototype.deregisterCharacterJoiner=function(e){return this._renderer.deregisterCharacterJoiner(e)},s([o(3,d.IOptionsService),o(4,f.ICharSizeService),o(5,d.IBufferService)],t)}(c.Disposable);t.RenderService=p},function(e,t,i){"use strict";var r=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},n=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharSizeService=void 0;var s=i(1),o=i(0),a=function(){function e(e,t,i){this._optionsService=i,this.width=0,this.height=0,this._onCharSizeChange=new o.EventEmitter,this._measureStrategy=new l(e,t,this._optionsService)}return Object.defineProperty(e.prototype,"hasValidSize",{get:function(){return this.width>0&&this.height>0},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onCharSizeChange",{get:function(){return this._onCharSizeChange.event},enumerable:!1,configurable:!0}),e.prototype.measure=function(){var e=this._measureStrategy.measure();e.width===this.width&&e.height===this.height||(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())},r([n(2,s.IOptionsService)],e)}();t.CharSizeService=a;var l=function(){function e(e,t,i){this._document=e,this._parentElement=t,this._optionsService=i,this._result={width:0,height:0},this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W",this._measureElement.setAttribute("aria-hidden","true"),this._parentElement.appendChild(this._measureElement)}return e.prototype.measure=function(){this._measureElement.style.fontFamily=this._optionsService.options.fontFamily,this._measureElement.style.fontSize=this._optionsService.options.fontSize+"px";var e=this._measureElement.getBoundingClientRect();return 0!==e.width&&0!==e.height&&(this._result.width=e.width,this._result.height=Math.ceil(e.height)),this._result},e}()},function(e,t,i){"use strict";var r=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},n=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseService=void 0;var s=i(5),o=i(30),a=function(){function e(e,t){this._renderService=e,this._charSizeService=t}return e.prototype.getCoords=function(e,t,i,r,n){return o.getCoords(e,t,i,r,this._charSizeService.hasValidSize,this._renderService.dimensions.actualCellWidth,this._renderService.dimensions.actualCellHeight,n)},e.prototype.getRawByteCoords=function(e,t,i,r){var n=this.getCoords(e,t,i,r);return o.getRawByteCoords(n)},r([n(0,s.IRenderService),n(1,s.ICharSizeService)],e)}();t.MouseService=a},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),s=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},o=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Linkifier2=void 0;var a=i(1),l=i(0),c=i(2),h=i(7),u=function(e){function t(t){var i=e.call(this)||this;return i._bufferService=t,i._linkProviders=[],i._linkCacheDisposables=[],i._isMouseOut=!0,i._activeLine=-1,i._onShowLinkUnderline=i.register(new l.EventEmitter),i._onHideLinkUnderline=i.register(new l.EventEmitter),i.register(c.getDisposeArrayDisposable(i._linkCacheDisposables)),i}return n(t,e),Object.defineProperty(t.prototype,"onShowLinkUnderline",{get:function(){return this._onShowLinkUnderline.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onHideLinkUnderline",{get:function(){return this._onHideLinkUnderline.event},enumerable:!1,configurable:!0}),t.prototype.registerLinkProvider=function(e){var t=this;return this._linkProviders.push(e),{dispose:function(){var i=t._linkProviders.indexOf(e);-1!==i&&t._linkProviders.splice(i,1)}}},t.prototype.attachToDom=function(e,t,i){var r=this;this._element=e,this._mouseService=t,this._renderService=i,this.register(h.addDisposableDomListener(this._element,"mouseleave",(function(){r._isMouseOut=!0,r._clearCurrentLink()}))),this.register(h.addDisposableDomListener(this._element,"mousemove",this._onMouseMove.bind(this))),this.register(h.addDisposableDomListener(this._element,"click",this._onClick.bind(this)))},t.prototype._onMouseMove=function(e){if(this._lastMouseEvent=e,this._element&&this._mouseService){var t=this._positionFromMouseEvent(e,this._element,this._mouseService);if(t){this._isMouseOut=!1;for(var i=e.composedPath(),r=0;re?this._bufferService.cols:o.link.range.end.x,l=o.link.range.start.y=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,c.disposeArray(this._linkCacheDisposables))},t.prototype._handleNewLink=function(e){var t=this;if(this._element&&this._lastMouseEvent&&this._mouseService){var i=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);i&&this._linkAtPosition(e.link,i)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:void 0===e.link.decorations||e.link.decorations.underline,pointerCursor:void 0===e.link.decorations||e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:function(){var e,i;return null===(i=null===(e=t._currentLink)||void 0===e?void 0:e.state)||void 0===i?void 0:i.decorations.pointerCursor},set:function(e){var i,r;(null===(i=t._currentLink)||void 0===i?void 0:i.state)&&t._currentLink.state.decorations.pointerCursor!==e&&(t._currentLink.state.decorations.pointerCursor=e,t._currentLink.state.isHovered&&(null===(r=t._element)||void 0===r||r.classList.toggle("xterm-cursor-pointer",e)))}},underline:{get:function(){var e,i;return null===(i=null===(e=t._currentLink)||void 0===e?void 0:e.state)||void 0===i?void 0:i.decorations.underline},set:function(i){var r,n,s;(null===(r=t._currentLink)||void 0===r?void 0:r.state)&&(null===(s=null===(n=t._currentLink)||void 0===n?void 0:n.state)||void 0===s?void 0:s.decorations.underline)!==i&&(t._currentLink.state.decorations.underline=i,t._currentLink.state.isHovered&&t._fireUnderlineEvent(e.link,i))}}}),this._renderService&&this._linkCacheDisposables.push(this._renderService.onRenderedBufferChange((function(e){t._clearCurrentLink(0===e.start?0:e.start+1+t._bufferService.buffer.ydisp,e.end+1+t._bufferService.buffer.ydisp)}))))}},t.prototype._linkHover=function(e,t,i){var r;(null===(r=this._currentLink)||void 0===r?void 0:r.state)&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(i,t.text)},t.prototype._fireUnderlineEvent=function(e,t){var i=e.range,r=this._bufferService.buffer.ydisp,n=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-r-1,i.end.x,i.end.y-r-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(n)},t.prototype._linkLeave=function(e,t,i){var r;(null===(r=this._currentLink)||void 0===r?void 0:r.state)&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(i,t.text)},t.prototype._linkAtPosition=function(e,t){var i=e.range.start.yt.y;return(e.range.start.y===e.range.end.y&&e.range.start.x<=t.x&&e.range.end.x>=t.x||i&&e.range.end.x>=t.x||r&&e.range.start.x<=t.x||i&&r)&&e.range.start.y<=t.y&&e.range.end.y>=t.y},t.prototype._positionFromMouseEvent=function(e,t,i){var r=i.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(r)return{x:r[0],y:r[1]+this._bufferService.buffer.ydisp}},t.prototype._createLinkUnderlineEvent=function(e,t,i,r,n){return{x1:e,y1:t,x2:i,y2:r,cols:this._bufferService.cols,fg:n}},s([o(0,a.IBufferService)],t)}(c.Disposable);t.Linkifier2=u},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CoreBrowserService=void 0;var r=function(){function e(e){this._textarea=e}return Object.defineProperty(e.prototype,"isFocused",{get:function(){return document.activeElement===this._textarea&&document.hasFocus()},enumerable:!1,configurable:!0}),e}();t.CoreBrowserService=r},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)});Object.defineProperty(t,"__esModule",{value:!0}),t.CoreTerminal=void 0;var s=i(2),o=i(1),a=i(66),l=i(67),c=i(68),h=i(74),u=i(75),d=i(0),f=i(76),p=i(77),_=i(78),m=i(80),g=i(81),v=i(19),y=i(82),b=function(e){function t(t){var i=e.call(this)||this;return i._onBinary=new d.EventEmitter,i._onData=new d.EventEmitter,i._onLineFeed=new d.EventEmitter,i._onResize=new d.EventEmitter,i._onScroll=new d.EventEmitter,i._instantiationService=new a.InstantiationService,i.optionsService=new h.OptionsService(t),i._instantiationService.setService(o.IOptionsService,i.optionsService),i._bufferService=i.register(i._instantiationService.createInstance(c.BufferService)),i._instantiationService.setService(o.IBufferService,i._bufferService),i._logService=i._instantiationService.createInstance(l.LogService),i._instantiationService.setService(o.ILogService,i._logService),i._coreService=i.register(i._instantiationService.createInstance(u.CoreService,(function(){return i.scrollToBottom()}))),i._instantiationService.setService(o.ICoreService,i._coreService),i._coreMouseService=i._instantiationService.createInstance(f.CoreMouseService),i._instantiationService.setService(o.ICoreMouseService,i._coreMouseService),i._dirtyRowService=i._instantiationService.createInstance(p.DirtyRowService),i._instantiationService.setService(o.IDirtyRowService,i._dirtyRowService),i.unicodeService=i._instantiationService.createInstance(_.UnicodeService),i._instantiationService.setService(o.IUnicodeService,i.unicodeService),i._charsetService=i._instantiationService.createInstance(m.CharsetService),i._instantiationService.setService(o.ICharsetService,i._charsetService),i._inputHandler=new v.InputHandler(i._bufferService,i._charsetService,i._coreService,i._dirtyRowService,i._logService,i.optionsService,i._coreMouseService,i.unicodeService),i.register(d.forwardEvent(i._inputHandler.onLineFeed,i._onLineFeed)),i.register(i._inputHandler),i.register(d.forwardEvent(i._bufferService.onResize,i._onResize)),i.register(d.forwardEvent(i._coreService.onData,i._onData)),i.register(d.forwardEvent(i._coreService.onBinary,i._onBinary)),i.register(i.optionsService.onOptionChange((function(e){return i._updateOptions(e)}))),i._writeBuffer=new y.WriteBuffer((function(e){return i._inputHandler.parse(e)})),i}return n(t,e),Object.defineProperty(t.prototype,"onBinary",{get:function(){return this._onBinary.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onData",{get:function(){return this._onData.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onLineFeed",{get:function(){return this._onLineFeed.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onResize",{get:function(){return this._onResize.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onScroll",{get:function(){return this._onScroll.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"cols",{get:function(){return this._bufferService.cols},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"rows",{get:function(){return this._bufferService.rows},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"buffers",{get:function(){return this._bufferService.buffers},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){var t;this._isDisposed||(e.prototype.dispose.call(this),null===(t=this._windowsMode)||void 0===t||t.dispose(),this._windowsMode=void 0)},t.prototype.write=function(e,t){this._writeBuffer.write(e,t)},t.prototype.writeSync=function(e){this._writeBuffer.writeSync(e)},t.prototype.resize=function(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,c.MINIMUM_COLS),t=Math.max(t,c.MINIMUM_ROWS),this._bufferService.resize(e,t))},t.prototype.scroll=function(e,t){void 0===t&&(t=!1);var i,r=this._bufferService.buffer;(i=this._cachedBlankLine)&&i.length===this.cols&&i.getFg(0)===e.fg&&i.getBg(0)===e.bg||(i=r.getBlankLine(e,t),this._cachedBlankLine=i),i.isWrapped=t;var n=r.ybase+r.scrollTop,s=r.ybase+r.scrollBottom;if(0===r.scrollTop){var o=r.lines.isFull;s===r.lines.length-1?o?r.lines.recycle().copyFrom(i):r.lines.push(i.clone()):r.lines.splice(s+1,0,i.clone()),o?this._bufferService.isUserScrolling&&(r.ydisp=Math.max(r.ydisp-1,0)):(r.ybase++,this._bufferService.isUserScrolling||r.ydisp++)}else r.lines.shiftElements(n+1,s-n+1-1,-1),r.lines.set(s,i.clone());this._bufferService.isUserScrolling||(r.ydisp=r.ybase),this._dirtyRowService.markRangeDirty(r.scrollTop,r.scrollBottom),this._onScroll.fire(r.ydisp)},t.prototype.scrollLines=function(e,t){var i=this._bufferService.buffer;if(e<0){if(0===i.ydisp)return;this._bufferService.isUserScrolling=!0}else e+i.ydisp>=i.ybase&&(this._bufferService.isUserScrolling=!1);var r=i.ydisp;i.ydisp=Math.max(Math.min(i.ydisp+e,i.ybase),0),r!==i.ydisp&&(t||this._onScroll.fire(i.ydisp))},t.prototype.scrollPages=function(e){this.scrollLines(e*(this.rows-1))},t.prototype.scrollToTop=function(){this.scrollLines(-this._bufferService.buffer.ydisp)},t.prototype.scrollToBottom=function(){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)},t.prototype.scrollToLine=function(e){var t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)},t.prototype.addEscHandler=function(e,t){return this._inputHandler.addEscHandler(e,t)},t.prototype.addDcsHandler=function(e,t){return this._inputHandler.addDcsHandler(e,t)},t.prototype.addCsiHandler=function(e,t){return this._inputHandler.addCsiHandler(e,t)},t.prototype.addOscHandler=function(e,t){return this._inputHandler.addOscHandler(e,t)},t.prototype._setup=function(){this.optionsService.options.windowsMode&&this._enableWindowsMode()},t.prototype.reset=function(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this._coreService.reset(),this._coreMouseService.reset()},t.prototype._updateOptions=function(e){var t;switch(e){case"scrollback":this.buffers.resize(this.cols,this.rows);break;case"windowsMode":this.optionsService.options.windowsMode?this._enableWindowsMode():(null===(t=this._windowsMode)||void 0===t||t.dispose(),this._windowsMode=void 0)}},t.prototype._enableWindowsMode=function(){var e=this;if(!this._windowsMode){var t=[];t.push(this.onLineFeed(g.updateWindowsModeWrappedState.bind(null,this._bufferService))),t.push(this.addCsiHandler({final:"H"},(function(){return g.updateWindowsModeWrappedState(e._bufferService),!1}))),this._windowsMode={dispose:function(){for(var e=0,i=t;e0?n[0].index:t.length;if(t.length!==u)throw new Error("[createInstance] First service dependency of "+e.name+" at position "+(u+1)+" conflicts with "+t.length+" static arguments");return new(e.bind.apply(e,r([void 0],r(t,o))))},e}();t.InstantiationService=a},function(e,t,i){"use strict";var r=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},n=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}},s=this&&this.__spreadArrays||function(){for(var e=0,t=0,i=arguments.length;t=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},o=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferService=t.MINIMUM_ROWS=t.MINIMUM_COLS=void 0;var a=i(1),l=i(69),c=i(0),h=i(2);t.MINIMUM_COLS=2,t.MINIMUM_ROWS=1;var u=function(e){function i(i){var r=e.call(this)||this;return r._optionsService=i,r.isUserScrolling=!1,r._onResize=new c.EventEmitter,r.cols=Math.max(i.options.cols,t.MINIMUM_COLS),r.rows=Math.max(i.options.rows,t.MINIMUM_ROWS),r.buffers=new l.BufferSet(i,r),r}return n(i,e),Object.defineProperty(i.prototype,"onResize",{get:function(){return this._onResize.event},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"buffer",{get:function(){return this.buffers.active},enumerable:!1,configurable:!0}),i.prototype.dispose=function(){e.prototype.dispose.call(this),this.buffers.dispose()},i.prototype.resize=function(e,t){this.cols=e,this.rows=t,this.buffers.resize(e,t),this.buffers.setupTabStops(this.cols),this._onResize.fire({cols:e,rows:t})},i.prototype.reset=function(){this.buffers.dispose(),this.buffers=new l.BufferSet(this._optionsService,this),this.isUserScrolling=!1},s([o(0,a.IOptionsService)],i)}(h.Disposable);t.BufferService=u},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)});Object.defineProperty(t,"__esModule",{value:!0}),t.BufferSet=void 0;var s=i(70),o=i(0),a=function(e){function t(t,i){var r=e.call(this)||this;return r._onBufferActivate=r.register(new o.EventEmitter),r._normal=new s.Buffer(!0,t,i),r._normal.fillViewportRows(),r._alt=new s.Buffer(!1,t,i),r._activeBuffer=r._normal,r.setupTabStops(),r}return n(t,e),Object.defineProperty(t.prototype,"onBufferActivate",{get:function(){return this._onBufferActivate.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"alt",{get:function(){return this._alt},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"active",{get:function(){return this._activeBuffer},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"normal",{get:function(){return this._normal},enumerable:!1,configurable:!0}),t.prototype.activateNormalBuffer=function(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))},t.prototype.activateAltBuffer=function(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))},t.prototype.resize=function(e,t){this._normal.resize(e,t),this._alt.resize(e,t)},t.prototype.setupTabStops=function(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)},t}(i(2).Disposable);t.BufferSet=a},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BufferStringIterator=t.Buffer=t.MAX_BUFFER_SIZE=void 0;var r=i(71),n=i(16),s=i(4),o=i(3),a=i(72),l=i(73),c=i(20),h=i(6);t.MAX_BUFFER_SIZE=4294967295;var u=function(){function e(e,t,i){this._hasScrollback=e,this._optionsService=t,this._bufferService=i,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.savedY=0,this.savedX=0,this.savedCurAttrData=n.DEFAULT_ATTR_DATA.clone(),this.savedCharset=c.DEFAULT_CHARSET,this.markers=[],this._nullCell=s.CellData.fromCharData([0,o.NULL_CELL_CHAR,o.NULL_CELL_WIDTH,o.NULL_CELL_CODE]),this._whitespaceCell=s.CellData.fromCharData([0,o.WHITESPACE_CELL_CHAR,o.WHITESPACE_CELL_WIDTH,o.WHITESPACE_CELL_CODE]),this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new r.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}return e.prototype.getNullCell=function(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new h.ExtendedAttrs),this._nullCell},e.prototype.getWhitespaceCell=function(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new h.ExtendedAttrs),this._whitespaceCell},e.prototype.getBlankLine=function(e,t){return new n.BufferLine(this._bufferService.cols,this.getNullCell(e),t)},Object.defineProperty(e.prototype,"hasScrollback",{get:function(){return this._hasScrollback&&this.lines.maxLength>this._rows},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"isCursorInViewport",{get:function(){var e=this.ybase+this.y-this.ydisp;return e>=0&&et.MAX_BUFFER_SIZE?t.MAX_BUFFER_SIZE:i},e.prototype.fillViewportRows=function(e){if(0===this.lines.length){void 0===e&&(e=n.DEFAULT_ATTR_DATA);for(var t=this._rows;t--;)this.lines.push(this.getBlankLine(e))}},e.prototype.clear=function(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new r.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()},e.prototype.resize=function(e,t){var i=this.getNullCell(n.DEFAULT_ATTR_DATA),r=this._getCorrectBufferLength(t);if(r>this.lines.maxLength&&(this.lines.maxLength=r),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+o+1?(this.ybase--,o++,this.ydisp>0&&this.ydisp--):this.lines.push(new n.BufferLine(e,i)));else for(a=this._rows;a>t;a--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(r0&&(this.lines.trimStart(l),this.ybase=Math.max(this.ybase-l,0),this.ydisp=Math.max(this.ydisp-l,0),this.savedY=Math.max(this.savedY-l,0)),this.lines.maxLength=r}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),o&&(this.y+=o),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(s=0;sthis._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))},e.prototype._reflowLarger=function(e,t){var i=a.reflowLargerGetLinesToRemove(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(n.DEFAULT_ATTR_DATA));if(i.length>0){var r=a.reflowLargerCreateNewLayout(this.lines,i);a.reflowLargerApplyNewLayout(this.lines,r.layout),this._reflowLargerAdjustViewport(e,t,r.countRemoved)}},e.prototype._reflowLargerAdjustViewport=function(e,t,i){for(var r=this.getNullCell(n.DEFAULT_ATTR_DATA),s=i;s-- >0;)0===this.ybase?(this.y>0&&this.y--,this.lines.length=0;o--){var l=this.lines.get(o);if(!(!l||!l.isWrapped&&l.getTrimmedLength()<=e)){for(var c=[l];l.isWrapped&&o>0;)l=this.lines.get(--o),c.unshift(l);var h=this.ybase+this.y;if(!(h>=o&&h0&&(r.push({start:o+c.length+s,newLines:_}),s+=_.length),c.push.apply(c,_);var v=f.length-1,y=f[v];0===y&&(y=f[--v]);for(var b=c.length-p-1,w=d;b>=0;){var C=Math.min(w,y);if(c[v].copyCellsFrom(c[b],w-C,y-C,C,!0),0==(y-=C)&&(y=f[--v]),0==(w-=C)){b--;var S=Math.max(b,0);w=a.getWrappedLineTrimmedLength(c,S,this._cols)}}for(m=0;m0;)0===this.ybase?this.y0){var x=[],E=[];for(m=0;m=0;m--)if(R&&R.start>O+L){for(var P=R.newLines.length-1;P>=0;P--)this.lines.set(m--,R.newLines[P]);m++,x.push({index:O+1,amount:R.newLines.length}),L+=R.newLines.length,R=r[++T]}else this.lines.set(m,E[O--]);var D=0;for(m=x.length-1;m>=0;m--)x[m].index+=D,this.lines.onInsertEmitter.fire(x[m]),D+=x[m].amount;var I=Math.max(0,A+s-this.lines.maxLength);I>0&&this.lines.onTrimEmitter.fire(I)}},e.prototype.stringIndexToBufferIndex=function(e,t,i){for(void 0===i&&(i=!1);t;){var r=this.lines.get(e);if(!r)return[-1,-1];for(var n=i?r.getTrimmedLength():r.length,s=0;s0&&this.lines.get(t).isWrapped;)t--;for(;i+10;);return e>=this._cols?this._cols-1:e<0?0:e},e.prototype.nextStop=function(e){for(null==e&&(e=this.x);!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e},e.prototype.addMarker=function(e){var t=this,i=new l.Marker(e);return this.markers.push(i),i.register(this.lines.onTrim((function(e){i.line-=e,i.line<0&&i.dispose()}))),i.register(this.lines.onInsert((function(e){i.line>=e.index&&(i.line+=e.amount)}))),i.register(this.lines.onDelete((function(e){i.line>=e.index&&i.linee.index&&(i.line-=e.amount)}))),i.register(i.onDispose((function(){return t._removeMarker(i)}))),i},e.prototype._removeMarker=function(e){this.markers.splice(this.markers.indexOf(e),1)},e.prototype.iterator=function(e,t,i,r,n){return new d(this,e,t,i,r,n)},e}();t.Buffer=u;var d=function(){function e(e,t,i,r,n,s){void 0===i&&(i=0),void 0===r&&(r=e.lines.length),void 0===n&&(n=0),void 0===s&&(s=0),this._buffer=e,this._trimRight=t,this._startIndex=i,this._endIndex=r,this._startOverscan=n,this._endOverscan=s,this._startIndex<0&&(this._startIndex=0),this._endIndex>this._buffer.lines.length&&(this._endIndex=this._buffer.lines.length),this._current=this._startIndex}return e.prototype.hasNext=function(){return this._currentthis._endIndex+this._endOverscan&&(e.last=this._endIndex+this._endOverscan),e.first=Math.max(e.first,0),e.last=Math.min(e.last,this._buffer.lines.length);for(var t="",i=e.first;i<=e.last;++i)t+=this._buffer.translateBufferLineToString(i,this._trimRight);return this._current=e.last+1,{range:e,content:t}},e}();t.BufferStringIterator=d},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CircularList=void 0;var r=i(0),n=function(){function e(e){this._maxLength=e,this.onDeleteEmitter=new r.EventEmitter,this.onInsertEmitter=new r.EventEmitter,this.onTrimEmitter=new r.EventEmitter,this._array=new Array(this._maxLength),this._startIndex=0,this._length=0}return Object.defineProperty(e.prototype,"onDelete",{get:function(){return this.onDeleteEmitter.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onInsert",{get:function(){return this.onInsertEmitter.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onTrim",{get:function(){return this.onTrimEmitter.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"maxLength",{get:function(){return this._maxLength},set:function(e){if(this._maxLength!==e){for(var t=new Array(e),i=0;ithis._length)for(var t=this._length;t=e;n--)this._array[this._getCyclicIndex(n+i.length)]=this._array[this._getCyclicIndex(n)];for(n=0;nthis._maxLength){var s=this._length+i.length-this._maxLength;this._startIndex+=s,this._length=this._maxLength,this.onTrimEmitter.fire(s)}else this._length+=i.length},e.prototype.trimStart=function(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)},e.prototype.shiftElements=function(e,t,i){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+i<0)throw new Error("Cannot shift elements in list beyond index 0");if(i>0){for(var r=t-1;r>=0;r--)this.set(e+r+i,this.get(e+r));var n=e+t+i-this._length;if(n>0)for(this._length+=n;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(r=0;r=a&&n0&&(v>u||0===h[v].getTrimmedLength());v--)g++;g>0&&(o.push(a+h.length-g),o.push(g)),a+=h.length-1}}}return o},t.reflowLargerCreateNewLayout=function(e,t){for(var i=[],r=0,n=t[r],s=0,o=0;oc&&(o-=c,a++);var h=2===e[a].getWidth(o-1);h&&o--;var u=h?i-1:i;n.push(u),l+=u}return n},t.getWrappedLineTrimmedLength=r},function(e,t,i){"use strict";var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){function i(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)});Object.defineProperty(t,"__esModule",{value:!0}),t.Marker=void 0;var s=i(0),o=function(e){function t(i){var r=e.call(this)||this;return r.line=i,r._id=t._nextId++,r.isDisposed=!1,r._onDispose=new s.EventEmitter,r}return n(t,e),Object.defineProperty(t.prototype,"id",{get:function(){return this._id},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onDispose",{get:function(){return this._onDispose.event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire())},t._nextId=1,t}(i(2).Disposable);t.Marker=o},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OptionsService=t.DEFAULT_OPTIONS=t.DEFAULT_BELL_SOUND=void 0;var r=i(0),n=i(11),s=i(33);t.DEFAULT_BELL_SOUND="data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",t.DEFAULT_OPTIONS=Object.freeze({cols:80,rows:24,cursorBlink:!1,cursorStyle:"block",cursorWidth:1,bellSound:t.DEFAULT_BELL_SOUND,bellStyle:"none",drawBoldTextInBrightColors:!0,fastScrollModifier:"alt",fastScrollSensitivity:5,fontFamily:"courier-new, courier, monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",lineHeight:1,linkTooltipHoverDuration:500,letterSpacing:0,logLevel:"info",scrollback:1e3,scrollSensitivity:1,screenReaderMode:!1,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!0,allowTransparency:!1,tabStopWidth:8,theme:{},rightClickSelectsWord:n.isMac,rendererType:"canvas",windowOptions:{},windowsMode:!1,wordSeparator:" ()[]{}',\"`",convertEol:!1,termName:"xterm",cancelEvents:!1});var o=["normal","bold","100","200","300","400","500","600","700","800","900"],a=["cols","rows"],l=function(){function e(e){this._onOptionChange=new r.EventEmitter,this.options=s.clone(t.DEFAULT_OPTIONS);for(var i=0,n=Object.keys(e);i=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},o=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreService=void 0;var a=i(1),l=i(0),c=i(33),h=i(2),u=Object.freeze({insertMode:!1}),d=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,origin:!1,reverseWraparound:!1,sendFocus:!1,wraparound:!0}),f=function(e){function t(t,i,r,n){var s=e.call(this)||this;return s._bufferService=i,s._logService=r,s._optionsService=n,s.isCursorInitialized=!1,s.isCursorHidden=!1,s._onData=s.register(new l.EventEmitter),s._onUserInput=s.register(new l.EventEmitter),s._onBinary=s.register(new l.EventEmitter),s._scrollToBottom=t,s.register({dispose:function(){return s._scrollToBottom=void 0}}),s.modes=c.clone(u),s.decPrivateModes=c.clone(d),s}return n(t,e),Object.defineProperty(t.prototype,"onData",{get:function(){return this._onData.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onUserInput",{get:function(){return this._onUserInput.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onBinary",{get:function(){return this._onBinary.event},enumerable:!1,configurable:!0}),t.prototype.reset=function(){this.modes=c.clone(u),this.decPrivateModes=c.clone(d)},t.prototype.triggerDataEvent=function(e,t){if(void 0===t&&(t=!1),!this._optionsService.options.disableStdin){var i=this._bufferService.buffer;i.ybase!==i.ydisp&&this._scrollToBottom(),t&&this._onUserInput.fire(),this._logService.debug('sending data "'+e+'"',(function(){return e.split("").map((function(e){return e.charCodeAt(0)}))})),this._onData.fire(e)}},t.prototype.triggerBinaryEvent=function(e){this._optionsService.options.disableStdin||(this._logService.debug('sending binary "'+e+'"',(function(){return e.split("").map((function(e){return e.charCodeAt(0)}))})),this._onBinary.fire(e))},s([o(1,a.IBufferService),o(2,a.ILogService),o(3,a.IOptionsService)],t)}(h.Disposable);t.CoreService=f},function(e,t,i){"use strict";var r=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},n=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreMouseService=void 0;var s=i(1),o=i(0),a={NONE:{events:0,restrict:function(){return!1}},X10:{events:1,restrict:function(e){return 4!==e.button&&1===e.action&&(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)}},VT200:{events:19,restrict:function(e){return 32!==e.action}},DRAG:{events:23,restrict:function(e){return 32!==e.action||3!==e.button}},ANY:{events:31,restrict:function(e){return!0}}};function l(e,t){var i=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return 4===e.button?(i|=64,i|=e.action):(i|=3&e.button,4&e.button&&(i|=64),8&e.button&&(i|=128),32===e.action?i|=32:0!==e.action||t||(i|=3)),i}var c=String.fromCharCode,h={DEFAULT:function(e){var t=[l(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":"\x1b[M"+c(t[0])+c(t[1])+c(t[2])},SGR:function(e){var t=0===e.action&&4!==e.button?"m":"M";return"\x1b[<"+l(e,!0)+";"+e.col+";"+e.row+t}},u=function(){function e(e,t){this._bufferService=e,this._coreService=t,this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._onProtocolChange=new o.EventEmitter,this._lastEvent=null;for(var i=0,r=Object.keys(a);i=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows)return!1;if(4===e.button&&32===e.action)return!1;if(3===e.button&&32!==e.action)return!1;if(4!==e.button&&(2===e.action||3===e.action))return!1;if(e.col++,e.row++,32===e.action&&this._lastEvent&&this._compareEvents(this._lastEvent,e))return!1;if(!this._protocols[this._activeProtocol].restrict(e))return!1;var t=this._encodings[this._activeEncoding](e);return t&&("DEFAULT"===this._activeEncoding?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0},e.prototype.explainEvents=function(e){return{down:!!(1&e),up:!!(2&e),drag:!!(4&e),move:!!(8&e),wheel:!!(16&e)}},e.prototype._compareEvents=function(e,t){return e.col===t.col&&e.row===t.row&&e.button===t.button&&e.action===t.action&&e.ctrl===t.ctrl&&e.alt===t.alt&&e.shift===t.shift},r([n(0,s.IBufferService),n(1,s.ICoreService)],e)}();t.CoreMouseService=u},function(e,t,i){"use strict";var r=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},n=this&&this.__param||function(e,t){return function(i,r){t(i,r,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DirtyRowService=void 0;var s=i(1),o=function(){function e(e){this._bufferService=e,this.clearRange()}return Object.defineProperty(e.prototype,"start",{get:function(){return this._start},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"end",{get:function(){return this._end},enumerable:!1,configurable:!0}),e.prototype.clearRange=function(){this._start=this._bufferService.buffer.y,this._end=this._bufferService.buffer.y},e.prototype.markDirty=function(e){ethis._end&&(this._end=e)},e.prototype.markRangeDirty=function(e,t){if(e>t){var i=e;e=t,t=i}ethis._end&&(this._end=t)},e.prototype.markAllDirty=function(){this.markRangeDirty(0,this._bufferService.rows-1)},r([n(0,s.IBufferService)],e)}();t.DirtyRowService=o},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeService=void 0;var r=i(0),n=i(79),s=function(){function e(){this._providers=Object.create(null),this._active="",this._onChange=new r.EventEmitter;var e=new n.UnicodeV6;this.register(e),this._active=e.version,this._activeProvider=e}return Object.defineProperty(e.prototype,"onChange",{get:function(){return this._onChange.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"versions",{get:function(){return Object.keys(this._providers)},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"activeVersion",{get:function(){return this._active},set:function(e){if(!this._providers[e])throw new Error('unknown Unicode version "'+e+'"');this._active=e,this._activeProvider=this._providers[e],this._onChange.fire(e)},enumerable:!1,configurable:!0}),e.prototype.register=function(e){this._providers[e.version]=e},e.prototype.wcwidth=function(e){return this._activeProvider.wcwidth(e)},e.prototype.getStringCellWidth=function(e){for(var t=0,i=e.length,r=0;r=i)return t+this.wcwidth(n);var s=e.charCodeAt(r);56320<=s&&s<=57343?n=1024*(n-55296)+s-56320+65536:t+=this.wcwidth(s)}t+=this.wcwidth(n)}return t},e}();t.UnicodeService=s},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV6=void 0;var r,n=i(15),s=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],o=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]],a=function(){function e(){if(this.version="6",!r){r=new Uint8Array(65536),n.fill(r,1),r[0]=0,n.fill(r,0,1,32),n.fill(r,0,127,160),n.fill(r,2,4352,4448),r[9001]=2,r[9002]=2,n.fill(r,2,11904,42192),r[12351]=1,n.fill(r,2,44032,55204),n.fill(r,2,63744,64256),n.fill(r,2,65040,65050),n.fill(r,2,65072,65136),n.fill(r,2,65280,65377),n.fill(r,2,65504,65511);for(var e=0;et[n][1])return!1;for(;n>=r;)if(e>t[i=r+n>>1][1])r=i+1;else{if(!(e=131072&&e<=196605||e>=196608&&e<=262141?2:1},e}();t.UnicodeV6=a},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CharsetService=void 0;var r=function(){function e(){this.glevel=0,this._charsets=[]}return e.prototype.reset=function(){this.charset=void 0,this._charsets=[],this.glevel=0},e.prototype.setgLevel=function(e){this.glevel=e,this.charset=this._charsets[e]},e.prototype.setgCharset=function(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)},e}();t.CharsetService=r},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.updateWindowsModeWrappedState=void 0;var r=i(3);t.updateWindowsModeWrappedState=function(e){var t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1),i=null==t?void 0:t.get(e.cols-1),n=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);n&&i&&(n.isWrapped=i[r.CHAR_DATA_CODE_INDEX]!==r.NULL_CELL_CODE&&i[r.CHAR_DATA_CODE_INDEX]!==r.WHITESPACE_CELL_CODE)}},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.WriteBuffer=void 0;var r=function(){function e(e){this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0}return e.prototype.writeSync=function(e){if(this._writeBuffer.length){for(var t=this._bufferOffset;t5e7)throw new Error("write data discarded, use flow control to avoid losing data");this._writeBuffer.length||(this._bufferOffset=0,setTimeout((function(){return i._innerWrite()}))),this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)},e.prototype._innerWrite=function(){for(var e=this,t=Date.now();this._writeBuffer.length>this._bufferOffset;){var i=this._writeBuffer[this._bufferOffset],r=this._callbacks[this._bufferOffset];if(this._bufferOffset++,this._action(i),this._pendingData-=i.length,r&&r(),Date.now()-t>=12)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>50&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout((function(){return e._innerWrite()}),0)):(this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0)},e}();t.WriteBuffer=r},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AddonManager=void 0;var r=function(){function e(){this._addons=[]}return e.prototype.dispose=function(){for(var e=this._addons.length-1;e>=0;e--)this._addons[e].instance.dispose()},e.prototype.loadAddon=function(e,t){var i=this,r={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(r),t.dispose=function(){return i._wrappedAddonDispose(r)},t.activate(e)},e.prototype._wrappedAddonDispose=function(e){if(!e.isDisposed){for(var t=-1,i=0;i0&&(n.set(c,h-1),a++)}return 2*a/(e.length+t.length-2*(i-1))},t.default=t.stringSimilarity},zUnb:function(e,t,i){"use strict";function r(e){return"function"==typeof e}i.r(t);let n=!1;const s={Promise:void 0,set useDeprecatedSynchronousErrorHandling(e){if(e){const e=new Error;console.warn("DEPRECATED! RxJS was set to use deprecated synchronous error handling behavior by code at: \n"+e.stack)}else n&&console.log("RxJS: Back to a better error behavior. Thank you. <3");n=e},get useDeprecatedSynchronousErrorHandling(){return n}};function o(e){setTimeout(()=>{throw e},0)}const a={closed:!0,next(e){},error(e){if(s.useDeprecatedSynchronousErrorHandling)throw e;o(e)},complete(){}},l=(()=>Array.isArray||(e=>e&&"number"==typeof e.length))();function c(e){return null!==e&&"object"==typeof e}const h=(()=>{function e(e){return Error.call(this),this.message=e?`${e.length} errors occurred during unsubscription:\n${e.map((e,t)=>`${t+1}) ${e.toString()}`).join("\n ")}`:"",this.name="UnsubscriptionError",this.errors=e,this}return e.prototype=Object.create(Error.prototype),e})();let u=(()=>{class e{constructor(e){this.closed=!1,this._parentOrParents=null,this._subscriptions=null,e&&(this._unsubscribe=e)}unsubscribe(){let t;if(this.closed)return;let{_parentOrParents:i,_unsubscribe:n,_subscriptions:s}=this;if(this.closed=!0,this._parentOrParents=null,this._subscriptions=null,i instanceof e)i.remove(this);else if(null!==i)for(let e=0;ee.concat(t instanceof h?t.errors:t),[])}const f=(()=>"function"==typeof Symbol?Symbol("rxSubscriber"):"@@rxSubscriber_"+Math.random())();class p extends u{constructor(e,t,i){switch(super(),this.syncErrorValue=null,this.syncErrorThrown=!1,this.syncErrorThrowable=!1,this.isStopped=!1,arguments.length){case 0:this.destination=a;break;case 1:if(!e){this.destination=a;break}if("object"==typeof e){e instanceof p?(this.syncErrorThrowable=e.syncErrorThrowable,this.destination=e,e.add(this)):(this.syncErrorThrowable=!0,this.destination=new _(this,e));break}default:this.syncErrorThrowable=!0,this.destination=new _(this,e,t,i)}}[f](){return this}static create(e,t,i){const r=new p(e,t,i);return r.syncErrorThrowable=!1,r}next(e){this.isStopped||this._next(e)}error(e){this.isStopped||(this.isStopped=!0,this._error(e))}complete(){this.isStopped||(this.isStopped=!0,this._complete())}unsubscribe(){this.closed||(this.isStopped=!0,super.unsubscribe())}_next(e){this.destination.next(e)}_error(e){this.destination.error(e),this.unsubscribe()}_complete(){this.destination.complete(),this.unsubscribe()}_unsubscribeAndRecycle(){const{_parentOrParents:e}=this;return this._parentOrParents=null,this.unsubscribe(),this.closed=!1,this.isStopped=!1,this._parentOrParents=e,this}}class _ extends p{constructor(e,t,i,n){let s;super(),this._parentSubscriber=e;let o=this;r(t)?s=t:t&&(s=t.next,i=t.error,n=t.complete,t!==a&&(o=Object.create(t),r(o.unsubscribe)&&this.add(o.unsubscribe.bind(o)),o.unsubscribe=this.unsubscribe.bind(this))),this._context=o,this._next=s,this._error=i,this._complete=n}next(e){if(!this.isStopped&&this._next){const{_parentSubscriber:t}=this;s.useDeprecatedSynchronousErrorHandling&&t.syncErrorThrowable?this.__tryOrSetError(t,this._next,e)&&this.unsubscribe():this.__tryOrUnsub(this._next,e)}}error(e){if(!this.isStopped){const{_parentSubscriber:t}=this,{useDeprecatedSynchronousErrorHandling:i}=s;if(this._error)i&&t.syncErrorThrowable?(this.__tryOrSetError(t,this._error,e),this.unsubscribe()):(this.__tryOrUnsub(this._error,e),this.unsubscribe());else if(t.syncErrorThrowable)i?(t.syncErrorValue=e,t.syncErrorThrown=!0):o(e),this.unsubscribe();else{if(this.unsubscribe(),i)throw e;o(e)}}}complete(){if(!this.isStopped){const{_parentSubscriber:e}=this;if(this._complete){const t=()=>this._complete.call(this._context);s.useDeprecatedSynchronousErrorHandling&&e.syncErrorThrowable?(this.__tryOrSetError(e,t),this.unsubscribe()):(this.__tryOrUnsub(t),this.unsubscribe())}else this.unsubscribe()}}__tryOrUnsub(e,t){try{e.call(this._context,t)}catch(i){if(this.unsubscribe(),s.useDeprecatedSynchronousErrorHandling)throw i;o(i)}}__tryOrSetError(e,t,i){if(!s.useDeprecatedSynchronousErrorHandling)throw new Error("bad call");try{t.call(this._context,i)}catch(r){return s.useDeprecatedSynchronousErrorHandling?(e.syncErrorValue=r,e.syncErrorThrown=!0,!0):(o(r),!0)}return!1}_unsubscribe(){const{_parentSubscriber:e}=this;this._context=null,this._parentSubscriber=null,e.unsubscribe()}}const m=(()=>"function"==typeof Symbol&&Symbol.observable||"@@observable")();function g(e){return e}let v=(()=>{class e{constructor(e){this._isScalar=!1,e&&(this._subscribe=e)}lift(t){const i=new e;return i.source=this,i.operator=t,i}subscribe(e,t,i){const{operator:r}=this,n=function(e,t,i){if(e){if(e instanceof p)return e;if(e[f])return e[f]()}return e||t||i?new p(e,t,i):new p(a)}(e,t,i);if(n.add(r?r.call(n,this.source):this.source||s.useDeprecatedSynchronousErrorHandling&&!n.syncErrorThrowable?this._subscribe(n):this._trySubscribe(n)),s.useDeprecatedSynchronousErrorHandling&&n.syncErrorThrowable&&(n.syncErrorThrowable=!1,n.syncErrorThrown))throw n.syncErrorValue;return n}_trySubscribe(e){try{return this._subscribe(e)}catch(t){s.useDeprecatedSynchronousErrorHandling&&(e.syncErrorThrown=!0,e.syncErrorValue=t),function(e){for(;e;){const{closed:t,destination:i,isStopped:r}=e;if(t||r)return!1;e=i&&i instanceof p?i:null}return!0}(e)?e.error(t):console.warn(t)}}forEach(e,t){return new(t=y(t))((t,i)=>{let r;r=this.subscribe(t=>{try{e(t)}catch(n){i(n),r&&r.unsubscribe()}},i,t)})}_subscribe(e){const{source:t}=this;return t&&t.subscribe(e)}[m](){return this}pipe(...e){return 0===e.length?this:(0===(t=e).length?g:1===t.length?t[0]:function(e){return t.reduce((e,t)=>t(e),e)})(this);var t}toPromise(e){return new(e=y(e))((e,t)=>{let i;this.subscribe(e=>i=e,e=>t(e),()=>e(i))})}}return e.create=t=>new e(t),e})();function y(e){if(e||(e=s.Promise||Promise),!e)throw new Error("no Promise impl found");return e}const b=(()=>{function e(){return Error.call(this),this.message="object unsubscribed",this.name="ObjectUnsubscribedError",this}return e.prototype=Object.create(Error.prototype),e})();class w extends u{constructor(e,t){super(),this.subject=e,this.subscriber=t,this.closed=!1}unsubscribe(){if(this.closed)return;this.closed=!0;const e=this.subject,t=e.observers;if(this.subject=null,!t||0===t.length||e.isStopped||e.closed)return;const i=t.indexOf(this.subscriber);-1!==i&&t.splice(i,1)}}class C extends p{constructor(e){super(e),this.destination=e}}let S=(()=>{class e extends v{constructor(){super(),this.observers=[],this.closed=!1,this.isStopped=!1,this.hasError=!1,this.thrownError=null}[f](){return new C(this)}lift(e){const t=new k(this,this);return t.operator=e,t}next(e){if(this.closed)throw new b;if(!this.isStopped){const{observers:t}=this,i=t.length,r=t.slice();for(let n=0;nnew k(e,t),e})();class k extends S{constructor(e,t){super(),this.destination=e,this.source=t}next(e){const{destination:t}=this;t&&t.next&&t.next(e)}error(e){const{destination:t}=this;t&&t.error&&this.destination.error(e)}complete(){const{destination:e}=this;e&&e.complete&&this.destination.complete()}_subscribe(e){const{source:t}=this;return t?this.source.subscribe(e):u.EMPTY}}function x(e){return e&&"function"==typeof e.schedule}class E extends p{constructor(e,t,i){super(),this.parent=e,this.outerValue=t,this.outerIndex=i,this.index=0}_next(e){this.parent.notifyNext(this.outerValue,e,this.outerIndex,this.index++,this)}_error(e){this.parent.notifyError(e,this),this.unsubscribe()}_complete(){this.parent.notifyComplete(this),this.unsubscribe()}}const A=e=>t=>{for(let i=0,r=e.length;ie&&"number"==typeof e.length&&"function"!=typeof e;function L(e){return!!e&&"function"!=typeof e.subscribe&&"function"==typeof e.then}const P=e=>{if(e&&"function"==typeof e[m])return r=e,e=>{const t=r[m]();if("function"!=typeof t.subscribe)throw new TypeError("Provided object does not correctly implement Symbol.observable");return t.subscribe(e)};if(R(e))return A(e);if(L(e))return i=e,e=>(i.then(t=>{e.closed||(e.next(t),e.complete())},t=>e.error(t)).then(null,o),e);if(e&&"function"==typeof e[T])return t=e,e=>{const i=t[T]();for(;;){const t=i.next();if(t.done){e.complete();break}if(e.next(t.value),e.closed)break}return"function"==typeof i.return&&e.add(()=>{i.return&&i.return()}),e};{const t=c(e)?"an invalid object":`'${e}'`;throw new TypeError(`You provided ${t} where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.`)}var t,i,r};function D(e,t,i,r,n=new E(e,i,r)){if(!n.closed)return t instanceof v?t.subscribe(n):P(t)(n)}class I extends p{notifyNext(e,t,i,r,n){this.destination.next(t)}notifyError(e,t){this.destination.error(e)}notifyComplete(e){this.destination.complete()}}function M(e,t){return function(i){if("function"!=typeof e)throw new TypeError("argument is not a function. Are you looking for `mapTo()`?");return i.lift(new F(e,t))}}class F{constructor(e,t){this.project=e,this.thisArg=t}call(e,t){return t.subscribe(new N(e,this.project,this.thisArg))}}class N extends p{constructor(e,t,i){super(e),this.project=t,this.count=0,this.thisArg=i||this}_next(e){let t;try{t=this.project.call(this.thisArg,e,this.count++)}catch(i){return void this.destination.error(i)}this.destination.next(t)}}function j(e,t){return new v(i=>{const r=new u;let n=0;return r.add(t.schedule((function(){n!==e.length?(i.next(e[n++]),i.closed||r.add(this.schedule())):i.complete()}))),r})}function B(e,t){return t?function(e,t){if(null!=e){if(function(e){return e&&"function"==typeof e[m]}(e))return function(e,t){return new v(i=>{const r=new u;return r.add(t.schedule(()=>{const n=e[m]();r.add(n.subscribe({next(e){r.add(t.schedule(()=>i.next(e)))},error(e){r.add(t.schedule(()=>i.error(e)))},complete(){r.add(t.schedule(()=>i.complete()))}}))})),r})}(e,t);if(L(e))return function(e,t){return new v(i=>{const r=new u;return r.add(t.schedule(()=>e.then(e=>{r.add(t.schedule(()=>{i.next(e),r.add(t.schedule(()=>i.complete()))}))},e=>{r.add(t.schedule(()=>i.error(e)))}))),r})}(e,t);if(R(e))return j(e,t);if(function(e){return e&&"function"==typeof e[T]}(e)||"string"==typeof e)return function(e,t){if(!e)throw new Error("Iterable cannot be null");return new v(i=>{const r=new u;let n;return r.add(()=>{n&&"function"==typeof n.return&&n.return()}),r.add(t.schedule(()=>{n=e[T](),r.add(t.schedule((function(){if(i.closed)return;let e,t;try{const i=n.next();e=i.value,t=i.done}catch(r){return void i.error(r)}t?i.complete():(i.next(e),this.schedule())})))})),r})}(e,t)}throw new TypeError((null!==e&&typeof e||e)+" is not observable")}(e,t):e instanceof v?e:new v(P(e))}function H(e,t,i=Number.POSITIVE_INFINITY){return"function"==typeof t?r=>r.pipe(H((i,r)=>B(e(i,r)).pipe(M((e,n)=>t(i,e,r,n))),i)):("number"==typeof t&&(i=t),t=>t.lift(new U(e,i)))}class U{constructor(e,t=Number.POSITIVE_INFINITY){this.project=e,this.concurrent=t}call(e,t){return t.subscribe(new V(e,this.project,this.concurrent))}}class V extends I{constructor(e,t,i=Number.POSITIVE_INFINITY){super(e),this.project=t,this.concurrent=i,this.hasCompleted=!1,this.buffer=[],this.active=0,this.index=0}_next(e){this.active0?this._next(t.shift()):0===this.active&&this.hasCompleted&&this.destination.complete()}}function q(e=Number.POSITIVE_INFINITY){return H(g,e)}function z(e,t){return t?j(e,t):new v(A(e))}function W(...e){let t=Number.POSITIVE_INFINITY,i=null,r=e[e.length-1];return x(r)?(i=e.pop(),e.length>1&&"number"==typeof e[e.length-1]&&(t=e.pop())):"number"==typeof r&&(t=e.pop()),null===i&&1===e.length&&e[0]instanceof v?e[0]:q(t)(z(e,i))}function $(){return function(e){return e.lift(new K(e))}}class K{constructor(e){this.connectable=e}call(e,t){const{connectable:i}=this;i._refCount++;const r=new G(e,i),n=t.subscribe(r);return r.closed||(r.connection=i.connect()),n}}class G extends p{constructor(e,t){super(e),this.connectable=t}_unsubscribe(){const{connectable:e}=this;if(!e)return void(this.connection=null);this.connectable=null;const t=e._refCount;if(t<=0)return void(this.connection=null);if(e._refCount=t-1,t>1)return void(this.connection=null);const{connection:i}=this,r=e._connection;this.connection=null,!r||i&&r!==i||r.unsubscribe()}}class Z extends v{constructor(e,t){super(),this.source=e,this.subjectFactory=t,this._refCount=0,this._isComplete=!1}_subscribe(e){return this.getSubject().subscribe(e)}getSubject(){const e=this._subject;return e&&!e.isStopped||(this._subject=this.subjectFactory()),this._subject}connect(){let e=this._connection;return e||(this._isComplete=!1,e=this._connection=new u,e.add(this.source.subscribe(new X(this.getSubject(),this))),e.closed&&(this._connection=null,e=u.EMPTY)),e}refCount(){return $()(this)}}const Y=(()=>{const e=Z.prototype;return{operator:{value:null},_refCount:{value:0,writable:!0},_subject:{value:null,writable:!0},_connection:{value:null,writable:!0},_subscribe:{value:e._subscribe},_isComplete:{value:e._isComplete,writable:!0},getSubject:{value:e.getSubject},connect:{value:e.connect},refCount:{value:e.refCount}}})();class X extends C{constructor(e,t){super(e),this.connectable=t}_error(e){this._unsubscribe(),super._error(e)}_complete(){this.connectable._isComplete=!0,this._unsubscribe(),super._complete()}_unsubscribe(){const e=this.connectable;if(e){this.connectable=null;const t=e._connection;e._refCount=0,e._subject=null,e._connection=null,t&&t.unsubscribe()}}}function Q(e,t){return function(i){let r;if(r="function"==typeof e?e:function(){return e},"function"==typeof t)return i.lift(new J(r,t));const n=Object.create(i,Y);return n.source=i,n.subjectFactory=r,n}}class J{constructor(e,t){this.subjectFactory=e,this.selector=t}call(e,t){const{selector:i}=this,r=this.subjectFactory(),n=i(r).subscribe(e);return n.add(t.subscribe(r)),n}}function ee(){return new S}function te(){return e=>$()(Q(ee)(e))}function ie(e){return{toString:e}.toString()}function re(e,t,i){return ie(()=>{const r=function(e){return function(...t){if(e){const i=e(...t);for(const e in i)this[e]=i[e]}}}(t);function n(...e){if(this instanceof n)return r.apply(this,e),this;const t=new n(...e);return i.annotation=t,i;function i(e,i,r){const n=e.hasOwnProperty("__parameters__")?e.__parameters__:Object.defineProperty(e,"__parameters__",{value:[]}).__parameters__;for(;n.length<=r;)n.push(null);return(n[r]=n[r]||[]).push(t),e}}return i&&(n.prototype=Object.create(i.prototype)),n.prototype.ngMetadataName=e,n.annotationCls=n,n})}const ne=re("Inject",e=>({token:e})),se=re("Optional"),oe=re("Self"),ae=re("SkipSelf");var le=function(e){return e[e.Default=0]="Default",e[e.Host=1]="Host",e[e.Self=2]="Self",e[e.SkipSelf=4]="SkipSelf",e[e.Optional=8]="Optional",e}({});function ce(e){for(let t in e)if(e[t]===ce)return t;throw Error("Could not find renamed property on target object.")}function he(e,t){for(const i in t)t.hasOwnProperty(i)&&!e.hasOwnProperty(i)&&(e[i]=t[i])}function ue(e){return{token:e.token,providedIn:e.providedIn||null,factory:e.factory,value:void 0}}function de(e){return{factory:e.factory,providers:e.providers||[],imports:e.imports||[]}}function fe(e){return pe(e,e[me])||pe(e,e[ye])}function pe(e,t){return t&&t.token===e?t:null}function _e(e){return e&&(e.hasOwnProperty(ge)||e.hasOwnProperty(be))?e[ge]:null}const me=ce({"\u0275prov":ce}),ge=ce({"\u0275inj":ce}),ve=ce({"\u0275provFallback":ce}),ye=ce({ngInjectableDef:ce}),be=ce({ngInjectorDef:ce});function we(e){if("string"==typeof e)return e;if(Array.isArray(e))return"["+e.map(we).join(", ")+"]";if(null==e)return""+e;if(e.overriddenName)return""+e.overriddenName;if(e.name)return""+e.name;const t=e.toString();if(null==t)return""+t;const i=t.indexOf("\n");return-1===i?t:t.substring(0,i)}function Ce(e,t){return null==e||""===e?null===t?"":t:null==t||""===t?e:e+" "+t}const Se=ce({__forward_ref__:ce});function ke(e){return e.__forward_ref__=ke,e.toString=function(){return we(this())},e}function xe(e){return Ee(e)?e():e}function Ee(e){return"function"==typeof e&&e.hasOwnProperty(Se)&&e.__forward_ref__===ke}const Ae="undefined"!=typeof globalThis&&globalThis,Oe="undefined"!=typeof window&&window,Te="undefined"!=typeof self&&"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope&&self,Re="undefined"!=typeof global&&global,Le=Ae||Re||Oe||Te,Pe=ce({"\u0275cmp":ce}),De=ce({"\u0275dir":ce}),Ie=ce({"\u0275pipe":ce}),Me=ce({"\u0275mod":ce}),Fe=ce({"\u0275loc":ce}),Ne=ce({"\u0275fac":ce}),je=ce({__NG_ELEMENT_ID__:ce});class Be{constructor(e,t){this._desc=e,this.ngMetadataName="InjectionToken",this.\u0275prov=void 0,"number"==typeof t?this.__NG_ELEMENT_ID__=t:void 0!==t&&(this.\u0275prov=ue({token:this,providedIn:t.providedIn||"root",factory:t.factory}))}toString(){return"InjectionToken "+this._desc}}const He=new Be("INJECTOR",-1),Ue={},Ve=/\n/gm,qe=ce({provide:String,useValue:ce});let ze,We=void 0;function $e(e){const t=We;return We=e,t}function Ke(e){const t=ze;return ze=e,t}function Ge(e,t=le.Default){if(void 0===We)throw new Error("inject() must be called from an injection context");return null===We?Xe(e,void 0,t):We.get(e,t&le.Optional?null:void 0,t)}function Ze(e,t=le.Default){return(ze||Ge)(xe(e),t)}const Ye=Ze;function Xe(e,t,i){const r=fe(e);if(r&&"root"==r.providedIn)return void 0===r.value?r.value=r.factory():r.value;if(i&le.Optional)return null;if(void 0!==t)return t;throw new Error(`Injector: NOT_FOUND [${we(e)}]`)}function Qe(e){const t=[];for(let i=0;iArray.isArray(e)?it(e,t):t(e))}function rt(e,t,i){t>=e.length?e.push(i):e.splice(t,0,i)}function nt(e,t){return t>=e.length-1?e.pop():e.splice(t,1)[0]}function st(e,t){const i=[];for(let r=0;r=0?e[1|r]=i:(r=~r,function(e,t,i,r){let n=e.length;if(n==t)e.push(i,r);else if(1===n)e.push(r,e[0]),e[0]=i;else{for(n--,e.push(e[n-1],e[n]);n>t;)e[n]=e[n-2],n--;e[t]=i,e[t+1]=r}}(e,r,t,i)),r}function at(e,t){const i=lt(e,t);if(i>=0)return e[1|i]}function lt(e,t){return function(e,t,i){let r=0,n=e.length>>1;for(;n!==r;){const i=r+(n-r>>1),s=e[i<<1];if(t===s)return i<<1;s>t?n=i:r=i+1}return~(n<<1)}(e,t)}var ct=function(e){return e[e.OnPush=0]="OnPush",e[e.Default=1]="Default",e}({}),ht=function(e){return e[e.Emulated=0]="Emulated",e[e.Native=1]="Native",e[e.None=2]="None",e[e.ShadowDom=3]="ShadowDom",e}({});const ut={},dt=[];let ft=0;function pt(e){return ie(()=>{const t={},i={type:e.type,providersResolver:null,decls:e.decls,vars:e.vars,factory:null,template:e.template||null,consts:e.consts||null,ngContentSelectors:e.ngContentSelectors,hostBindings:e.hostBindings||null,hostVars:e.hostVars||0,hostAttrs:e.hostAttrs||null,contentQueries:e.contentQueries||null,declaredInputs:t,inputs:null,outputs:null,exportAs:e.exportAs||null,onPush:e.changeDetection===ct.OnPush,directiveDefs:null,pipeDefs:null,selectors:e.selectors||dt,viewQuery:e.viewQuery||null,features:e.features||null,data:e.data||{},encapsulation:e.encapsulation||ht.Emulated,id:"c",styles:e.styles||dt,_:null,setInput:null,schemas:e.schemas||null,tView:null},r=e.directives,n=e.features,s=e.pipes;return i.id+=ft++,i.inputs=yt(e.inputs,t),i.outputs=yt(e.outputs),n&&n.forEach(e=>e(i)),i.directiveDefs=r?()=>("function"==typeof r?r():r).map(_t):null,i.pipeDefs=s?()=>("function"==typeof s?s():s).map(mt):null,i})}function _t(e){return Ct(e)||function(e){return e[De]||null}(e)}function mt(e){return function(e){return e[Ie]||null}(e)}const gt={};function vt(e){const t={type:e.type,bootstrap:e.bootstrap||dt,declarations:e.declarations||dt,imports:e.imports||dt,exports:e.exports||dt,transitiveCompileScopes:null,schemas:e.schemas||null,id:e.id||null};return null!=e.id&&ie(()=>{gt[e.id]=e.type}),t}function yt(e,t){if(null==e)return ut;const i={};for(const r in e)if(e.hasOwnProperty(r)){let n=e[r],s=n;Array.isArray(n)&&(s=n[1],n=n[0]),i[n]=r,t&&(t[n]=s)}return i}const bt=pt;function wt(e){return{type:e.type,name:e.name,factory:null,pure:!1!==e.pure,onDestroy:e.type.prototype.ngOnDestroy||null}}function Ct(e){return e[Pe]||null}function St(e,t){return e.hasOwnProperty(Ne)?e[Ne]:null}function kt(e,t){const i=e[Me]||null;if(!i&&!0===t)throw new Error(`Type ${we(e)} does not have '\u0275mod' property.`);return i}function xt(e){return Array.isArray(e)&&"object"==typeof e[1]}function Et(e){return Array.isArray(e)&&!0===e[1]}function At(e){return 0!=(8&e.flags)}function Ot(e){return 2==(2&e.flags)}function Tt(e){return 1==(1&e.flags)}function Rt(e){return null!==e.template}function Lt(e){return 0!=(512&e[2])}class Pt{constructor(e,t,i){this.previousValue=e,this.currentValue=t,this.firstChange=i}isFirstChange(){return this.firstChange}}function Dt(){return It}function It(e){return e.type.prototype.ngOnChanges&&(e.setInput=Ft),Mt}function Mt(){const e=Nt(this),t=null==e?void 0:e.current;if(t){const i=e.previous;if(i===ut)e.previous=t;else for(let e in t)i[e]=t[e];e.current=null,this.ngOnChanges(t)}}function Ft(e,t,i,r){const n=Nt(e)||function(e,t){return e.__ngSimpleChanges__=t}(e,{previous:ut,current:null}),s=n.current||(n.current={}),o=n.previous,a=this.declaredInputs[i],l=o[a];s[a]=new Pt(l&&l.currentValue,t,o===ut),e[r]=t}function Nt(e){return e.__ngSimpleChanges__||null}Dt.ngInherit=!0;let jt=void 0;function Bt(){return void 0!==jt?jt:"undefined"!=typeof document?document:void 0}function Ht(e){return!!e.listen}const Ut={createRenderer:(e,t)=>Bt()};function Vt(e){for(;Array.isArray(e);)e=e[0];return e}function qt(e,t){return Vt(t[e+20])}function zt(e,t){return Vt(t[e.index])}function Wt(e,t){return e.data[t+20]}function $t(e,t){return e[t+20]}function Kt(e,t){const i=t[e];return xt(i)?i:i[0]}function Gt(e){const t=function(e){return e.__ngContext__||null}(e);return t?Array.isArray(t)?t:t.lView:null}function Zt(e){return 4==(4&e[2])}function Yt(e){return 128==(128&e[2])}function Xt(e,t){return null===e||null==t?null:e[t]}function Qt(e){e[18]=0}function Jt(e,t){e[5]+=t;let i=e,r=e[3];for(;null!==r&&(1===t&&1===i[5]||-1===t&&0===i[5]);)r[5]+=t,i=r,r=r[3]}const ei={lFrame:Ci(null),bindingsEnabled:!0,checkNoChangesMode:!1};function ti(){return ei.bindingsEnabled}function ii(){return ei.lFrame.lView}function ri(){return ei.lFrame.tView}function ni(e){ei.lFrame.contextLView=e}function si(){return ei.lFrame.previousOrParentTNode}function oi(e,t){ei.lFrame.previousOrParentTNode=e,ei.lFrame.isParent=t}function ai(){return ei.lFrame.isParent}function li(){ei.lFrame.isParent=!1}function ci(){return ei.checkNoChangesMode}function hi(e){ei.checkNoChangesMode=e}function ui(){const e=ei.lFrame;let t=e.bindingRootIndex;return-1===t&&(t=e.bindingRootIndex=e.tView.bindingStartIndex),t}function di(){return ei.lFrame.bindingIndex++}function fi(e){const t=ei.lFrame,i=t.bindingIndex;return t.bindingIndex=t.bindingIndex+e,i}function pi(e,t){const i=ei.lFrame;i.bindingIndex=i.bindingRootIndex=e,_i(t)}function _i(e){ei.lFrame.currentDirectiveIndex=e}function mi(e){const t=ei.lFrame.currentDirectiveIndex;return-1===t?null:e[t]}function gi(){return ei.lFrame.currentQueryIndex}function vi(e){ei.lFrame.currentQueryIndex=e}function yi(e,t){const i=wi();ei.lFrame=i,i.previousOrParentTNode=t,i.lView=e}function bi(e,t){const i=wi(),r=e[1];ei.lFrame=i,i.previousOrParentTNode=t,i.lView=e,i.tView=r,i.contextLView=e,i.bindingIndex=r.bindingStartIndex}function wi(){const e=ei.lFrame,t=null===e?null:e.child;return null===t?Ci(e):t}function Ci(e){const t={previousOrParentTNode:null,isParent:!0,lView:null,tView:null,selectedIndex:0,contextLView:null,elementDepthCount:0,currentNamespace:null,currentDirectiveIndex:-1,bindingRootIndex:-1,bindingIndex:-1,currentQueryIndex:0,parent:e,child:null};return null!==e&&(e.child=t),t}function Si(){const e=ei.lFrame;return ei.lFrame=e.parent,e.previousOrParentTNode=null,e.lView=null,e}const ki=Si;function xi(){const e=Si();e.isParent=!0,e.tView=null,e.selectedIndex=0,e.contextLView=null,e.elementDepthCount=0,e.currentDirectiveIndex=-1,e.currentNamespace=null,e.bindingRootIndex=-1,e.bindingIndex=-1,e.currentQueryIndex=0}function Ei(){return ei.lFrame.selectedIndex}function Ai(e){ei.lFrame.selectedIndex=e}function Oi(){const e=ei.lFrame;return Wt(e.tView,e.selectedIndex)}function Ti(){ei.lFrame.currentNamespace="http://www.w3.org/2000/svg"}function Ri(e,t){for(let i=t.directiveStart,r=t.directiveEnd;i=r)break}else t[o]<0&&(e[18]+=65536),(s>11>16&&(3&e[2])===t&&(e[2]+=2048,s.call(o)):s.call(o)}class Fi{constructor(e,t,i){this.factory=e,this.resolving=!1,this.canSeeViewProviders=t,this.injectImpl=i}}function Ni(e,t,i){const r=Ht(e);let n=0;for(;nt){o=s-1;break}}}for(;s>16}function Wi(e,t){let i=zi(e),r=t;for(;i>0;)r=r[15],i--;return r}function $i(e){return"string"==typeof e?e:null==e?"":""+e}function Ki(e){return"function"==typeof e?e.name||e.toString():"object"==typeof e&&null!=e&&"function"==typeof e.type?e.type.name||e.type.toString():$i(e)}const Gi=(()=>("undefined"!=typeof requestAnimationFrame&&requestAnimationFrame||setTimeout).bind(Le))();function Zi(e){return{name:"window",target:e.ownerDocument.defaultView}}function Yi(e){return e instanceof Function?e():e}let Xi=!0;function Qi(e){const t=Xi;return Xi=e,t}let Ji=0;function er(e,t){const i=ir(e,t);if(-1!==i)return i;const r=t[1];r.firstCreatePass&&(e.injectorIndex=t.length,tr(r.data,e),tr(t,null),tr(r.blueprint,null));const n=rr(e,t),s=e.injectorIndex;if(Vi(n)){const e=qi(n),i=Wi(n,t),r=i[1].data;for(let n=0;n<8;n++)t[s+n]=i[e+n]|r[e+n]}return t[s+8]=n,s}function tr(e,t){e.push(0,0,0,0,0,0,0,0,t)}function ir(e,t){return-1===e.injectorIndex||e.parent&&e.parent.injectorIndex===e.injectorIndex||null==t[e.injectorIndex+8]?-1:e.injectorIndex}function rr(e,t){if(e.parent&&-1!==e.parent.injectorIndex)return e.parent.injectorIndex;let i=t[6],r=1;for(;i&&-1===i.injectorIndex;)i=(t=t[15])?t[6]:null,r++;return i?i.injectorIndex|r<<16:-1}function nr(e,t,i){!function(e,t,i){let r;"string"==typeof i?r=i.charCodeAt(0)||0:i.hasOwnProperty(je)&&(r=i[je]),null==r&&(r=i[je]=Ji++);const n=255&r,s=1<0?255&t:t}(i);if("function"==typeof n){yi(t,e);try{const e=n();if(null!=e||r&le.Optional)return e;throw new Error(`No provider for ${Ki(i)}!`)}finally{ki()}}else if("number"==typeof n){if(-1===n)return new dr(e,t);let s=null,o=ir(e,t),a=-1,l=r&le.Host?t[16][6]:null;for((-1===o||r&le.SkipSelf)&&(a=-1===o?rr(e,t):t[o+8],ur(r,!1)?(s=t[1],o=qi(a),t=Wi(a,t)):o=-1);-1!==o;){a=t[o+8];const e=t[1];if(hr(n,o,e.data)){const e=ar(o,t,i,s,r,l);if(e!==or)return e}ur(r,t[1].data[o+8]===l)&&hr(n,o,t)?(s=e,o=qi(a),t=Wi(a,t)):o=-1}}}if(r&le.Optional&&void 0===n&&(n=null),0==(r&(le.Self|le.Host))){const e=t[9],s=Ke(void 0);try{return e?e.get(i,n,r&le.Optional):Xe(i,n,r&le.Optional)}finally{Ke(s)}}if(r&le.Optional)return n;throw new Error(`NodeInjector: NOT_FOUND [${Ki(i)}]`)}const or={};function ar(e,t,i,r,n,s){const o=t[1],a=o.data[e+8],l=lr(a,o,i,null==r?Ot(a)&&Xi:r!=o&&3===a.type,n&le.Host&&s===a);return null!==l?cr(t,o,l,a):or}function lr(e,t,i,r,n){const s=e.providerIndexes,o=t.data,a=1048575&s,l=e.directiveStart,c=s>>20,h=n?a+c:e.directiveEnd;for(let u=r?a:a+c;u=l&&e.type===i)return u}if(n){const e=o[l];if(e&&Rt(e)&&e.type===i)return l}return null}function cr(e,t,i,r){let n=e[i];const s=t.data;if(n instanceof Fi){const o=n;if(o.resolving)throw new Error("Circular dep for "+Ki(s[i]));const a=Qi(o.canSeeViewProviders);let l;o.resolving=!0,o.injectImpl&&(l=Ke(o.injectImpl)),yi(e,r);try{n=e[i]=o.factory(void 0,s,e,r),t.firstCreatePass&&i>=r.directiveStart&&function(e,t,i){const{ngOnChanges:r,ngOnInit:n,ngDoCheck:s}=t.type.prototype;if(r){const r=It(t);(i.preOrderHooks||(i.preOrderHooks=[])).push(e,r),(i.preOrderCheckHooks||(i.preOrderCheckHooks=[])).push(e,r)}n&&(i.preOrderHooks||(i.preOrderHooks=[])).push(0-e,n),s&&((i.preOrderHooks||(i.preOrderHooks=[])).push(e,s),(i.preOrderCheckHooks||(i.preOrderCheckHooks=[])).push(e,s))}(i,s[i],t)}finally{o.injectImpl&&Ke(l),Qi(a),o.resolving=!1,ki()}}return n}function hr(e,t,i){const r=64&e,n=32&e;let s;return s=128&e?r?n?i[t+7]:i[t+6]:n?i[t+5]:i[t+4]:r?n?i[t+3]:i[t+2]:n?i[t+1]:i[t],!!(s&1<{const e=fr(xe(t));return e?e():null};let i=St(t);if(null===i){const e=_e(t);i=e&&e.factory}return i||null}function pr(e){return ie(()=>{const t=e.prototype.constructor,i=t[Ne]||fr(t),r=Object.prototype;let n=Object.getPrototypeOf(e.prototype).constructor;for(;n&&n!==r;){const e=n[Ne]||fr(n);if(e&&e!==i)return e;n=Object.getPrototypeOf(n)}return e=>new e})}function _r(e){return e.ngDebugContext}function mr(e){return e.ngOriginalError}function gr(e,...t){e.error(...t)}class vr{constructor(){this._console=console}handleError(e){const t=this._findOriginalError(e),i=this._findContext(e),r=function(e){return e.ngErrorLogger||gr}(e);r(this._console,"ERROR",e),t&&r(this._console,"ORIGINAL ERROR",t),i&&r(this._console,"ERROR CONTEXT",i)}_findContext(e){return e?_r(e)?_r(e):this._findContext(mr(e)):null}_findOriginalError(e){let t=mr(e);for(;t&&mr(t);)t=mr(t);return t}}class yr{constructor(e){this.changingThisBreaksApplicationSecurity=e}toString(){return"SafeValue must use [property]=binding: "+this.changingThisBreaksApplicationSecurity+" (see http://g.co/ng/security#xss)"}}function br(e){return e instanceof yr?e.changingThisBreaksApplicationSecurity:e}function wr(e,t){const i=function(e){return e instanceof yr&&e.getTypeName()||null}(e);if(null!=i&&i!==t){if("ResourceURL"===i&&"URL"===t)return!0;throw new Error(`Required a safe ${t}, got a ${i} (see http://g.co/ng/security#xss)`)}return i===t}let Cr=!0,Sr=!1;function kr(){return Sr=!0,Cr}class xr{getInertBodyElement(e){e=""+e;try{const t=(new window.DOMParser).parseFromString(e,"text/html").body;return t.removeChild(t.firstChild),t}catch(t){return null}}}class Er{constructor(e){if(this.defaultDoc=e,this.inertDocument=this.defaultDoc.implementation.createHTMLDocument("sanitization-inert"),null==this.inertDocument.body){const e=this.inertDocument.createElement("html");this.inertDocument.appendChild(e);const t=this.inertDocument.createElement("body");e.appendChild(t)}}getInertBodyElement(e){const t=this.inertDocument.createElement("template");if("content"in t)return t.innerHTML=e,t;const i=this.inertDocument.createElement("body");return i.innerHTML=e,this.defaultDoc.documentMode&&this.stripCustomNsAttrs(i),i}stripCustomNsAttrs(e){const t=e.attributes;for(let r=t.length-1;0Tr(e.trim())).join(", ")),this.buf.push(" ",t,'="',zr(o),'"')}var r;return this.buf.push(">"),!0}endElement(e){const t=e.nodeName.toLowerCase();Fr.hasOwnProperty(t)&&!Pr.hasOwnProperty(t)&&(this.buf.push(""))}chars(e){this.buf.push(zr(e))}checkClobberedElement(e,t){if(t&&(e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_CONTAINED_BY)===Node.DOCUMENT_POSITION_CONTAINED_BY)throw new Error("Failed to sanitize html because the element is clobbered: "+e.outerHTML);return t}}const Vr=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,qr=/([^\#-~ |!])/g;function zr(e){return e.replace(/&/g,"&").replace(Vr,(function(e){return"&#"+(1024*(e.charCodeAt(0)-55296)+(e.charCodeAt(1)-56320)+65536)+";"})).replace(qr,(function(e){return"&#"+e.charCodeAt(0)+";"})).replace(//g,">")}let Wr;function $r(e){return"content"in e&&function(e){return e.nodeType===Node.ELEMENT_NODE&&"TEMPLATE"===e.nodeName}(e)?e.content:null}var Kr=function(e){return e[e.NONE=0]="NONE",e[e.HTML=1]="HTML",e[e.STYLE=2]="STYLE",e[e.SCRIPT=3]="SCRIPT",e[e.URL=4]="URL",e[e.RESOURCE_URL=5]="RESOURCE_URL",e}({});function Gr(e){const t=Yr();return t?t.sanitize(Kr.HTML,e)||"":wr(e,"HTML")?br(e):function(e,t){let i=null;try{Wr=Wr||function(e){return function(){try{return!!(new window.DOMParser).parseFromString("","text/html")}catch(e){return!1}}()?new xr:new Er(e)}(e);let r=t?String(t):"";i=Wr.getInertBodyElement(r);let n=5,s=r;do{if(0===n)throw new Error("Failed to sanitize html because the input is unstable");n--,r=s,s=i.innerHTML,i=Wr.getInertBodyElement(r)}while(r!==s);const o=new Ur,a=o.sanitizeChildren($r(i)||i);return kr()&&o.sanitizedSomething&&console.warn("WARNING: sanitizing HTML stripped some content, see http://g.co/ng/security#xss"),a}finally{if(i){const e=$r(i)||i;for(;e.firstChild;)e.removeChild(e.firstChild)}}}(Bt(),$i(e))}function Zr(e){const t=Yr();return t?t.sanitize(Kr.URL,e)||"":wr(e,"URL")?br(e):Tr($i(e))}function Yr(){const e=ii();return e&&e[12]}function Xr(e,t){e.__ngContext__=t}function Qr(e){throw new Error("Multiple components match node with tagname "+e.tagName)}function Jr(){throw new Error("Cannot mix multi providers and regular providers")}function en(e,t,i){let r=e.length;for(;;){const n=e.indexOf(t,i);if(-1===n)return n;if(0===n||e.charCodeAt(n-1)<=32){const i=t.length;if(n+i===r||e.charCodeAt(n+i)<=32)return n}i=n+1}}function tn(e,t,i){let r=0;for(;rs?"":n[h+1].toLowerCase();const t=8&r?e:null;if(t&&-1!==en(t,c,0)||2&r&&c!==e){if(on(r))return!1;o=!0}}}}else{if(!o&&!on(r)&&!on(l))return!1;if(o&&on(l))continue;o=!1,r=l|1&r}}return on(r)||o}function on(e){return 0==(1&e)}function an(e,t,i,r){if(null===t)return-1;let n=0;if(r||!i){let i=!1;for(;n-1)for(i++;i0?'="'+t+'"':"")+"]"}else 8&r?n+="."+o:4&r&&(n+=" "+o);else""===n||on(o)||(t+=hn(s,n),n=""),r=o,s=s||!on(r);i++}return""!==n&&(t+=hn(s,n)),t}const dn={};function fn(e){const t=e[3];return Et(t)?t[3]:t}function pn(e){return mn(e[13])}function _n(e){return mn(e[4])}function mn(e){for(;null!==e&&!Et(e);)e=e[4];return e}function gn(e){vn(ri(),ii(),Ei()+e,ci())}function vn(e,t,i,r){if(!r)if(3==(3&t[2])){const r=e.preOrderCheckHooks;null!==r&&Li(t,r,i)}else{const r=e.preOrderHooks;null!==r&&Pi(t,r,0,i)}Ai(i)}function yn(e,t){return e<<17|t<<2}function bn(e){return e>>17&32767}function wn(e){return 2|e}function Cn(e){return(131068&e)>>2}function Sn(e,t){return-131069&e|t<<2}function kn(e){return 1|e}function xn(e,t){const i=e.contentQueries;if(null!==i)for(let r=0;r20&&vn(e,t,0,ci()),i(r,n)}finally{Ai(s)}}function Dn(e,t,i){if(At(t)){const r=t.directiveEnd;for(let n=t.directiveStart;n0&&function e(t){for(let r=pn(t);null!==r;r=_n(r))for(let t=10;t0&&e(i)}const i=t[1].components;if(null!==i)for(let r=0;r0&&e(n)}}(i)}}function is(e,t){const i=Kt(t,e),r=i[1];!function(e,t){for(let i=t.length;iPromise.resolve(null))();function cs(e){return e[7]||(e[7]=[])}function hs(e,t,i){return(null===e||Rt(e))&&(i=function(e){for(;Array.isArray(e);){if("object"==typeof e[1])return e;e=e[0]}return null}(i[t.index])),i[11]}function us(e,t){const i=e[9],r=i?i.get(vr,null):null;r&&r.handleError(t)}function ds(e,t,i,r,n){for(let s=0;s0&&(e[i-1][4]=r[4]);const s=nt(e,10+t);ms(r[1],r,!1,null);const o=s[19];null!==o&&o.detachView(s[1]),r[3]=null,r[4]=null,r[2]&=-129}return r}function ys(e,t){if(!(256&t[2])){const i=t[11];Ht(i)&&i.destroyNode&&Ls(e,t,i,3,null,null),function(e){let t=e[13];if(!t)return ws(e[1],e);for(;t;){let i=null;if(xt(t))i=t[13];else{const e=t[10];e&&(i=e)}if(!i){for(;t&&!t[4]&&t!==e;)xt(t)&&ws(t[1],t),t=bs(t,e);null===t&&(t=e),xt(t)&&ws(t[1],t),i=t&&t[4]}t=i}}(t)}}function bs(e,t){let i;return xt(e)&&(i=e[6])&&2===i.type?fs(i,e):e[3]===t?null:e[3]}function ws(e,t){if(!(256&t[2])){t[2]&=-129,t[2]|=256,function(e,t){let i;if(null!=e&&null!=(i=e.destroyHooks))for(let r=0;r=0?e[a]():e[-a].unsubscribe(),r+=2}else i[r].call(e[i[r+1]]);t[7]=null}}(e,t);const i=t[6];i&&3===i.type&&Ht(t[11])&&t[11].destroy();const r=t[17];if(null!==r&&Et(t[3])){r!==t[3]&&gs(r,t);const i=t[19];null!==i&&i.detachView(e)}}}function Cs(e,t,i){let r=t.parent;for(;null!=r&&(4===r.type||5===r.type);)r=(t=r).parent;if(null==r){const e=i[6];return 2===e.type?ps(e,i):i[0]}if(t&&5===t.type&&4&t.flags)return zt(t,i).parentNode;if(2&r.flags){const t=e.data,i=t[t[r.index].directiveStart].encapsulation;if(i!==ht.ShadowDom&&i!==ht.Native)return null}return zt(r,i)}function Ss(e,t,i,r){Ht(e)?e.insertBefore(t,i,r):t.insertBefore(i,r,!0)}function ks(e,t,i){Ht(e)?e.appendChild(t,i):t.appendChild(i)}function xs(e,t,i,r){null!==r?Ss(e,t,i,r):ks(e,t,i)}function Es(e,t){return Ht(e)?e.parentNode(t):t.parentNode}function As(e,t){if(2===e.type){const i=fs(e,t);return null===i?null:Ts(i.indexOf(t,10)-10,i)}return 4===e.type||5===e.type?zt(e,t):null}function Os(e,t,i,r){const n=Cs(e,r,t);if(null!=n){const e=t[11],s=As(r.parent||t[6],t);if(Array.isArray(i))for(let t=0;t-1&&this._viewContainerRef.detach(e),this._viewContainerRef=null}ys(this._lView[1],this._lView)}onDestroy(e){jn(this._lView[1],this._lView,null,e)}markForCheck(){ns(this._cdRefInjectingView||this._lView)}detach(){this._lView[2]&=-129}reattach(){this._lView[2]|=128}detectChanges(){ss(this._lView[1],this._lView,this.context)}checkNoChanges(){!function(e,t,i){hi(!0);try{ss(e,t,i)}finally{hi(!1)}}(this._lView[1],this._lView,this.context)}attachToViewContainerRef(e){if(this._appRef)throw new Error("This view is already attached directly to the ApplicationRef!");this._viewContainerRef=e}detachFromAppRef(){var e;this._appRef=null,Ls(this._lView[1],e=this._lView,e[11],2,null,null)}attachToAppRef(e){if(this._viewContainerRef)throw new Error("This view is already attached to a ViewContainer!");this._appRef=e}}class Fs extends Ms{constructor(e){super(e),this._view=e}detectChanges(){os(this._view)}checkNoChanges(){!function(e){hi(!0);try{os(e)}finally{hi(!1)}}(this._view)}get context(){return null}}let Ns,js,Bs;function Hs(e,t,i){return Ns||(Ns=class extends e{}),new Ns(zt(t,i))}function Us(e,t,i,r){return js||(js=class extends e{constructor(e,t,i){super(),this._declarationView=e,this._declarationTContainer=t,this.elementRef=i}createEmbeddedView(e){const t=this._declarationTContainer.tViews,i=An(this._declarationView,t,e,16,null,t.node);i[17]=this._declarationView[this._declarationTContainer.index];const r=this._declarationView[19];return null!==r&&(i[19]=r.createEmbeddedView(t)),Tn(t,i,e),new Ms(i)}}),0===i.type?new js(r,i,Hs(t,i,r)):null}function Vs(e,t,i,r){let n;Bs||(Bs=class extends e{constructor(e,t,i){super(),this._lContainer=e,this._hostTNode=t,this._hostView=i}get element(){return Hs(t,this._hostTNode,this._hostView)}get injector(){return new dr(this._hostTNode,this._hostView)}get parentInjector(){const e=rr(this._hostTNode,this._hostView),t=Wi(e,this._hostView),i=function(e,t,i){if(i.parent&&-1!==i.parent.injectorIndex){const e=i.parent.injectorIndex;let t=i.parent;for(;null!=t.parent&&e==t.parent.injectorIndex;)t=t.parent;return t}let r=zi(e),n=t,s=t[6];for(;r>1;)n=n[15],s=n[6],r--;return s}(e,this._hostView,this._hostTNode);return Vi(e)&&null!=i?new dr(i,t):new dr(null,this._hostView)}clear(){for(;this.length>0;)this.remove(this.length-1)}get(e){return null!==this._lContainer[8]&&this._lContainer[8][e]||null}get length(){return this._lContainer.length-10}createEmbeddedView(e,t,i){const r=e.createEmbeddedView(t||{});return this.insert(r,i),r}createComponent(e,t,i,r,n){const s=i||this.parentInjector;if(!n&&null==e.ngModule&&s){const e=s.get(et,null);e&&(n=e)}const o=e.create(s,r,void 0,n);return this.insert(o.hostView,t),o}insert(e,t){const i=e._lView,r=i[1];if(e.destroyed)throw new Error("Cannot insert a destroyed View in a ViewContainer!");if(this.allocateContainerIfNeeded(),Et(i[3])){const t=this.indexOf(e);if(-1!==t)this.detach(t);else{const t=i[3],r=new Bs(t,t[6],t[3]);r.detach(r.indexOf(e))}}const n=this._adjustIndex(t);return function(e,t,i,r){const n=10+r,s=i.length;r>0&&(i[n-1][4]=t),r{class e{}return e.__NG_ELEMENT_ID__=()=>Ws(),e})();const Ws=qs,$s=Function,Ks=new Be("Set Injector scope."),Gs={},Zs={},Ys=[];let Xs=void 0;function Qs(){return void 0===Xs&&(Xs=new Je),Xs}function Js(e,t=null,i=null,r){return new eo(e,i,t||Qs(),r)}class eo{constructor(e,t,i,r=null){this.parent=i,this.records=new Map,this.injectorDefTypes=new Set,this.onDestroy=new Set,this._destroyed=!1;const n=[];t&&it(t,i=>this.processProvider(i,e,t)),it([e],e=>this.processInjectorType(e,[],n)),this.records.set(He,ro(void 0,this));const s=this.records.get(Ks);this.scope=null!=s?s.value:null,this.source=r||("object"==typeof e?null:we(e))}get destroyed(){return this._destroyed}destroy(){this.assertNotDestroyed(),this._destroyed=!0;try{this.onDestroy.forEach(e=>e.ngOnDestroy())}finally{this.records.clear(),this.onDestroy.clear(),this.injectorDefTypes.clear()}}get(e,t=Ue,i=le.Default){this.assertNotDestroyed();const r=$e(this);try{if(!(i&le.SkipSelf)){let t=this.records.get(e);if(void 0===t){const i=("function"==typeof(n=e)||"object"==typeof n&&n instanceof Be)&&fe(e);t=i&&this.injectableDefInScope(i)?ro(to(e),Gs):null,this.records.set(e,t)}if(null!=t)return this.hydrate(e,t)}return(i&le.Self?Qs():this.parent).get(e,t=i&le.Optional&&t===Ue?null:t)}catch(s){if("NullInjectorError"===s.name){if((s.ngTempTokenPath=s.ngTempTokenPath||[]).unshift(we(e)),r)throw s;return function(e,t,i,r){const n=e.ngTempTokenPath;throw t.__source&&n.unshift(t.__source),e.message=function(e,t,i,r=null){e=e&&"\n"===e.charAt(0)&&"\u0275"==e.charAt(1)?e.substr(2):e;let n=we(t);if(Array.isArray(t))n=t.map(we).join(" -> ");else if("object"==typeof t){let e=[];for(let i in t)if(t.hasOwnProperty(i)){let r=t[i];e.push(i+":"+("string"==typeof r?JSON.stringify(r):we(r)))}n=`{${e.join(", ")}}`}return`${i}${r?"("+r+")":""}[${n}]: ${e.replace(Ve,"\n ")}`}("\n"+e.message,n,i,r),e.ngTokenPath=n,e.ngTempTokenPath=null,e}(s,e,"R3InjectorError",this.source)}throw s}finally{$e(r)}var n}_resolveInjectorDefTypes(){this.injectorDefTypes.forEach(e=>this.get(e))}toString(){const e=[];return this.records.forEach((t,i)=>e.push(we(i))),`R3Injector[${e.join(", ")}]`}assertNotDestroyed(){if(this._destroyed)throw new Error("Injector has already been destroyed.")}processInjectorType(e,t,i){if(!(e=xe(e)))return!1;let r=_e(e);const n=null==r&&e.ngModule||void 0,s=void 0===n?e:n,o=-1!==i.indexOf(s);if(void 0!==n&&(r=_e(n)),null==r)return!1;if(null!=r.imports&&!o){let e;i.push(s);try{it(r.imports,r=>{this.processInjectorType(r,t,i)&&(void 0===e&&(e=[]),e.push(r))})}finally{}if(void 0!==e)for(let t=0;tthis.processProvider(e,i,r||Ys))}}this.injectorDefTypes.add(s),this.records.set(s,ro(r.factory,Gs));const a=r.providers;if(null!=a&&!o){const t=e;it(a,e=>this.processProvider(e,t,a))}return void 0!==n&&void 0!==e.providers}processProvider(e,t,i){let r=so(e=xe(e))?e:xe(e&&e.provide);const n=function(e,t,i){return no(e)?ro(void 0,e.useValue):ro(io(e,t,i),Gs)}(e,t,i);if(so(e)||!0!==e.multi){const e=this.records.get(r);e&&void 0!==e.multi&&Jr()}else{let t=this.records.get(r);t?void 0===t.multi&&Jr():(t=ro(void 0,Gs,!0),t.factory=()=>Qe(t.multi),this.records.set(r,t)),r=e,t.multi.push(e)}this.records.set(r,n)}hydrate(e,t){var i;return t.value===Zs?function(e){throw new Error("Cannot instantiate cyclic dependency! "+e)}(we(e)):t.value===Gs&&(t.value=Zs,t.value=t.factory()),"object"==typeof t.value&&t.value&&null!==(i=t.value)&&"object"==typeof i&&"function"==typeof i.ngOnDestroy&&this.onDestroy.add(t.value),t.value}injectableDefInScope(e){return!!e.providedIn&&("string"==typeof e.providedIn?"any"===e.providedIn||e.providedIn===this.scope:this.injectorDefTypes.has(e.providedIn))}}function to(e){const t=fe(e),i=null!==t?t.factory:St(e);if(null!==i)return i;const r=_e(e);if(null!==r)return r.factory;if(e instanceof Be)throw new Error(`Token ${we(e)} is missing a \u0275prov definition.`);if(e instanceof Function)return function(e){const t=e.length;if(t>0){const i=st(t,"?");throw new Error(`Can't resolve all parameters for ${we(e)}: (${i.join(", ")}).`)}const i=function(e){const t=e&&(e[me]||e[ye]||e[ve]&&e[ve]());if(t){const i=function(e){if(e.hasOwnProperty("name"))return e.name;const t=(""+e).match(/^function\s*([^\s(]+)/);return null===t?"":t[1]}(e);return console.warn(`DEPRECATED: DI is instantiating a token "${i}" that inherits its @Injectable decorator but does not provide one itself.\nThis will become an error in a future version of Angular. Please add @Injectable() to the "${i}" class.`),t}return null}(e);return null!==i?()=>i.factory(e):()=>new e}(e);throw new Error("unreachable")}function io(e,t,i){let r=void 0;if(so(e)){const t=xe(e);return St(t)||to(t)}if(no(e))r=()=>xe(e.useValue);else if((n=e)&&n.useFactory)r=()=>e.useFactory(...Qe(e.deps||[]));else if(function(e){return!(!e||!e.useExisting)}(e))r=()=>Ze(xe(e.useExisting));else{const n=xe(e&&(e.useClass||e.provide));if(n||function(e,t,i){let r="";throw e&&t&&(r=` - only instances of Provider and Type are allowed, got: [${t.map(e=>e==i?"?"+i+"?":"...").join(", ")}]`),new Error(`Invalid provider for the NgModule '${we(e)}'`+r)}(t,i,e),!function(e){return!!e.deps}(e))return St(n)||to(n);r=()=>new n(...Qe(e.deps))}var n;return r}function ro(e,t,i=!1){return{factory:e,value:t,multi:i?[]:void 0}}function no(e){return null!==e&&"object"==typeof e&&qe in e}function so(e){return"function"==typeof e}const oo=function(e,t,i){return function(e,t=null,i=null,r){const n=Js(e,t,i,r);return n._resolveInjectorDefTypes(),n}({name:i},t,e,i)};let ao=(()=>{class e{static create(e,t){return Array.isArray(e)?oo(e,t,""):oo(e.providers,e.parent,e.name||"")}}return e.THROW_IF_NOT_FOUND=Ue,e.NULL=new Je,e.\u0275prov=ue({token:e,providedIn:"any",factory:()=>Ze(He)}),e.__NG_ELEMENT_ID__=-1,e})();const lo=new Be("AnalyzeForEntryComponents");function co(e,t,i){let r=i?e.styles:null,n=i?e.classes:null,s=0;if(null!==t)for(let o=0;oa(Vt(e[r.index])).target:r.index;if(Ht(i)){let o=null;if(!a&&l&&(o=function(e,t,i,r){const n=e.cleanup;if(null!=n)for(let s=0;si?e[i]:null}"string"==typeof e&&(s+=2)}return null}(e,t,n,r.index)),null!==o)(o.__ngLastListenerFn__||o).__ngNextListenerFn__=s,o.__ngLastListenerFn__=s,u=!1;else{s=jo(r,t,s,!1);const e=i.listen(f.name||p,n,s);h.push(s,e),c&&c.push(n,m,_,_+1)}}else s=jo(r,t,s,!0),p.addEventListener(n,s,o),h.push(s),c&&c.push(n,m,_,o)}const d=r.outputs;let f;if(u&&null!==d&&(f=d[n])){const e=f.length;if(e)for(let i=0;i0;)t=t[15],e--;return t}(e,ei.lFrame.contextLView))[8]}(e)}function Ho(e,t){let i=null;const r=function(e){const t=e.attrs;if(null!=t){const e=t.indexOf(5);if(0==(1&e))return t[e+1]}return null}(e);for(let n=0;n=0}const $o={textEnd:0,key:0,keyEnd:0,value:0,valueEnd:0};function Ko(e){return e.substring($o.key,$o.keyEnd)}function Go(e,t){const i=$o.textEnd;return i===t?-1:(t=$o.keyEnd=function(e,t,i){for(;t32;)t++;return t}(e,$o.key=t,i),Zo(e,t,i))}function Zo(e,t,i){for(;t=0;i=Go(t,i))ot(e,Ko(t),!0)}function ea(e,t,i,r){const n=ii(),s=ri(),o=fi(2);s.firstUpdatePass&&ra(s,e,o,r),t!==dn&&go(n,o,t)&&oa(s,s.data[Ei()+20],n,n[11],e,n[o+1]=function(e,t){return null==e||("string"==typeof t?e+=t:"object"==typeof e&&(e=we(br(e)))),e}(t,i),r,o)}function ta(e,t,i,r){const n=ri(),s=fi(2);n.firstUpdatePass&&ra(n,null,s,r);const o=ii();if(i!==dn&&go(o,s,i)){const a=n.data[Ei()+20];if(ca(a,r)&&!ia(n,s)){let e=r?a.classesWithoutHost:a.stylesWithoutHost;null!==e&&(i=Ce(e,i||"")),xo(n,a,o,i,r)}else!function(e,t,i,r,n,s,o,a){n===dn&&(n=qo);let l=0,c=0,h=0=e.expandoStartIndex}function ra(e,t,i,r){const n=e.data;if(null===n[i+1]){const s=n[Ei()+20],o=ia(e,i);ca(s,r)&&null===t&&!o&&(t=!1),t=function(e,t,i,r){const n=mi(e);let s=r?t.residualClasses:t.residualStyles;if(null===n)0===(r?t.classBindings:t.styleBindings)&&(i=sa(i=na(null,e,t,i,r),t.attrs,r),s=null);else{const o=t.directiveStylingLast;if(-1===o||e[o]!==n)if(i=na(n,e,t,i,r),null===s){let i=function(e,t,i){const r=i?t.classBindings:t.styleBindings;if(0!==Cn(r))return e[bn(r)]}(e,t,r);void 0!==i&&Array.isArray(i)&&(i=na(null,e,t,i[1],r),i=sa(i,t.attrs,r),function(e,t,i,r){e[bn(i?t.classBindings:t.styleBindings)]=r}(e,t,r,i))}else s=function(e,t,i){let r=void 0;const n=t.directiveEnd;for(let s=1+t.directiveStylingLast;s0)&&(h=!0)}else c=i;if(n)if(0!==l){const t=bn(e[a+1]);e[r+1]=yn(t,a),0!==t&&(e[t+1]=Sn(e[t+1],r)),e[a+1]=131071&e[a+1]|r<<17}else e[r+1]=yn(a,0),0!==a&&(e[a+1]=Sn(e[a+1],r)),a=r;else e[r+1]=yn(l,0),0===a?a=r:e[l+1]=Sn(e[l+1],r),l=r;h&&(e[r+1]=wn(e[r+1])),zo(e,c,r,!0),zo(e,c,r,!1),function(e,t,i,r,n){const s=n?e.residualClasses:e.residualStyles;null!=s&&"string"==typeof t&<(s,t)>=0&&(i[r+1]=kn(i[r+1]))}(t,c,e,r,s),o=yn(a,l),s?t.classBindings=o:t.styleBindings=o}(n,s,t,i,o,r)}}function na(e,t,i,r,n){let s=null;const o=i.directiveEnd;let a=i.directiveStylingLast;for(-1===a?a=i.directiveStart:a++;a0;){const t=e[n],s=Array.isArray(t),l=s?t[1]:t,c=null===l;let h=i[n+1];h===dn&&(h=c?qo:void 0);let u=c?at(h,r):l===r?h:void 0;if(s&&!la(u)&&(u=at(t,r)),la(u)&&(a=u,o))return a;const d=e[n+1];n=o?bn(d):Cn(d)}if(null!==t){let e=s?t.residualClasses:t.residualStyles;null!=e&&(a=at(e,r))}return a}function la(e){return void 0!==e}function ca(e,t){return 0!=(e.flags&(t?16:32))}function ha(e,t=""){const i=ii(),r=ri(),n=e+20,s=r.firstCreatePass?On(r,i[6],e,3,null,null):r.data[n],o=i[n]=function(e,t){return Ht(t)?t.createText(e):t.createTextNode(e)}(t,i[11]);Os(r,i,o,s),oi(s,!1)}function ua(e){return da("",e,""),ua}function da(e,t,i){const r=ii(),n=yo(r,e,t,i);return n!==dn&&function(e,t,i){const r=qt(t,e),n=e[11];Ht(n)?n.setValue(r,i):r.textContent=i}(r,Ei(),n),da}function fa(e,t,i){const r=ii();return go(r,di(),t)&&Un(ri(),Oi(),r,e,t,r[11],i,!0),fa}function pa(e,t,i){const r=ii();if(go(r,di(),t)){const n=ri(),s=Oi();Un(n,s,r,e,t,hs(mi(n.data),s,r),i,!0)}return pa}function _a(e,t){const i=Gt(e)[1],r=i.data.length-1;Ri(i,{directiveStart:r,directiveEnd:r+1})}function ma(e){let t=Object.getPrototypeOf(e.type.prototype).constructor,i=!0;const r=[e];for(;t;){let n=void 0;if(Rt(e))n=t.\u0275cmp||t.\u0275dir;else{if(t.\u0275cmp)throw new Error("Directives cannot inherit Components");n=t.\u0275dir}if(n){if(i){r.push(n);const t=e;t.inputs=ga(e.inputs),t.declaredInputs=ga(e.declaredInputs),t.outputs=ga(e.outputs);const i=n.hostBindings;i&&ba(e,i);const s=n.viewQuery,o=n.contentQueries;if(s&&va(e,s),o&&ya(e,o),he(e.inputs,n.inputs),he(e.declaredInputs,n.declaredInputs),he(e.outputs,n.outputs),Rt(n)&&n.data.animation){const t=e.data;t.animation=(t.animation||[]).concat(n.data.animation)}}const t=n.features;if(t)for(let r=0;r=0;r--){const n=e[r];n.hostVars=t+=n.hostVars,n.hostAttrs=Hi(n.hostAttrs,i=Hi(i,n.hostAttrs))}}(r)}function ga(e){return e===ut?{}:e===dt?[]:e}function va(e,t){const i=e.viewQuery;e.viewQuery=i?(e,r)=>{t(e,r),i(e,r)}:t}function ya(e,t){const i=e.contentQueries;e.contentQueries=i?(e,r,n)=>{t(e,r,n),i(e,r,n)}:t}function ba(e,t){const i=e.hostBindings;e.hostBindings=i?(e,r)=>{t(e,r),i(e,r)}:t}function wa(e,t,i,r,n){if(e=xe(e),Array.isArray(e))for(let s=0;s>20;if(so(e)||!e.multi){const r=new Fi(l,n,Co),f=ka(a,t,n?h:h+d,u);-1===f?(nr(er(c,o),s,a),Ca(s,e,t.length),t.push(a),c.directiveStart++,c.directiveEnd++,n&&(c.providerIndexes+=1048576),i.push(r),o.push(r)):(i[f]=r,o[f]=r)}else{const f=ka(a,t,h+d,u),p=ka(a,t,h,h+d),_=f>=0&&i[f],m=p>=0&&i[p];if(n&&!m||!n&&!_){nr(er(c,o),s,a);const h=function(e,t,i,r,n){const s=new Fi(e,i,Co);return s.multi=[],s.index=t,s.componentProviders=0,Sa(s,n,r&&!i),s}(n?Ea:xa,i.length,n,r,l);!n&&m&&(i[p].providerFactory=h),Ca(s,e,t.length,0),t.push(a),c.directiveStart++,c.directiveEnd++,n&&(c.providerIndexes+=1048576),i.push(h),o.push(h)}else Ca(s,e,f>-1?f:p,Sa(i[n?p:f],l,!n&&r));!n&&r&&m&&i[p].componentProviders++}}}function Ca(e,t,i,r){const n=so(t);if(n||t.useClass){const s=(t.useClass||t).prototype.ngOnDestroy;if(s){const o=e.destroyHooks||(e.destroyHooks=[]);if(!n&&t.multi){const e=o.indexOf(i);-1===e?o.push(i,[r,s]):o[e+1].push(r,s)}else o.push(i,s)}}}function Sa(e,t,i){return i&&e.componentProviders++,e.multi.push(t)-1}function ka(e,t,i,r){for(let n=i;n{i.providersResolver=(i,r)=>function(e,t,i){const r=ri();if(r.firstCreatePass){const n=Rt(e);wa(i,r.data,r.blueprint,n,!0),wa(t,r.data,r.blueprint,n,!1)}}(i,r?r(e):e,t)}}class Ta{}class Ra{resolveComponentFactory(e){throw function(e){const t=Error(`No component factory found for ${we(e)}. Did you add it to @NgModule.entryComponents?`);return t.ngComponent=e,t}(e)}}let La=(()=>{class e{}return e.NULL=new Ra,e})(),Pa=(()=>{class e{constructor(e){this.nativeElement=e}}return e.__NG_ELEMENT_ID__=()=>Da(e),e})();const Da=function(e){return Hs(e,si(),ii())};class Ia{}var Ma=function(e){return e[e.Important=1]="Important",e[e.DashCase=2]="DashCase",e}({});let Fa=(()=>{class e{}return e.__NG_ELEMENT_ID__=()=>Na(),e})();const Na=function(){const e=ii(),t=Kt(si().index,e);return function(e){const t=e[11];if(Ht(t))return t;throw new Error("Cannot inject Renderer2 when the application uses Renderer3!")}(xt(t)?t:e)};let ja=(()=>{class e{}return e.\u0275prov=ue({token:e,providedIn:"root",factory:()=>null}),e})();class Ba{constructor(e){this.full=e,this.major=e.split(".")[0],this.minor=e.split(".")[1],this.patch=e.split(".").slice(2).join(".")}}const Ha=new Ba("10.0.14");class Ua{constructor(){}supports(e){return po(e)}create(e){return new qa(e)}}const Va=(e,t)=>t;class qa{constructor(e){this.length=0,this._linkedRecords=null,this._unlinkedRecords=null,this._previousItHead=null,this._itHead=null,this._itTail=null,this._additionsHead=null,this._additionsTail=null,this._movesHead=null,this._movesTail=null,this._removalsHead=null,this._removalsTail=null,this._identityChangesHead=null,this._identityChangesTail=null,this._trackByFn=e||Va}forEachItem(e){let t;for(t=this._itHead;null!==t;t=t._next)e(t)}forEachOperation(e){let t=this._itHead,i=this._removalsHead,r=0,n=null;for(;t||i;){const s=!i||t&&t.currentIndex{r=this._trackByFn(t,e),null!==n&&Object.is(n.trackById,r)?(s&&(n=this._verifyReinsertion(n,e,r,t)),Object.is(n.item,e)||this._addIdentityChange(n,e)):(n=this._mismatch(n,e,r,t),s=!0),n=n._next,t++}),this.length=t;return this._truncate(n),this.collection=e,this.isDirty}get isDirty(){return null!==this._additionsHead||null!==this._movesHead||null!==this._removalsHead||null!==this._identityChangesHead}_reset(){if(this.isDirty){let e,t;for(e=this._previousItHead=this._itHead;null!==e;e=e._next)e._nextPrevious=e._next;for(e=this._additionsHead;null!==e;e=e._nextAdded)e.previousIndex=e.currentIndex;for(this._additionsHead=this._additionsTail=null,e=this._movesHead;null!==e;e=t)e.previousIndex=e.currentIndex,t=e._nextMoved;this._movesHead=this._movesTail=null,this._removalsHead=this._removalsTail=null,this._identityChangesHead=this._identityChangesTail=null}}_mismatch(e,t,i,r){let n;return null===e?n=this._itTail:(n=e._prev,this._remove(e)),null!==(e=null===this._linkedRecords?null:this._linkedRecords.get(i,r))?(Object.is(e.item,t)||this._addIdentityChange(e,t),this._moveAfter(e,n,r)):null!==(e=null===this._unlinkedRecords?null:this._unlinkedRecords.get(i,null))?(Object.is(e.item,t)||this._addIdentityChange(e,t),this._reinsertAfter(e,n,r)):e=this._addAfter(new za(t,i),n,r),e}_verifyReinsertion(e,t,i,r){let n=null===this._unlinkedRecords?null:this._unlinkedRecords.get(i,null);return null!==n?e=this._reinsertAfter(n,e._prev,r):e.currentIndex!=r&&(e.currentIndex=r,this._addToMoves(e,r)),e}_truncate(e){for(;null!==e;){const t=e._next;this._addToRemovals(this._unlink(e)),e=t}null!==this._unlinkedRecords&&this._unlinkedRecords.clear(),null!==this._additionsTail&&(this._additionsTail._nextAdded=null),null!==this._movesTail&&(this._movesTail._nextMoved=null),null!==this._itTail&&(this._itTail._next=null),null!==this._removalsTail&&(this._removalsTail._nextRemoved=null),null!==this._identityChangesTail&&(this._identityChangesTail._nextIdentityChange=null)}_reinsertAfter(e,t,i){null!==this._unlinkedRecords&&this._unlinkedRecords.remove(e);const r=e._prevRemoved,n=e._nextRemoved;return null===r?this._removalsHead=n:r._nextRemoved=n,null===n?this._removalsTail=r:n._prevRemoved=r,this._insertAfter(e,t,i),this._addToMoves(e,i),e}_moveAfter(e,t,i){return this._unlink(e),this._insertAfter(e,t,i),this._addToMoves(e,i),e}_addAfter(e,t,i){return this._insertAfter(e,t,i),this._additionsTail=null===this._additionsTail?this._additionsHead=e:this._additionsTail._nextAdded=e,e}_insertAfter(e,t,i){const r=null===t?this._itHead:t._next;return e._next=r,e._prev=t,null===r?this._itTail=e:r._prev=e,null===t?this._itHead=e:t._next=e,null===this._linkedRecords&&(this._linkedRecords=new $a),this._linkedRecords.put(e),e.currentIndex=i,e}_remove(e){return this._addToRemovals(this._unlink(e))}_unlink(e){null!==this._linkedRecords&&this._linkedRecords.remove(e);const t=e._prev,i=e._next;return null===t?this._itHead=i:t._next=i,null===i?this._itTail=t:i._prev=t,e}_addToMoves(e,t){return e.previousIndex===t||(this._movesTail=null===this._movesTail?this._movesHead=e:this._movesTail._nextMoved=e),e}_addToRemovals(e){return null===this._unlinkedRecords&&(this._unlinkedRecords=new $a),this._unlinkedRecords.put(e),e.currentIndex=null,e._nextRemoved=null,null===this._removalsTail?(this._removalsTail=this._removalsHead=e,e._prevRemoved=null):(e._prevRemoved=this._removalsTail,this._removalsTail=this._removalsTail._nextRemoved=e),e}_addIdentityChange(e,t){return e.item=t,this._identityChangesTail=null===this._identityChangesTail?this._identityChangesHead=e:this._identityChangesTail._nextIdentityChange=e,e}}class za{constructor(e,t){this.item=e,this.trackById=t,this.currentIndex=null,this.previousIndex=null,this._nextPrevious=null,this._prev=null,this._next=null,this._prevDup=null,this._nextDup=null,this._prevRemoved=null,this._nextRemoved=null,this._nextAdded=null,this._nextMoved=null,this._nextIdentityChange=null}}class Wa{constructor(){this._head=null,this._tail=null}add(e){null===this._head?(this._head=this._tail=e,e._nextDup=null,e._prevDup=null):(this._tail._nextDup=e,e._prevDup=this._tail,e._nextDup=null,this._tail=e)}get(e,t){let i;for(i=this._head;null!==i;i=i._nextDup)if((null===t||t<=i.currentIndex)&&Object.is(i.trackById,e))return i;return null}remove(e){const t=e._prevDup,i=e._nextDup;return null===t?this._head=i:t._nextDup=i,null===i?this._tail=t:i._prevDup=t,null===this._head}}class $a{constructor(){this.map=new Map}put(e){const t=e.trackById;let i=this.map.get(t);i||(i=new Wa,this.map.set(t,i)),i.add(e)}get(e,t){const i=this.map.get(e);return i?i.get(e,t):null}remove(e){const t=e.trackById;return this.map.get(t).remove(e)&&this.map.delete(t),e}get isEmpty(){return 0===this.map.size}clear(){this.map.clear()}}function Ka(e,t,i){const r=e.previousIndex;if(null===r)return r;let n=0;return i&&r{if(t&&t.key===i)this._maybeAddToChanges(t,e),this._appendAfter=t,t=t._next;else{const r=this._getOrCreateRecordForKey(i,e);t=this._insertBeforeOrAppend(t,r)}}),t){t._prev&&(t._prev._next=null),this._removalsHead=t;for(let e=t;null!==e;e=e._nextRemoved)e===this._mapHead&&(this._mapHead=null),this._records.delete(e.key),e._nextRemoved=e._next,e.previousValue=e.currentValue,e.currentValue=null,e._prev=null,e._next=null}return this._changesTail&&(this._changesTail._nextChanged=null),this._additionsTail&&(this._additionsTail._nextAdded=null),this.isDirty}_insertBeforeOrAppend(e,t){if(e){const i=e._prev;return t._next=e,t._prev=i,e._prev=t,i&&(i._next=t),e===this._mapHead&&(this._mapHead=t),this._appendAfter=e,e}return this._appendAfter?(this._appendAfter._next=t,t._prev=this._appendAfter):this._mapHead=t,this._appendAfter=t,null}_getOrCreateRecordForKey(e,t){if(this._records.has(e)){const i=this._records.get(e);this._maybeAddToChanges(i,t);const r=i._prev,n=i._next;return r&&(r._next=n),n&&(n._prev=r),i._next=null,i._prev=null,i}const i=new Ya(e);return this._records.set(e,i),i.currentValue=t,this._addToAdditions(i),i}_reset(){if(this.isDirty){let e;for(this._previousMapHead=this._mapHead,e=this._previousMapHead;null!==e;e=e._next)e._nextPrevious=e._next;for(e=this._changesHead;null!==e;e=e._nextChanged)e.previousValue=e.currentValue;for(e=this._additionsHead;null!=e;e=e._nextAdded)e.previousValue=e.currentValue;this._changesHead=this._changesTail=null,this._additionsHead=this._additionsTail=null,this._removalsHead=null}}_maybeAddToChanges(e,t){Object.is(t,e.currentValue)||(e.previousValue=e.currentValue,e.currentValue=t,this._addToChanges(e))}_addToAdditions(e){null===this._additionsHead?this._additionsHead=this._additionsTail=e:(this._additionsTail._nextAdded=e,this._additionsTail=e)}_addToChanges(e){null===this._changesHead?this._changesHead=this._changesTail=e:(this._changesTail._nextChanged=e,this._changesTail=e)}_forEach(e,t){e instanceof Map?e.forEach(t):Object.keys(e).forEach(i=>t(e[i],i))}}class Ya{constructor(e){this.key=e,this.previousValue=null,this.currentValue=null,this._nextPrevious=null,this._next=null,this._prev=null,this._nextAdded=null,this._nextRemoved=null,this._nextChanged=null}}let Xa=(()=>{class e{constructor(e){this.factories=e}static create(t,i){if(null!=i){const e=i.factories.slice();t=t.concat(e)}return new e(t)}static extend(t){return{provide:e,useFactory:i=>{if(!i)throw new Error("Cannot extend IterableDiffers without a parent injector");return e.create(t,i)},deps:[[e,new ae,new se]]}}find(e){const t=this.factories.find(t=>t.supports(e));if(null!=t)return t;throw new Error(`Cannot find a differ supporting object '${e}' of type '${i=e,i.name||typeof i}'`);var i}}return e.\u0275prov=ue({token:e,providedIn:"root",factory:()=>new e([new Ua])}),e})(),Qa=(()=>{class e{constructor(e){this.factories=e}static create(t,i){if(i){const e=i.factories.slice();t=t.concat(e)}return new e(t)}static extend(t){return{provide:e,useFactory:i=>{if(!i)throw new Error("Cannot extend KeyValueDiffers without a parent injector");return e.create(t,i)},deps:[[e,new ae,new se]]}}find(e){const t=this.factories.find(t=>t.supports(e));if(t)return t;throw new Error(`Cannot find a differ supporting object '${e}'`)}}return e.\u0275prov=ue({token:e,providedIn:"root",factory:()=>new e([new Ga])}),e})();const Ja=[new Ga],el=new Xa([new Ua]),tl=new Qa(Ja);let il=(()=>{class e{}return e.__NG_ELEMENT_ID__=()=>rl(e,Pa),e})();const rl=function(e,t){return Us(e,t,si(),ii())};let nl=(()=>{class e{}return e.__NG_ELEMENT_ID__=()=>sl(e,Pa),e})();const sl=function(e,t){return Vs(e,t,si(),ii())},ol={};class al extends La{constructor(e){super(),this.ngModule=e}resolveComponentFactory(e){const t=Ct(e);return new hl(t,this.ngModule)}}function ll(e){const t=[];for(let i in e)e.hasOwnProperty(i)&&t.push({propName:e[i],templateName:i});return t}const cl=new Be("SCHEDULER_TOKEN",{providedIn:"root",factory:()=>Gi});class hl extends Ta{constructor(e,t){super(),this.componentDef=e,this.ngModule=t,this.componentType=e.type,this.selector=e.selectors.map(un).join(","),this.ngContentSelectors=e.ngContentSelectors?e.ngContentSelectors:[],this.isBoundToModule=!!t}get inputs(){return ll(this.componentDef.inputs)}get outputs(){return ll(this.componentDef.outputs)}create(e,t,i,r){const n=(r=r||this.ngModule)?function(e,t){return{get:(i,r,n)=>{const s=e.get(i,ol,n);return s!==ol||r===ol?s:t.get(i,r,n)}}}(e,r.injector):e,s=n.get(Ia,Ut),o=n.get(ja,null),a=s.createRenderer(null,this.componentDef),l=this.componentDef.selectors[0][0]||"div",c=i?function(e,t,i){if(Ht(e))return e.selectRootElement(t,i===ht.ShadowDom);let r="string"==typeof t?e.querySelector(t):t;return r.textContent="",r}(a,i,this.componentDef.encapsulation):En(l,s.createRenderer(null,this.componentDef),function(e){const t=e.toLowerCase();return"svg"===t?"http://www.w3.org/2000/svg":"math"===t?"http://www.w3.org/1998/MathML/":null}(l)),h=this.componentDef.onPush?576:528,u={components:[],scheduler:Gi,clean:ls,playerHandler:null,flags:0},d=Nn(0,-1,null,1,0,null,null,null,null,null),f=An(null,d,u,h,null,null,s,a,o,n);let p,_;bi(f,null);try{const e=function(e,t,i,r,n,s){const o=i[1];i[20]=e;const a=On(o,null,0,3,null,null),l=a.mergedAttrs=t.hostAttrs;null!==l&&(co(a,l,!0),null!==e&&(Ni(n,e,l),null!==a.classes&&Is(n,e,a.classes),null!==a.styles&&Ds(n,e,a.styles)));const c=r.createRenderer(e,t),h=An(i,Fn(t),null,t.onPush?64:16,i[20],a,r,c,void 0);return o.firstCreatePass&&(nr(er(a,i),o,t.type),Kn(o,a),Zn(a,i.length,1)),rs(i,h),i[20]=h}(c,this.componentDef,f,s,a);if(c)if(i)Ni(a,c,["ng-version",Ha.full]);else{const{attrs:e,classes:t}=function(e){const t=[],i=[];let r=1,n=2;for(;r0&&Is(a,c,t.join(" "))}if(_=Wt(d,0),void 0!==t){const e=_.projection=[];for(let i=0;ie(o,t)),t.contentQueries&&t.contentQueries(1,o,i.length-1);const a=si();if(s.firstCreatePass&&(null!==t.hostBindings||null!==t.hostAttrs)){Ai(a.index-20);const e=i[1];qn(e,t),zn(e,i,t.hostVars),Wn(t,o)}return o}(e,this.componentDef,f,u,[_a]),Tn(d,f,null)}finally{xi()}const m=new ul(this.componentType,p,Hs(Pa,_,f),f,_);return d.node.child=_,m}}class ul extends class{}{constructor(e,t,i,r,n){super(),this.location=i,this._rootLView=r,this._tNode=n,this.destroyCbs=[],this.instance=t,this.hostView=this.changeDetectorRef=new Fs(r),function(e,t,i,r){let n=e.node;null==n&&(e.node=n=Bn(0,null,2,-1,null,null)),r[6]=n}(r[1],0,0,r),this.componentType=e}get injector(){return new dr(this._tNode,this._rootLView)}destroy(){this.destroyCbs&&(this.destroyCbs.forEach(e=>e()),this.destroyCbs=null,!this.hostView.destroyed&&this.hostView.destroy())}onDestroy(e){this.destroyCbs&&this.destroyCbs.push(e)}}const dl=void 0;var fl=["en",[["a","p"],["AM","PM"],dl],[["AM","PM"],dl,dl],[["S","M","T","W","T","F","S"],["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],["Su","Mo","Tu","We","Th","Fr","Sa"]],dl,[["J","F","M","A","M","J","J","A","S","O","N","D"],["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],["January","February","March","April","May","June","July","August","September","October","November","December"]],dl,[["B","A"],["BC","AD"],["Before Christ","Anno Domini"]],0,[6,0],["M/d/yy","MMM d, y","MMMM d, y","EEEE, MMMM d, y"],["h:mm a","h:mm:ss a","h:mm:ss a z","h:mm:ss a zzzz"],["{1}, {0}",dl,"{1} 'at' {0}",dl],[".",",",";","%","+","-","E","\xd7","\u2030","\u221e","NaN",":"],["#,##0.###","#,##0%","\xa4#,##0.00","#E0"],"USD","$","US Dollar",{},"ltr",function(e){let t=Math.floor(Math.abs(e)),i=e.toString().replace(/^[^.]*\.?/,"").length;return 1===t&&0===i?1:5}];let pl={};function _l(e){return e in pl||(pl[e]=Le.ng&&Le.ng.common&&Le.ng.common.locales&&Le.ng.common.locales[e]),pl[e]}var ml=function(e){return e[e.LocaleId=0]="LocaleId",e[e.DayPeriodsFormat=1]="DayPeriodsFormat",e[e.DayPeriodsStandalone=2]="DayPeriodsStandalone",e[e.DaysFormat=3]="DaysFormat",e[e.DaysStandalone=4]="DaysStandalone",e[e.MonthsFormat=5]="MonthsFormat",e[e.MonthsStandalone=6]="MonthsStandalone",e[e.Eras=7]="Eras",e[e.FirstDayOfWeek=8]="FirstDayOfWeek",e[e.WeekendRange=9]="WeekendRange",e[e.DateFormat=10]="DateFormat",e[e.TimeFormat=11]="TimeFormat",e[e.DateTimeFormat=12]="DateTimeFormat",e[e.NumberSymbols=13]="NumberSymbols",e[e.NumberFormats=14]="NumberFormats",e[e.CurrencyCode=15]="CurrencyCode",e[e.CurrencySymbol=16]="CurrencySymbol",e[e.CurrencyName=17]="CurrencyName",e[e.Currencies=18]="Currencies",e[e.Directionality=19]="Directionality",e[e.PluralCase=20]="PluralCase",e[e.ExtraData=21]="ExtraData",e}({});let gl="en-US";function vl(e){var t,i;i="Expected localeId to be defined",null==(t=e)&&function(e,t,i,r){throw new Error("ASSERTION ERROR: "+e+` [Expected=> null != ${t} <=Actual]`)}(i,t),"string"==typeof e&&(gl=e.toLowerCase().replace(/_/g,"-"))}const yl=new Map;class bl extends et{constructor(e,t){super(),this._parent=t,this._bootstrapComponents=[],this.injector=this,this.destroyCbs=[],this.componentFactoryResolver=new al(this);const i=kt(e),r=e[Fe]||null;r&&vl(r),this._bootstrapComponents=Yi(i.bootstrap),this._r3Injector=Js(e,t,[{provide:et,useValue:this},{provide:La,useValue:this.componentFactoryResolver}],we(e)),this._r3Injector._resolveInjectorDefTypes(),this.instance=this.get(e)}get(e,t=ao.THROW_IF_NOT_FOUND,i=le.Default){return e===ao||e===et||e===He?this:this._r3Injector.get(e,t,i)}destroy(){const e=this._r3Injector;!e.destroyed&&e.destroy(),this.destroyCbs.forEach(e=>e()),this.destroyCbs=null}onDestroy(e){this.destroyCbs.push(e)}}class wl extends tt{constructor(e){super(),this.moduleType=e,null!==kt(e)&&function e(t){if(null!==t.\u0275mod.id){const e=t.\u0275mod.id;(function(e,t,i){if(t&&t!==i)throw new Error(`Duplicate module registered for ${e} - ${we(t)} vs ${we(t.name)}`)})(e,yl.get(e),t),yl.set(e,t)}let i=t.\u0275mod.imports;i instanceof Function&&(i=i()),i&&i.forEach(t=>e(t))}(e)}create(e){return new bl(this.moduleType,e)}}function Cl(e,t,i){const r=ui()+e,n=ii();return n[r]===dn?mo(n,r,i?t.call(i):t()):function(e,t){return e[t]}(n,r)}function Sl(e,t){const i=e[t];return i===dn?void 0:i}function kl(e,t){const i=ri();let r;const n=e+20;i.firstCreatePass?(r=function(e,t){if(t)for(let i=t.length-1;i>=0;i--){const r=t[i];if(e===r.name)return r}throw new Error(`The pipe '${e}' could not be found!`)}(t,i.pipeRegistry),i.data[n]=r,r.onDestroy&&(i.destroyHooks||(i.destroyHooks=[])).push(n,r.onDestroy)):r=i.data[n];const s=r.factory||(r.factory=St(r.type)),o=Ke(Co),a=Qi(!1),l=s();return Qi(a),Ke(o),function(e,t,i,r){const n=i+20;n>=e.data.length&&(e.data[n]=null,e.blueprint[n]=null),t[n]=r}(i,ii(),e,l),l}function xl(e,t,i){const r=ii(),n=$t(r,e);return function(e,t){return fo.isWrapped(t)&&(t=fo.unwrap(t),e[ei.lFrame.bindingIndex]=dn),t}(r,function(e,t){return e[1].data[t+20].pure}(r,e)?function(e,t,i,r,n,s){const o=t+i;return go(e,o,n)?mo(e,o+1,s?r.call(s,n):r(n)):Sl(e,o+1)}(r,ui(),t,n.transform,i,n):n.transform(i))}const El=class extends S{constructor(e=!1){super(),this.__isAsync=e}emit(e){super.next(e)}subscribe(e,t,i){let r,n=e=>null,s=()=>null;e&&"object"==typeof e?(r=this.__isAsync?t=>{setTimeout(()=>e.next(t))}:t=>{e.next(t)},e.error&&(n=this.__isAsync?t=>{setTimeout(()=>e.error(t))}:t=>{e.error(t)}),e.complete&&(s=this.__isAsync?()=>{setTimeout(()=>e.complete())}:()=>{e.complete()})):(r=this.__isAsync?t=>{setTimeout(()=>e(t))}:t=>{e(t)},t&&(n=this.__isAsync?e=>{setTimeout(()=>t(e))}:e=>{t(e)}),i&&(s=this.__isAsync?()=>{setTimeout(()=>i())}:()=>{i()}));const o=super.subscribe(r,n,s);return e instanceof u&&e.add(o),o}};function Al(){return this._results[uo()]()}class Ol{constructor(){this.dirty=!0,this._results=[],this.changes=new El,this.length=0;const e=uo(),t=Ol.prototype;t[e]||(t[e]=Al)}map(e){return this._results.map(e)}filter(e){return this._results.filter(e)}find(e){return this._results.find(e)}reduce(e,t){return this._results.reduce(e,t)}forEach(e){this._results.forEach(e)}some(e){return this._results.some(e)}toArray(){return this._results.slice()}toString(){return this._results.toString()}reset(e){this._results=function e(t,i){void 0===i&&(i=t);for(let r=0;r0)n.push(a[t/2]);else{const s=o[t+1],a=i[-r];for(let t=10;t{class e{constructor(e){this.appInits=e,this.initialized=!1,this.done=!1,this.donePromise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}runInitializers(){if(this.initialized)return;const e=[],t=()=>{this.done=!0,this.resolve()};if(this.appInits)for(let i=0;i{t()}).catch(e=>{this.reject(e)}),0===e.length&&t(),this.initialized=!0}}return e.\u0275fac=function(t){return new(t||e)(Ze(Zl,8))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const Xl=new Be("AppId"),Ql={provide:Xl,useFactory:function(){return`${Jl()}${Jl()}${Jl()}`},deps:[]};function Jl(){return String.fromCharCode(97+Math.floor(25*Math.random()))}const ec=new Be("Platform Initializer"),tc=new Be("Platform ID"),ic=new Be("appBootstrapListener");let rc=(()=>{class e{log(e){console.log(e)}warn(e){console.warn(e)}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const nc=new Be("LocaleId"),sc=new Be("DefaultCurrencyCode");class oc{constructor(e,t){this.ngModuleFactory=e,this.componentFactories=t}}const ac=function(e){return new wl(e)},lc=ac,cc=function(e){return Promise.resolve(ac(e))},hc=function(e){const t=ac(e),i=Yi(kt(e).declarations).reduce((e,t)=>{const i=Ct(t);return i&&e.push(new hl(i)),e},[]);return new oc(t,i)},uc=hc,dc=function(e){return Promise.resolve(hc(e))};let fc=(()=>{class e{constructor(){this.compileModuleSync=lc,this.compileModuleAsync=cc,this.compileModuleAndAllComponentsSync=uc,this.compileModuleAndAllComponentsAsync=dc}clearCache(){}clearCacheFor(e){}getModuleId(e){}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const pc=(()=>Promise.resolve(0))();function _c(e){"undefined"==typeof Zone?pc.then(()=>{e&&e.apply(null,null)}):Zone.current.scheduleMicroTask("scheduleMicrotask",e)}class mc{constructor({enableLongStackTrace:e=!1,shouldCoalesceEventChangeDetection:t=!1}){if(this.hasPendingMacrotasks=!1,this.hasPendingMicrotasks=!1,this.isStable=!0,this.onUnstable=new El(!1),this.onMicrotaskEmpty=new El(!1),this.onStable=new El(!1),this.onError=new El(!1),"undefined"==typeof Zone)throw new Error("In this configuration Angular requires Zone.js");Zone.assertZonePatched(),this._nesting=0,this._outer=this._inner=Zone.current,Zone.wtfZoneSpec&&(this._inner=this._inner.fork(Zone.wtfZoneSpec)),Zone.TaskTrackingZoneSpec&&(this._inner=this._inner.fork(new Zone.TaskTrackingZoneSpec)),e&&Zone.longStackTraceZoneSpec&&(this._inner=this._inner.fork(Zone.longStackTraceZoneSpec)),this.shouldCoalesceEventChangeDetection=t,this.lastRequestAnimationFrameId=-1,this.nativeRequestAnimationFrame=function(){let e=Le.requestAnimationFrame,t=Le.cancelAnimationFrame;if("undefined"!=typeof Zone&&e&&t){const i=e[Zone.__symbol__("OriginalDelegate")];i&&(e=i);const r=t[Zone.__symbol__("OriginalDelegate")];r&&(t=r)}return{nativeRequestAnimationFrame:e,nativeCancelAnimationFrame:t}}().nativeRequestAnimationFrame,function(e){const t=!!e.shouldCoalesceEventChangeDetection&&e.nativeRequestAnimationFrame&&(()=>{!function(e){-1===e.lastRequestAnimationFrameId&&(e.lastRequestAnimationFrameId=e.nativeRequestAnimationFrame.call(Le,()=>{e.fakeTopEventTask||(e.fakeTopEventTask=Zone.root.scheduleEventTask("fakeTopEventTask",()=>{e.lastRequestAnimationFrameId=-1,bc(e),yc(e)},void 0,()=>{},()=>{})),e.fakeTopEventTask.invoke()}),bc(e))}(e)});e._inner=e._inner.fork({name:"angular",properties:{isAngularZone:!0,maybeDelayChangeDetection:t},onInvokeTask:(i,r,n,s,o,a)=>{try{return wc(e),i.invokeTask(n,s,o,a)}finally{t&&"eventTask"===s.type&&t(),Cc(e)}},onInvoke:(t,i,r,n,s,o,a)=>{try{return wc(e),t.invoke(r,n,s,o,a)}finally{Cc(e)}},onHasTask:(t,i,r,n)=>{t.hasTask(r,n),i===r&&("microTask"==n.change?(e._hasPendingMicrotasks=n.microTask,bc(e),yc(e)):"macroTask"==n.change&&(e.hasPendingMacrotasks=n.macroTask))},onHandleError:(t,i,r,n)=>(t.handleError(r,n),e.runOutsideAngular(()=>e.onError.emit(n)),!1)})}(this)}static isInAngularZone(){return!0===Zone.current.get("isAngularZone")}static assertInAngularZone(){if(!mc.isInAngularZone())throw new Error("Expected to be in Angular Zone, but it is not!")}static assertNotInAngularZone(){if(mc.isInAngularZone())throw new Error("Expected to not be in Angular Zone, but it is!")}run(e,t,i){return this._inner.run(e,t,i)}runTask(e,t,i,r){const n=this._inner,s=n.scheduleEventTask("NgZoneEvent: "+r,e,vc,gc,gc);try{return n.runTask(s,t,i)}finally{n.cancelTask(s)}}runGuarded(e,t,i){return this._inner.runGuarded(e,t,i)}runOutsideAngular(e){return this._outer.run(e)}}function gc(){}const vc={};function yc(e){if(0==e._nesting&&!e.hasPendingMicrotasks&&!e.isStable)try{e._nesting++,e.onMicrotaskEmpty.emit(null)}finally{if(e._nesting--,!e.hasPendingMicrotasks)try{e.runOutsideAngular(()=>e.onStable.emit(null))}finally{e.isStable=!0}}}function bc(e){e.hasPendingMicrotasks=!!(e._hasPendingMicrotasks||e.shouldCoalesceEventChangeDetection&&-1!==e.lastRequestAnimationFrameId)}function wc(e){e._nesting++,e.isStable&&(e.isStable=!1,e.onUnstable.emit(null))}function Cc(e){e._nesting--,yc(e)}class Sc{constructor(){this.hasPendingMicrotasks=!1,this.hasPendingMacrotasks=!1,this.isStable=!0,this.onUnstable=new El,this.onMicrotaskEmpty=new El,this.onStable=new El,this.onError=new El}run(e,t,i){return e.apply(t,i)}runGuarded(e,t,i){return e.apply(t,i)}runOutsideAngular(e){return e()}runTask(e,t,i,r){return e.apply(t,i)}}let kc=(()=>{class e{constructor(e){this._ngZone=e,this._pendingCount=0,this._isZoneStable=!0,this._didWork=!1,this._callbacks=[],this.taskTrackingZone=null,this._watchAngularEvents(),e.run(()=>{this.taskTrackingZone="undefined"==typeof Zone?null:Zone.current.get("TaskTrackingZone")})}_watchAngularEvents(){this._ngZone.onUnstable.subscribe({next:()=>{this._didWork=!0,this._isZoneStable=!1}}),this._ngZone.runOutsideAngular(()=>{this._ngZone.onStable.subscribe({next:()=>{mc.assertNotInAngularZone(),_c(()=>{this._isZoneStable=!0,this._runCallbacksIfReady()})}})})}increasePendingRequestCount(){return this._pendingCount+=1,this._didWork=!0,this._pendingCount}decreasePendingRequestCount(){if(this._pendingCount-=1,this._pendingCount<0)throw new Error("pending async requests below zero");return this._runCallbacksIfReady(),this._pendingCount}isStable(){return this._isZoneStable&&0===this._pendingCount&&!this._ngZone.hasPendingMacrotasks}_runCallbacksIfReady(){if(this.isStable())_c(()=>{for(;0!==this._callbacks.length;){let e=this._callbacks.pop();clearTimeout(e.timeoutId),e.doneCb(this._didWork)}this._didWork=!1});else{let e=this.getPendingTasks();this._callbacks=this._callbacks.filter(t=>!t.updateCb||!t.updateCb(e)||(clearTimeout(t.timeoutId),!1)),this._didWork=!0}}getPendingTasks(){return this.taskTrackingZone?this.taskTrackingZone.macroTasks.map(e=>({source:e.source,creationLocation:e.creationLocation,data:e.data})):[]}addCallback(e,t,i){let r=-1;t&&t>0&&(r=setTimeout(()=>{this._callbacks=this._callbacks.filter(e=>e.timeoutId!==r),e(this._didWork,this.getPendingTasks())},t)),this._callbacks.push({doneCb:e,timeoutId:r,updateCb:i})}whenStable(e,t,i){if(i&&!this.taskTrackingZone)throw new Error('Task tracking zone is required when passing an update callback to whenStable(). Is "zone.js/dist/task-tracking.js" loaded?');this.addCallback(e,t,i),this._runCallbacksIfReady()}getPendingRequestCount(){return this._pendingCount}findProviders(e,t,i){return[]}}return e.\u0275fac=function(t){return new(t||e)(Ze(mc))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),xc=(()=>{class e{constructor(){this._applications=new Map,Oc.addToWindow(this)}registerApplication(e,t){this._applications.set(e,t)}unregisterApplication(e){this._applications.delete(e)}unregisterAllApplications(){this._applications.clear()}getTestability(e){return this._applications.get(e)||null}getAllTestabilities(){return Array.from(this._applications.values())}getAllRootElements(){return Array.from(this._applications.keys())}findTestabilityInTree(e,t=!0){return Oc.findTestabilityInTree(this,e,t)}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();class Ec{addToWindow(e){}findTestabilityInTree(e,t,i){return null}}let Ac,Oc=new Ec;const Tc=new Be("AllowMultipleToken");class Rc{constructor(e,t){this.name=e,this.token=t}}function Lc(e,t,i=[]){const r="Platform: "+t,n=new Be(r);return(t=[])=>{let s=Pc();if(!s||s.injector.get(Tc,!1))if(e)e(i.concat(t).concat({provide:n,useValue:!0}));else{const e=i.concat(t).concat({provide:n,useValue:!0},{provide:Ks,useValue:"platform"});!function(e){if(Ac&&!Ac.destroyed&&!Ac.injector.get(Tc,!1))throw new Error("There can be only one platform. Destroy the previous one to create a new one.");Ac=e.get(Dc);const t=e.get(ec,null);t&&t.forEach(e=>e())}(ao.create({providers:e,name:r}))}return function(e){const t=Pc();if(!t)throw new Error("No platform exists!");if(!t.injector.get(e,null))throw new Error("A platform with a different configuration has been created. Please destroy it first.");return t}(n)}}function Pc(){return Ac&&!Ac.destroyed?Ac:null}let Dc=(()=>{class e{constructor(e){this._injector=e,this._modules=[],this._destroyListeners=[],this._destroyed=!1}bootstrapModuleFactory(e,t){const i=function(e,t){let i;return i="noop"===e?new Sc:("zone.js"===e?void 0:e)||new mc({enableLongStackTrace:kr(),shouldCoalesceEventChangeDetection:t}),i}(t?t.ngZone:void 0,t&&t.ngZoneEventCoalescing||!1),r=[{provide:mc,useValue:i}];return i.run(()=>{const t=ao.create({providers:r,parent:this.injector,name:e.moduleType.name}),n=e.create(t),s=n.injector.get(vr,null);if(!s)throw new Error("No ErrorHandler. Is platform module (BrowserModule) included?");return n.onDestroy(()=>Fc(this._modules,n)),i.runOutsideAngular(()=>i.onError.subscribe({next:e=>{s.handleError(e)}})),function(e,t,i){try{const r=i();return Po(r)?r.catch(i=>{throw t.runOutsideAngular(()=>e.handleError(i)),i}):r}catch(r){throw t.runOutsideAngular(()=>e.handleError(r)),r}}(s,i,()=>{const e=n.injector.get(Yl);return e.runInitializers(),e.donePromise.then(()=>(vl(n.injector.get(nc,"en-US")||"en-US"),this._moduleDoBootstrap(n),n))})})}bootstrapModule(e,t=[]){const i=Ic({},t);return function(e,t,i){const r=new wl(i);return Promise.resolve(r)}(0,0,e).then(e=>this.bootstrapModuleFactory(e,i))}_moduleDoBootstrap(e){const t=e.injector.get(Mc);if(e._bootstrapComponents.length>0)e._bootstrapComponents.forEach(e=>t.bootstrap(e));else{if(!e.instance.ngDoBootstrap)throw new Error(`The module ${we(e.instance.constructor)} was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. Please define one of these.`);e.instance.ngDoBootstrap(t)}this._modules.push(e)}onDestroy(e){this._destroyListeners.push(e)}get injector(){return this._injector}destroy(){if(this._destroyed)throw new Error("The platform has already been destroyed!");this._modules.slice().forEach(e=>e.destroy()),this._destroyListeners.forEach(e=>e()),this._destroyed=!0}get destroyed(){return this._destroyed}}return e.\u0275fac=function(t){return new(t||e)(Ze(ao))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();function Ic(e,t){return Array.isArray(t)?t.reduce(Ic,e):Object.assign(Object.assign({},e),t)}let Mc=(()=>{class e{constructor(e,t,i,r,n,s){this._zone=e,this._console=t,this._injector=i,this._exceptionHandler=r,this._componentFactoryResolver=n,this._initStatus=s,this._bootstrapListeners=[],this._views=[],this._runningTick=!1,this._enforceNoNewChanges=!1,this._stable=!0,this.componentTypes=[],this.components=[],this._enforceNoNewChanges=kr(),this._zone.onMicrotaskEmpty.subscribe({next:()=>{this._zone.run(()=>{this.tick()})}});const o=new v(e=>{this._stable=this._zone.isStable&&!this._zone.hasPendingMacrotasks&&!this._zone.hasPendingMicrotasks,this._zone.runOutsideAngular(()=>{e.next(this._stable),e.complete()})}),a=new v(e=>{let t;this._zone.runOutsideAngular(()=>{t=this._zone.onStable.subscribe(()=>{mc.assertNotInAngularZone(),_c(()=>{this._stable||this._zone.hasPendingMacrotasks||this._zone.hasPendingMicrotasks||(this._stable=!0,e.next(!0))})})});const i=this._zone.onUnstable.subscribe(()=>{mc.assertInAngularZone(),this._stable&&(this._stable=!1,this._zone.runOutsideAngular(()=>{e.next(!1)}))});return()=>{t.unsubscribe(),i.unsubscribe()}});this.isStable=W(o,a.pipe(te()))}bootstrap(e,t){if(!this._initStatus.done)throw new Error("Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.");let i;i=e instanceof Ta?e:this._componentFactoryResolver.resolveComponentFactory(e),this.componentTypes.push(i.componentType);const r=i.isBoundToModule?void 0:this._injector.get(et),n=i.create(ao.NULL,[],t||i.selector,r);n.onDestroy(()=>{this._unloadComponent(n)});const s=n.injector.get(kc,null);return s&&n.injector.get(xc).registerApplication(n.location.nativeElement,s),this._loadComponent(n),kr()&&this._console.log("Angular is running in development mode. Call enableProdMode() to enable production mode."),n}tick(){if(this._runningTick)throw new Error("ApplicationRef.tick is called recursively");try{this._runningTick=!0;for(let e of this._views)e.detectChanges();if(this._enforceNoNewChanges)for(let e of this._views)e.checkNoChanges()}catch(e){this._zone.runOutsideAngular(()=>this._exceptionHandler.handleError(e))}finally{this._runningTick=!1}}attachView(e){const t=e;this._views.push(t),t.attachToAppRef(this)}detachView(e){const t=e;Fc(this._views,t),t.detachFromAppRef()}_loadComponent(e){this.attachView(e.hostView),this.tick(),this.components.push(e),this._injector.get(ic,[]).concat(this._bootstrapListeners).forEach(t=>t(e))}_unloadComponent(e){this.detachView(e.hostView),Fc(this.components,e)}ngOnDestroy(){this._views.slice().forEach(e=>e.destroy())}get viewCount(){return this._views.length}}return e.\u0275fac=function(t){return new(t||e)(Ze(mc),Ze(rc),Ze(ao),Ze(vr),Ze(La),Ze(Yl))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();function Fc(e,t){const i=e.indexOf(t);i>-1&&e.splice(i,1)}class Nc{}class jc{}const Bc={factoryPathPrefix:"",factoryPathSuffix:".ngfactory"};let Hc=(()=>{class e{constructor(e,t){this._compiler=e,this._config=t||Bc}load(e){return this.loadAndCompile(e)}loadAndCompile(e){let[t,r]=e.split("#");return void 0===r&&(r="default"),i("zn8P")(t).then(e=>e[r]).then(e=>Uc(e,t,r)).then(e=>this._compiler.compileModuleAsync(e))}loadFactory(e){let[t,r]=e.split("#"),n="NgFactory";return void 0===r&&(r="default",n=""),i("zn8P")(this._config.factoryPathPrefix+t+this._config.factoryPathSuffix).then(e=>e[r+n]).then(e=>Uc(e,t,r))}}return e.\u0275fac=function(t){return new(t||e)(Ze(fc),Ze(jc,8))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();function Uc(e,t,i){if(!e)throw new Error(`Cannot find '${i}' in '${t}'`);return e}const Vc=Lc(null,"core",[{provide:tc,useValue:"unknown"},{provide:Dc,deps:[ao]},{provide:xc,deps:[]},{provide:rc,deps:[]}]),qc=[{provide:Mc,useClass:Mc,deps:[mc,rc,ao,vr,La,Yl]},{provide:cl,deps:[mc],useFactory:function(e){let t=[];return e.onStable.subscribe(()=>{for(;t.length;)t.pop()()}),function(e){t.push(e)}}},{provide:Yl,useClass:Yl,deps:[[new se,Zl]]},{provide:fc,useClass:fc,deps:[]},Ql,{provide:Xa,useFactory:function(){return el},deps:[]},{provide:Qa,useFactory:function(){return tl},deps:[]},{provide:nc,useFactory:function(e){return vl(e=e||"undefined"!=typeof $localize&&$localize.locale||"en-US"),e},deps:[[new ne(nc),new se,new ae]]},{provide:sc,useValue:"USD"}];let zc=(()=>{class e{constructor(e){}}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)(Ze(Mc))},providers:qc}),e})(),Wc=null;function $c(){return Wc}const Kc=new Be("DocumentToken");let Gc=(()=>{class e{}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({factory:Zc,token:e,providedIn:"platform"}),e})();function Zc(){return Ze(Xc)}const Yc=new Be("Location Initialized");let Xc=(()=>{class e extends Gc{constructor(e){super(),this._doc=e,this._init()}_init(){this.location=$c().getLocation(),this._history=$c().getHistory()}getBaseHrefFromDOM(){return $c().getBaseHref(this._doc)}onPopState(e){$c().getGlobalEventTarget(this._doc,"window").addEventListener("popstate",e,!1)}onHashChange(e){$c().getGlobalEventTarget(this._doc,"window").addEventListener("hashchange",e,!1)}get href(){return this.location.href}get protocol(){return this.location.protocol}get hostname(){return this.location.hostname}get port(){return this.location.port}get pathname(){return this.location.pathname}get search(){return this.location.search}get hash(){return this.location.hash}set pathname(e){this.location.pathname=e}pushState(e,t,i){Qc()?this._history.pushState(e,t,i):this.location.hash=i}replaceState(e,t,i){Qc()?this._history.replaceState(e,t,i):this.location.hash=i}forward(){this._history.forward()}back(){this._history.back()}getState(){return this._history.state}}return e.\u0275fac=function(t){return new(t||e)(Ze(Kc))},e.\u0275prov=ue({factory:Jc,token:e,providedIn:"platform"}),e})();function Qc(){return!!window.history.pushState}function Jc(){return new Xc(Ze(Kc))}function eh(e,t){if(0==e.length)return t;if(0==t.length)return e;let i=0;return e.endsWith("/")&&i++,t.startsWith("/")&&i++,2==i?e+t.substring(1):1==i?e+t:e+"/"+t}function th(e){const t=e.match(/#|\?|$/),i=t&&t.index||e.length;return e.slice(0,i-("/"===e[i-1]?1:0))+e.slice(i)}function ih(e){return e&&"?"!==e[0]?"?"+e:e}let rh=(()=>{class e{}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({factory:nh,token:e,providedIn:"root"}),e})();function nh(e){const t=Ze(Kc).location;return new oh(Ze(Gc),t&&t.origin||"")}const sh=new Be("appBaseHref");let oh=(()=>{class e extends rh{constructor(e,t){if(super(),this._platformLocation=e,null==t&&(t=this._platformLocation.getBaseHrefFromDOM()),null==t)throw new Error("No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.");this._baseHref=t}onPopState(e){this._platformLocation.onPopState(e),this._platformLocation.onHashChange(e)}getBaseHref(){return this._baseHref}prepareExternalUrl(e){return eh(this._baseHref,e)}path(e=!1){const t=this._platformLocation.pathname+ih(this._platformLocation.search),i=this._platformLocation.hash;return i&&e?`${t}${i}`:t}pushState(e,t,i,r){const n=this.prepareExternalUrl(i+ih(r));this._platformLocation.pushState(e,t,n)}replaceState(e,t,i,r){const n=this.prepareExternalUrl(i+ih(r));this._platformLocation.replaceState(e,t,n)}forward(){this._platformLocation.forward()}back(){this._platformLocation.back()}}return e.\u0275fac=function(t){return new(t||e)(Ze(Gc),Ze(sh,8))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),ah=(()=>{class e extends rh{constructor(e,t){super(),this._platformLocation=e,this._baseHref="",null!=t&&(this._baseHref=t)}onPopState(e){this._platformLocation.onPopState(e),this._platformLocation.onHashChange(e)}getBaseHref(){return this._baseHref}path(e=!1){let t=this._platformLocation.hash;return null==t&&(t="#"),t.length>0?t.substring(1):t}prepareExternalUrl(e){const t=eh(this._baseHref,e);return t.length>0?"#"+t:t}pushState(e,t,i,r){let n=this.prepareExternalUrl(i+ih(r));0==n.length&&(n=this._platformLocation.pathname),this._platformLocation.pushState(e,t,n)}replaceState(e,t,i,r){let n=this.prepareExternalUrl(i+ih(r));0==n.length&&(n=this._platformLocation.pathname),this._platformLocation.replaceState(e,t,n)}forward(){this._platformLocation.forward()}back(){this._platformLocation.back()}}return e.\u0275fac=function(t){return new(t||e)(Ze(Gc),Ze(sh,8))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),lh=(()=>{class e{constructor(e,t){this._subject=new El,this._urlChangeListeners=[],this._platformStrategy=e;const i=this._platformStrategy.getBaseHref();this._platformLocation=t,this._baseHref=th(hh(i)),this._platformStrategy.onPopState(e=>{this._subject.emit({url:this.path(!0),pop:!0,state:e.state,type:e.type})})}path(e=!1){return this.normalize(this._platformStrategy.path(e))}getState(){return this._platformLocation.getState()}isCurrentPathEqualTo(e,t=""){return this.path()==this.normalize(e+ih(t))}normalize(t){return e.stripTrailingSlash(function(e,t){return e&&t.startsWith(e)?t.substring(e.length):t}(this._baseHref,hh(t)))}prepareExternalUrl(e){return e&&"/"!==e[0]&&(e="/"+e),this._platformStrategy.prepareExternalUrl(e)}go(e,t="",i=null){this._platformStrategy.pushState(i,"",e,t),this._notifyUrlChangeListeners(this.prepareExternalUrl(e+ih(t)),i)}replaceState(e,t="",i=null){this._platformStrategy.replaceState(i,"",e,t),this._notifyUrlChangeListeners(this.prepareExternalUrl(e+ih(t)),i)}forward(){this._platformStrategy.forward()}back(){this._platformStrategy.back()}onUrlChange(e){this._urlChangeListeners.push(e),this._urlChangeSubscription||(this._urlChangeSubscription=this.subscribe(e=>{this._notifyUrlChangeListeners(e.url,e.state)}))}_notifyUrlChangeListeners(e="",t){this._urlChangeListeners.forEach(i=>i(e,t))}subscribe(e,t,i){return this._subject.subscribe({next:e,error:t,complete:i})}}return e.\u0275fac=function(t){return new(t||e)(Ze(rh),Ze(Gc))},e.normalizeQueryParams=ih,e.joinWithSlash=eh,e.stripTrailingSlash=th,e.\u0275prov=ue({factory:ch,token:e,providedIn:"root"}),e})();function ch(){return new lh(Ze(rh),Ze(Gc))}function hh(e){return e.replace(/\/index.html$/,"")}var uh=function(e){return e[e.Zero=0]="Zero",e[e.One=1]="One",e[e.Two=2]="Two",e[e.Few=3]="Few",e[e.Many=4]="Many",e[e.Other=5]="Other",e}({});class dh{}let fh=(()=>{class e extends dh{constructor(e){super(),this.locale=e}getPluralCategory(e,t){switch(function(e){return function(e){const t=function(e){return e.toLowerCase().replace(/_/g,"-")}(e);let i=_l(t);if(i)return i;const r=t.split("-")[0];if(i=_l(r),i)return i;if("en"===r)return fl;throw new Error(`Missing locale data for the locale "${e}".`)}(e)[ml.PluralCase]}(t||this.locale)(e)){case uh.Zero:return"zero";case uh.One:return"one";case uh.Two:return"two";case uh.Few:return"few";case uh.Many:return"many";default:return"other"}}}return e.\u0275fac=function(t){return new(t||e)(Ze(nc))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();function ph(e,t){t=encodeURIComponent(t);for(const i of e.split(";")){const e=i.indexOf("="),[r,n]=-1==e?[i,""]:[i.slice(0,e),i.slice(e+1)];if(r.trim()===t)return decodeURIComponent(n)}return null}let _h=(()=>{class e{constructor(e,t,i,r){this._iterableDiffers=e,this._keyValueDiffers=t,this._ngEl=i,this._renderer=r,this._iterableDiffer=null,this._keyValueDiffer=null,this._initialClasses=[],this._rawClass=null}set klass(e){this._removeClasses(this._initialClasses),this._initialClasses="string"==typeof e?e.split(/\s+/):[],this._applyClasses(this._initialClasses),this._applyClasses(this._rawClass)}set ngClass(e){this._removeClasses(this._rawClass),this._applyClasses(this._initialClasses),this._iterableDiffer=null,this._keyValueDiffer=null,this._rawClass="string"==typeof e?e.split(/\s+/):e,this._rawClass&&(po(this._rawClass)?this._iterableDiffer=this._iterableDiffers.find(this._rawClass).create():this._keyValueDiffer=this._keyValueDiffers.find(this._rawClass).create())}ngDoCheck(){if(this._iterableDiffer){const e=this._iterableDiffer.diff(this._rawClass);e&&this._applyIterableChanges(e)}else if(this._keyValueDiffer){const e=this._keyValueDiffer.diff(this._rawClass);e&&this._applyKeyValueChanges(e)}}_applyKeyValueChanges(e){e.forEachAddedItem(e=>this._toggleClass(e.key,e.currentValue)),e.forEachChangedItem(e=>this._toggleClass(e.key,e.currentValue)),e.forEachRemovedItem(e=>{e.previousValue&&this._toggleClass(e.key,!1)})}_applyIterableChanges(e){e.forEachAddedItem(e=>{if("string"!=typeof e.item)throw new Error("NgClass can only toggle CSS classes expressed as strings, got "+we(e.item));this._toggleClass(e.item,!0)}),e.forEachRemovedItem(e=>this._toggleClass(e.item,!1))}_applyClasses(e){e&&(Array.isArray(e)||e instanceof Set?e.forEach(e=>this._toggleClass(e,!0)):Object.keys(e).forEach(t=>this._toggleClass(t,!!e[t])))}_removeClasses(e){e&&(Array.isArray(e)||e instanceof Set?e.forEach(e=>this._toggleClass(e,!1)):Object.keys(e).forEach(e=>this._toggleClass(e,!1)))}_toggleClass(e,t){(e=e.trim())&&e.split(/\s+/g).forEach(e=>{t?this._renderer.addClass(this._ngEl.nativeElement,e):this._renderer.removeClass(this._ngEl.nativeElement,e)})}}return e.\u0275fac=function(t){return new(t||e)(Co(Xa),Co(Qa),Co(Pa),Co(Fa))},e.\u0275dir=bt({type:e,selectors:[["","ngClass",""]],inputs:{klass:["class","klass"],ngClass:"ngClass"}}),e})();class mh{constructor(e,t,i,r){this.$implicit=e,this.ngForOf=t,this.index=i,this.count=r}get first(){return 0===this.index}get last(){return this.index===this.count-1}get even(){return this.index%2==0}get odd(){return!this.even}}let gh=(()=>{class e{constructor(e,t,i){this._viewContainer=e,this._template=t,this._differs=i,this._ngForOf=null,this._ngForOfDirty=!0,this._differ=null}set ngForOf(e){this._ngForOf=e,this._ngForOfDirty=!0}set ngForTrackBy(e){kr()&&null!=e&&"function"!=typeof e&&console&&console.warn&&console.warn(`trackBy must be a function, but received ${JSON.stringify(e)}. See https://angular.io/api/common/NgForOf#change-propagation for more information.`),this._trackByFn=e}get ngForTrackBy(){return this._trackByFn}set ngForTemplate(e){e&&(this._template=e)}ngDoCheck(){if(this._ngForOfDirty){this._ngForOfDirty=!1;const i=this._ngForOf;if(!this._differ&&i)try{this._differ=this._differs.find(i).create(this.ngForTrackBy)}catch(t){throw new Error(`Cannot find a differ supporting object '${i}' of type '${e=i,e.name||typeof e}'. NgFor only supports binding to Iterables such as Arrays.`)}}var e;if(this._differ){const e=this._differ.diff(this._ngForOf);e&&this._applyChanges(e)}}_applyChanges(e){const t=[];e.forEachOperation((e,i,r)=>{if(null==e.previousIndex){const i=this._viewContainer.createEmbeddedView(this._template,new mh(null,this._ngForOf,-1,-1),null===r?void 0:r),n=new vh(e,i);t.push(n)}else if(null==r)this._viewContainer.remove(null===i?void 0:i);else if(null!==i){const n=this._viewContainer.get(i);this._viewContainer.move(n,r);const s=new vh(e,n);t.push(s)}});for(let i=0;i{this._viewContainer.get(e.currentIndex).context.$implicit=e.item})}_perViewChange(e,t){e.context.$implicit=t.item}static ngTemplateContextGuard(e,t){return!0}}return e.\u0275fac=function(t){return new(t||e)(Co(nl),Co(il),Co(Xa))},e.\u0275dir=bt({type:e,selectors:[["","ngFor","","ngForOf",""]],inputs:{ngForOf:"ngForOf",ngForTrackBy:"ngForTrackBy",ngForTemplate:"ngForTemplate"}}),e})();class vh{constructor(e,t){this.record=e,this.view=t}}let yh=(()=>{class e{constructor(e,t){this._viewContainer=e,this._context=new bh,this._thenTemplateRef=null,this._elseTemplateRef=null,this._thenViewRef=null,this._elseViewRef=null,this._thenTemplateRef=t}set ngIf(e){this._context.$implicit=this._context.ngIf=e,this._updateView()}set ngIfThen(e){wh("ngIfThen",e),this._thenTemplateRef=e,this._thenViewRef=null,this._updateView()}set ngIfElse(e){wh("ngIfElse",e),this._elseTemplateRef=e,this._elseViewRef=null,this._updateView()}_updateView(){this._context.$implicit?this._thenViewRef||(this._viewContainer.clear(),this._elseViewRef=null,this._thenTemplateRef&&(this._thenViewRef=this._viewContainer.createEmbeddedView(this._thenTemplateRef,this._context))):this._elseViewRef||(this._viewContainer.clear(),this._thenViewRef=null,this._elseTemplateRef&&(this._elseViewRef=this._viewContainer.createEmbeddedView(this._elseTemplateRef,this._context)))}static ngTemplateContextGuard(e,t){return!0}}return e.\u0275fac=function(t){return new(t||e)(Co(nl),Co(il))},e.\u0275dir=bt({type:e,selectors:[["","ngIf",""]],inputs:{ngIf:"ngIf",ngIfThen:"ngIfThen",ngIfElse:"ngIfElse"}}),e})();class bh{constructor(){this.$implicit=null,this.ngIf=null}}function wh(e,t){if(t&&!t.createEmbeddedView)throw new Error(`${e} must be a TemplateRef, but received '${we(t)}'.`)}class Ch{constructor(e,t){this._viewContainerRef=e,this._templateRef=t,this._created=!1}create(){this._created=!0,this._viewContainerRef.createEmbeddedView(this._templateRef)}destroy(){this._created=!1,this._viewContainerRef.clear()}enforceState(e){e&&!this._created?this.create():!e&&this._created&&this.destroy()}}let Sh=(()=>{class e{constructor(){this._defaultUsed=!1,this._caseCount=0,this._lastCaseCheckIndex=0,this._lastCasesMatched=!1}set ngSwitch(e){this._ngSwitch=e,0===this._caseCount&&this._updateDefaultCases(!0)}_addCase(){return this._caseCount++}_addDefault(e){this._defaultViews||(this._defaultViews=[]),this._defaultViews.push(e)}_matchCase(e){const t=e==this._ngSwitch;return this._lastCasesMatched=this._lastCasesMatched||t,this._lastCaseCheckIndex++,this._lastCaseCheckIndex===this._caseCount&&(this._updateDefaultCases(!this._lastCasesMatched),this._lastCaseCheckIndex=0,this._lastCasesMatched=!1),t}_updateDefaultCases(e){if(this._defaultViews&&e!==this._defaultUsed){this._defaultUsed=e;for(let t=0;t{class e{constructor(e,t,i){this.ngSwitch=i,i._addCase(),this._view=new Ch(e,t)}ngDoCheck(){this._view.enforceState(this.ngSwitch._matchCase(this.ngSwitchCase))}}return e.\u0275fac=function(t){return new(t||e)(Co(nl),Co(il),Co(Sh,1))},e.\u0275dir=bt({type:e,selectors:[["","ngSwitchCase",""]],inputs:{ngSwitchCase:"ngSwitchCase"}}),e})(),xh=(()=>{class e{constructor(e,t,i){i._addDefault(new Ch(e,t))}}return e.\u0275fac=function(t){return new(t||e)(Co(nl),Co(il),Co(Sh,1))},e.\u0275dir=bt({type:e,selectors:[["","ngSwitchDefault",""]]}),e})();class Eh{createSubscription(e,t){return e.subscribe({next:t,error:e=>{throw e}})}dispose(e){e.unsubscribe()}onDestroy(e){e.unsubscribe()}}class Ah{createSubscription(e,t){return e.then(t,e=>{throw e})}dispose(e){}onDestroy(e){}}const Oh=new Ah,Th=new Eh;let Rh=(()=>{class e{constructor(e){this._ref=e,this._latestValue=null,this._subscription=null,this._obj=null,this._strategy=null}ngOnDestroy(){this._subscription&&this._dispose()}transform(e){return this._obj?e!==this._obj?(this._dispose(),this.transform(e)):this._latestValue:(e&&this._subscribe(e),this._latestValue)}_subscribe(e){this._obj=e,this._strategy=this._selectStrategy(e),this._subscription=this._strategy.createSubscription(e,t=>this._updateLatestValue(e,t))}_selectStrategy(t){if(Po(t))return Oh;if(Do(t))return Th;throw Error(`InvalidPipeArgument: '${t}' for pipe '${we(e)}'`)}_dispose(){this._strategy.dispose(this._subscription),this._latestValue=null,this._subscription=null,this._obj=null}_updateLatestValue(e,t){e===this._obj&&(this._latestValue=t,this._ref.markForCheck())}}return e.\u0275fac=function(t){return new(t||e)(function(e=le.Default){const t=qs(!0);if(null!=t||e&le.Optional)return t;throw new Error("No provider for ChangeDetectorRef!")}())},e.\u0275pipe=wt({name:"async",type:e,pure:!1}),e})(),Lh=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[{provide:dh,useClass:fh}]}),e})();function Ph(e){return"browser"===e}let Dh=(()=>{class e{}return e.\u0275prov=ue({token:e,providedIn:"root",factory:()=>new Ih(Ze(Kc),window,Ze(vr))}),e})();class Ih{constructor(e,t,i){this.document=e,this.window=t,this.errorHandler=i,this.offset=()=>[0,0]}setOffset(e){this.offset=Array.isArray(e)?()=>e:e}getScrollPosition(){return this.supportsScrolling()?[this.window.scrollX,this.window.scrollY]:[0,0]}scrollToPosition(e){this.supportsScrolling()&&this.window.scrollTo(e[0],e[1])}scrollToAnchor(e){if(this.supportsScrolling()){const t=this.document.getElementById(e)||this.document.getElementsByName(e)[0];t&&this.scrollToElement(t)}}setHistoryScrollRestoration(e){if(this.supportScrollRestoration()){const t=this.window.history;t&&t.scrollRestoration&&(t.scrollRestoration=e)}}scrollToElement(e){const t=e.getBoundingClientRect(),i=t.left+this.window.pageXOffset,r=t.top+this.window.pageYOffset,n=this.offset();this.window.scrollTo(i-n[0],r-n[1])}supportScrollRestoration(){try{if(!this.window||!this.window.scrollTo)return!1;const e=Mh(this.window.history)||Mh(Object.getPrototypeOf(this.window.history));return!(!e||!e.writable&&!e.set)}catch(e){return!1}}supportsScrolling(){try{return!!this.window.scrollTo}catch(e){return!1}}}function Mh(e){return Object.getOwnPropertyDescriptor(e,"scrollRestoration")}class Fh extends class extends class{}{constructor(){super()}supportsDOMEvents(){return!0}}{static makeCurrent(){var e;e=new Fh,Wc||(Wc=e)}getProperty(e,t){return e[t]}log(e){window.console&&window.console.log&&window.console.log(e)}logGroup(e){window.console&&window.console.group&&window.console.group(e)}logGroupEnd(){window.console&&window.console.groupEnd&&window.console.groupEnd()}onAndCancel(e,t,i){return e.addEventListener(t,i,!1),()=>{e.removeEventListener(t,i,!1)}}dispatchEvent(e,t){e.dispatchEvent(t)}remove(e){return e.parentNode&&e.parentNode.removeChild(e),e}getValue(e){return e.value}createElement(e,t){return(t=t||this.getDefaultDocument()).createElement(e)}createHtmlDocument(){return document.implementation.createHTMLDocument("fakeTitle")}getDefaultDocument(){return document}isElementNode(e){return e.nodeType===Node.ELEMENT_NODE}isShadowRoot(e){return e instanceof DocumentFragment}getGlobalEventTarget(e,t){return"window"===t?window:"document"===t?e:"body"===t?e.body:null}getHistory(){return window.history}getLocation(){return window.location}getBaseHref(e){const t=jh||(jh=document.querySelector("base"),jh)?jh.getAttribute("href"):null;return null==t?null:(i=t,Nh||(Nh=document.createElement("a")),Nh.setAttribute("href",i),"/"===Nh.pathname.charAt(0)?Nh.pathname:"/"+Nh.pathname);var i}resetBaseElement(){jh=null}getUserAgent(){return window.navigator.userAgent}performanceNow(){return window.performance&&window.performance.now?window.performance.now():(new Date).getTime()}supportsCookies(){return!0}getCookie(e){return ph(document.cookie,e)}}let Nh,jh=null;const Bh=new Be("TRANSITION_ID"),Hh=[{provide:Zl,useFactory:function(e,t,i){return()=>{i.get(Yl).donePromise.then(()=>{const i=$c();Array.prototype.slice.apply(t.querySelectorAll("style[ng-transition]")).filter(t=>t.getAttribute("ng-transition")===e).forEach(e=>i.remove(e))})}},deps:[Bh,Kc,ao],multi:!0}];class Uh{static init(){var e;e=new Uh,Oc=e}addToWindow(e){Le.getAngularTestability=(t,i=!0)=>{const r=e.findTestabilityInTree(t,i);if(null==r)throw new Error("Could not find testability for element.");return r},Le.getAllAngularTestabilities=()=>e.getAllTestabilities(),Le.getAllAngularRootElements=()=>e.getAllRootElements(),Le.frameworkStabilizers||(Le.frameworkStabilizers=[]),Le.frameworkStabilizers.push(e=>{const t=Le.getAllAngularTestabilities();let i=t.length,r=!1;const n=function(t){r=r||t,i--,0==i&&e(r)};t.forEach((function(e){e.whenStable(n)}))})}findTestabilityInTree(e,t,i){if(null==t)return null;const r=e.getTestability(t);return null!=r?r:i?$c().isShadowRoot(t)?this.findTestabilityInTree(e,t.host,!0):this.findTestabilityInTree(e,t.parentElement,!0):null}}const Vh=new Be("EventManagerPlugins");let qh=(()=>{class e{constructor(e,t){this._zone=t,this._eventNameToPlugin=new Map,e.forEach(e=>e.manager=this),this._plugins=e.slice().reverse()}addEventListener(e,t,i){return this._findPluginFor(t).addEventListener(e,t,i)}addGlobalEventListener(e,t,i){return this._findPluginFor(t).addGlobalEventListener(e,t,i)}getZone(){return this._zone}_findPluginFor(e){const t=this._eventNameToPlugin.get(e);if(t)return t;const i=this._plugins;for(let r=0;r{class e{constructor(){this._stylesSet=new Set}addStyles(e){const t=new Set;e.forEach(e=>{this._stylesSet.has(e)||(this._stylesSet.add(e),t.add(e))}),this.onStylesAdded(t)}onStylesAdded(e){}getAllStyles(){return Array.from(this._stylesSet)}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),$h=(()=>{class e extends Wh{constructor(e){super(),this._doc=e,this._hostNodes=new Set,this._styleNodes=new Set,this._hostNodes.add(e.head)}_addStylesToHost(e,t){e.forEach(e=>{const i=this._doc.createElement("style");i.textContent=e,this._styleNodes.add(t.appendChild(i))})}addHost(e){this._addStylesToHost(this._stylesSet,e),this._hostNodes.add(e)}removeHost(e){this._hostNodes.delete(e)}onStylesAdded(e){this._hostNodes.forEach(t=>this._addStylesToHost(e,t))}ngOnDestroy(){this._styleNodes.forEach(e=>$c().remove(e))}}return e.\u0275fac=function(t){return new(t||e)(Ze(Kc))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const Kh={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"},Gh=/%COMP%/g;function Zh(e,t,i){for(let r=0;r{if("__ngUnwrap__"===t)return e;!1===e(t)&&(t.preventDefault(),t.returnValue=!1)}}let Xh=(()=>{class e{constructor(e,t,i){this.eventManager=e,this.sharedStylesHost=t,this.appId=i,this.rendererByCompId=new Map,this.defaultRenderer=new Qh(e)}createRenderer(e,t){if(!e||!t)return this.defaultRenderer;switch(t.encapsulation){case ht.Emulated:{let i=this.rendererByCompId.get(t.id);return i||(i=new Jh(this.eventManager,this.sharedStylesHost,t,this.appId),this.rendererByCompId.set(t.id,i)),i.applyToHost(e),i}case ht.Native:case ht.ShadowDom:return new eu(this.eventManager,this.sharedStylesHost,e,t);default:if(!this.rendererByCompId.has(t.id)){const e=Zh(t.id,t.styles,[]);this.sharedStylesHost.addStyles(e),this.rendererByCompId.set(t.id,this.defaultRenderer)}return this.defaultRenderer}}begin(){}end(){}}return e.\u0275fac=function(t){return new(t||e)(Ze(qh),Ze($h),Ze(Xl))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();class Qh{constructor(e){this.eventManager=e,this.data=Object.create(null)}destroy(){}createElement(e,t){return t?document.createElementNS(Kh[t]||t,e):document.createElement(e)}createComment(e){return document.createComment(e)}createText(e){return document.createTextNode(e)}appendChild(e,t){e.appendChild(t)}insertBefore(e,t,i){e&&e.insertBefore(t,i)}removeChild(e,t){e&&e.removeChild(t)}selectRootElement(e,t){let i="string"==typeof e?document.querySelector(e):e;if(!i)throw new Error(`The selector "${e}" did not match any elements`);return t||(i.textContent=""),i}parentNode(e){return e.parentNode}nextSibling(e){return e.nextSibling}setAttribute(e,t,i,r){if(r){t=r+":"+t;const n=Kh[r];n?e.setAttributeNS(n,t,i):e.setAttribute(t,i)}else e.setAttribute(t,i)}removeAttribute(e,t,i){if(i){const r=Kh[i];r?e.removeAttributeNS(r,t):e.removeAttribute(`${i}:${t}`)}else e.removeAttribute(t)}addClass(e,t){e.classList.add(t)}removeClass(e,t){e.classList.remove(t)}setStyle(e,t,i,r){r&Ma.DashCase?e.style.setProperty(t,i,r&Ma.Important?"important":""):e.style[t]=i}removeStyle(e,t,i){i&Ma.DashCase?e.style.removeProperty(t):e.style[t]=""}setProperty(e,t,i){e[t]=i}setValue(e,t){e.nodeValue=t}listen(e,t,i){return"string"==typeof e?this.eventManager.addGlobalEventListener(e,t,Yh(i)):this.eventManager.addEventListener(e,t,Yh(i))}}class Jh extends Qh{constructor(e,t,i,r){super(e),this.component=i;const n=Zh(r+"-"+i.id,i.styles,[]);t.addStyles(n),this.contentAttr="_ngcontent-%COMP%".replace(Gh,r+"-"+i.id),this.hostAttr=function(e){return"_nghost-%COMP%".replace(Gh,e)}(r+"-"+i.id)}applyToHost(e){super.setAttribute(e,this.hostAttr,"")}createElement(e,t){const i=super.createElement(e,t);return super.setAttribute(i,this.contentAttr,""),i}}class eu extends Qh{constructor(e,t,i,r){super(e),this.sharedStylesHost=t,this.hostEl=i,this.component=r,this.shadowRoot=r.encapsulation===ht.ShadowDom?i.attachShadow({mode:"open"}):i.createShadowRoot(),this.sharedStylesHost.addHost(this.shadowRoot);const n=Zh(r.id,r.styles,[]);for(let s=0;s{class e extends zh{constructor(e){super(e)}supports(e){return!0}addEventListener(e,t,i){return e.addEventListener(t,i,!1),()=>this.removeEventListener(e,t,i)}removeEventListener(e,t,i){return e.removeEventListener(t,i)}}return e.\u0275fac=function(t){return new(t||e)(Ze(Kc))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const iu=["alt","control","meta","shift"],ru={"\b":"Backspace","\t":"Tab","\x7f":"Delete","\x1b":"Escape",Del:"Delete",Esc:"Escape",Left:"ArrowLeft",Right:"ArrowRight",Up:"ArrowUp",Down:"ArrowDown",Menu:"ContextMenu",Scroll:"ScrollLock",Win:"OS"},nu={A:"1",B:"2",C:"3",D:"4",E:"5",F:"6",G:"7",H:"8",I:"9",J:"*",K:"+",M:"-",N:".",O:"/","`":"0","\x90":"NumLock"},su={alt:e=>e.altKey,control:e=>e.ctrlKey,meta:e=>e.metaKey,shift:e=>e.shiftKey};let ou=(()=>{class e extends zh{constructor(e){super(e)}supports(t){return null!=e.parseEventName(t)}addEventListener(t,i,r){const n=e.parseEventName(i),s=e.eventCallback(n.fullKey,r,this.manager.getZone());return this.manager.getZone().runOutsideAngular(()=>$c().onAndCancel(t,n.domEventName,s))}static parseEventName(t){const i=t.toLowerCase().split("."),r=i.shift();if(0===i.length||"keydown"!==r&&"keyup"!==r)return null;const n=e._normalizeKey(i.pop());let s="";if(iu.forEach(e=>{const t=i.indexOf(e);t>-1&&(i.splice(t,1),s+=e+".")}),s+=n,0!=i.length||0===n.length)return null;const o={};return o.domEventName=r,o.fullKey=s,o}static getEventFullKey(e){let t="",i=function(e){let t=e.key;if(null==t){if(t=e.keyIdentifier,null==t)return"Unidentified";t.startsWith("U+")&&(t=String.fromCharCode(parseInt(t.substring(2),16)),3===e.location&&nu.hasOwnProperty(t)&&(t=nu[t]))}return ru[t]||t}(e);return i=i.toLowerCase()," "===i?i="space":"."===i&&(i="dot"),iu.forEach(r=>{r!=i&&(0,su[r])(e)&&(t+=r+".")}),t+=i,t}static eventCallback(t,i,r){return n=>{e.getEventFullKey(n)===t&&r.runGuarded(()=>i(n))}}static _normalizeKey(e){switch(e){case"esc":return"escape";default:return e}}}return e.\u0275fac=function(t){return new(t||e)(Ze(Kc))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const au=Lc(Vc,"browser",[{provide:tc,useValue:"browser"},{provide:ec,useValue:function(){Fh.makeCurrent(),Uh.init()},multi:!0},{provide:Kc,useFactory:function(){return function(e){jt=e}(document),document},deps:[]}]),lu=[[],{provide:Ks,useValue:"root"},{provide:vr,useFactory:function(){return new vr},deps:[]},{provide:Vh,useClass:tu,multi:!0,deps:[Kc,mc,tc]},{provide:Vh,useClass:ou,multi:!0,deps:[Kc]},[],{provide:Xh,useClass:Xh,deps:[qh,$h,Xl]},{provide:Ia,useExisting:Xh},{provide:Wh,useExisting:$h},{provide:$h,useClass:$h,deps:[Kc]},{provide:kc,useClass:kc,deps:[mc]},{provide:qh,useClass:qh,deps:[Vh,mc]},[]];let cu=(()=>{class e{constructor(e){if(e)throw new Error("BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.")}static withServerTransition(t){return{ngModule:e,providers:[{provide:Xl,useValue:t.appId},{provide:Bh,useExisting:Xl},Hh]}}}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)(Ze(e,12))},providers:lu,imports:[Lh,zc]}),e})();"undefined"!=typeof window&&window;class hu{}function uu(e,t){return{type:7,name:e,definitions:t,options:{}}}function du(e,t=null){return{type:4,styles:t,timings:e}}function fu(e,t=null){return{type:2,steps:e,options:t}}function pu(e){return{type:6,styles:e,offset:null}}function _u(e,t,i){return{type:0,name:e,styles:t,options:i}}function mu(e,t,i=null){return{type:1,expr:e,animation:t,options:i}}function gu(e=null){return{type:9,options:e}}function vu(e,t,i=null){return{type:11,selector:e,animation:t,options:i}}function yu(e){Promise.resolve(null).then(e)}class bu{constructor(e=0,t=0){this._onDoneFns=[],this._onStartFns=[],this._onDestroyFns=[],this._started=!1,this._destroyed=!1,this._finished=!1,this.parentPlayer=null,this.totalTime=e+t}_onFinish(){this._finished||(this._finished=!0,this._onDoneFns.forEach(e=>e()),this._onDoneFns=[])}onStart(e){this._onStartFns.push(e)}onDone(e){this._onDoneFns.push(e)}onDestroy(e){this._onDestroyFns.push(e)}hasStarted(){return this._started}init(){}play(){this.hasStarted()||(this._onStart(),this.triggerMicrotask()),this._started=!0}triggerMicrotask(){yu(()=>this._onFinish())}_onStart(){this._onStartFns.forEach(e=>e()),this._onStartFns=[]}pause(){}restart(){}finish(){this._onFinish()}destroy(){this._destroyed||(this._destroyed=!0,this.hasStarted()||this._onStart(),this.finish(),this._onDestroyFns.forEach(e=>e()),this._onDestroyFns=[])}reset(){}setPosition(e){}getPosition(){return 0}triggerCallback(e){const t="start"==e?this._onStartFns:this._onDoneFns;t.forEach(e=>e()),t.length=0}}class wu{constructor(e){this._onDoneFns=[],this._onStartFns=[],this._finished=!1,this._started=!1,this._destroyed=!1,this._onDestroyFns=[],this.parentPlayer=null,this.totalTime=0,this.players=e;let t=0,i=0,r=0;const n=this.players.length;0==n?yu(()=>this._onFinish()):this.players.forEach(e=>{e.onDone(()=>{++t==n&&this._onFinish()}),e.onDestroy(()=>{++i==n&&this._onDestroy()}),e.onStart(()=>{++r==n&&this._onStart()})}),this.totalTime=this.players.reduce((e,t)=>Math.max(e,t.totalTime),0)}_onFinish(){this._finished||(this._finished=!0,this._onDoneFns.forEach(e=>e()),this._onDoneFns=[])}init(){this.players.forEach(e=>e.init())}onStart(e){this._onStartFns.push(e)}_onStart(){this.hasStarted()||(this._started=!0,this._onStartFns.forEach(e=>e()),this._onStartFns=[])}onDone(e){this._onDoneFns.push(e)}onDestroy(e){this._onDestroyFns.push(e)}hasStarted(){return this._started}play(){this.parentPlayer||this.init(),this._onStart(),this.players.forEach(e=>e.play())}pause(){this.players.forEach(e=>e.pause())}restart(){this.players.forEach(e=>e.restart())}finish(){this._onFinish(),this.players.forEach(e=>e.finish())}destroy(){this._onDestroy()}_onDestroy(){this._destroyed||(this._destroyed=!0,this._onFinish(),this.players.forEach(e=>e.destroy()),this._onDestroyFns.forEach(e=>e()),this._onDestroyFns=[])}reset(){this.players.forEach(e=>e.reset()),this._destroyed=!1,this._finished=!1,this._started=!1}setPosition(e){const t=e*this.totalTime;this.players.forEach(e=>{const i=e.totalTime?Math.min(1,t/e.totalTime):1;e.setPosition(i)})}getPosition(){let e=0;return this.players.forEach(t=>{const i=t.getPosition();e=Math.min(i,e)}),e}beforeDestroy(){this.players.forEach(e=>{e.beforeDestroy&&e.beforeDestroy()})}triggerCallback(e){const t="start"==e?this._onStartFns:this._onDoneFns;t.forEach(e=>e()),t.length=0}}function Cu(){return"undefined"!=typeof process&&"[object process]"==={}.toString.call(process)}function Su(e){switch(e.length){case 0:return new bu;case 1:return e[0];default:return new wu(e)}}function ku(e,t,i,r,n={},s={}){const o=[],a=[];let l=-1,c=null;if(r.forEach(e=>{const i=e.offset,r=i==l,h=r&&c||{};Object.keys(e).forEach(i=>{let r=i,a=e[i];if("offset"!==i)switch(r=t.normalizePropertyName(r,o),a){case"!":a=n[i];break;case"*":a=s[i];break;default:a=t.normalizeStyleValue(i,r,a,o)}h[r]=a}),r||a.push(h),c=h,l=i}),o.length){const e="\n - ";throw new Error(`Unable to animate due to the following errors:${e}${o.join(e)}`)}return a}function xu(e,t,i,r){switch(t){case"start":e.onStart(()=>r(i&&Eu(i,"start",e)));break;case"done":e.onDone(()=>r(i&&Eu(i,"done",e)));break;case"destroy":e.onDestroy(()=>r(i&&Eu(i,"destroy",e)))}}function Eu(e,t,i){const r=i.totalTime,n=Au(e.element,e.triggerName,e.fromState,e.toState,t||e.phaseName,null==r?e.totalTime:r,!!i.disabled),s=e._data;return null!=s&&(n._data=s),n}function Au(e,t,i,r,n="",s=0,o){return{element:e,triggerName:t,fromState:i,toState:r,phaseName:n,totalTime:s,disabled:!!o}}function Ou(e,t,i){let r;return e instanceof Map?(r=e.get(t),r||e.set(t,r=i)):(r=e[t],r||(r=e[t]=i)),r}function Tu(e){const t=e.indexOf(":");return[e.substring(1,t),e.substr(t+1)]}let Ru=(e,t)=>!1,Lu=(e,t)=>!1,Pu=(e,t,i)=>[];const Du=Cu();(Du||"undefined"!=typeof Element)&&(Ru=(e,t)=>e.contains(t),Lu=(()=>{if(Du||Element.prototype.matches)return(e,t)=>e.matches(t);{const e=Element.prototype,t=e.matchesSelector||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;return t?(e,i)=>t.apply(e,[i]):Lu}})(),Pu=(e,t,i)=>{let r=[];if(i)r.push(...e.querySelectorAll(t));else{const i=e.querySelector(t);i&&r.push(i)}return r});let Iu=null,Mu=!1;function Fu(e){Iu||(Iu=("undefined"!=typeof document?document.body:null)||{},Mu=!!Iu.style&&"WebkitAppearance"in Iu.style);let t=!0;return Iu.style&&!function(e){return"ebkit"==e.substring(1,6)}(e)&&(t=e in Iu.style,!t&&Mu)&&(t="Webkit"+e.charAt(0).toUpperCase()+e.substr(1)in Iu.style),t}const Nu=Lu,ju=Ru,Bu=Pu;function Hu(e){const t={};return Object.keys(e).forEach(i=>{const r=i.replace(/([a-z])([A-Z])/g,"$1-$2");t[r]=e[i]}),t}let Uu=(()=>{class e{validateStyleProperty(e){return Fu(e)}matchesElement(e,t){return Nu(e,t)}containsElement(e,t){return ju(e,t)}query(e,t,i){return Bu(e,t,i)}computeStyle(e,t,i){return i||""}animate(e,t,i,r,n,s=[],o){return new bu(i,r)}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),Vu=(()=>{class e{}return e.NOOP=new Uu,e})();function qu(e){if("number"==typeof e)return e;const t=e.match(/^(-?[\.\d]+)(m?s)/);return!t||t.length<2?0:zu(parseFloat(t[1]),t[2])}function zu(e,t){switch(t){case"s":return 1e3*e;default:return e}}function Wu(e,t,i){return e.hasOwnProperty("duration")?e:function(e,t,i){let r,n=0,s="";if("string"==typeof e){const i=e.match(/^(-?[\.\d]+)(m?s)(?:\s+(-?[\.\d]+)(m?s))?(?:\s+([-a-z]+(?:\(.+?\))?))?$/i);if(null===i)return t.push(`The provided timing value "${e}" is invalid.`),{duration:0,delay:0,easing:""};r=zu(parseFloat(i[1]),i[2]);const o=i[3];null!=o&&(n=zu(parseFloat(o),i[4]));const a=i[5];a&&(s=a)}else r=e;if(!i){let i=!1,s=t.length;r<0&&(t.push("Duration values below 0 are not allowed for this animation step."),i=!0),n<0&&(t.push("Delay values below 0 are not allowed for this animation step."),i=!0),i&&t.splice(s,0,`The provided timing value "${e}" is invalid.`)}return{duration:r,delay:n,easing:s}}(e,t,i)}function $u(e,t={}){return Object.keys(e).forEach(i=>{t[i]=e[i]}),t}function Ku(e,t,i={}){if(t)for(let r in e)i[r]=e[r];else $u(e,i);return i}function Gu(e,t,i){return i?t+":"+i+";":""}function Zu(e){let t="";for(let i=0;i{const n=nd(r);i&&!i.hasOwnProperty(r)&&(i[r]=e.style[n]),e.style[n]=t[r]}),Cu()&&Zu(e))}function Xu(e,t){e.style&&(Object.keys(t).forEach(t=>{const i=nd(t);e.style[i]=""}),Cu()&&Zu(e))}function Qu(e){return Array.isArray(e)?1==e.length?e[0]:fu(e):e}const Ju=new RegExp("{{\\s*(.+?)\\s*}}","g");function ed(e){let t=[];if("string"==typeof e){let i;for(;i=Ju.exec(e);)t.push(i[1]);Ju.lastIndex=0}return t}function td(e,t,i){const r=e.toString(),n=r.replace(Ju,(e,r)=>{let n=t[r];return t.hasOwnProperty(r)||(i.push("Please provide a value for the animation param "+r),n=""),n.toString()});return n==r?e:n}function id(e){const t=[];let i=e.next();for(;!i.done;)t.push(i.value),i=e.next();return t}const rd=/-+([a-z0-9])/g;function nd(e){return e.replace(rd,(...e)=>e[1].toUpperCase())}function sd(e,t){return 0===e||0===t}function od(e,t,i){const r=Object.keys(i);if(r.length&&t.length){let s=t[0],o=[];if(r.forEach(e=>{s.hasOwnProperty(e)||o.push(e),s[e]=i[e]}),o.length)for(var n=1;nfunction(e,t,i){if(":"==e[0]){const r=function(e,t){switch(e){case":enter":return"void => *";case":leave":return"* => void";case":increment":return(e,t)=>parseFloat(t)>parseFloat(e);case":decrement":return(e,t)=>parseFloat(t) *"}}(e,i);if("function"==typeof r)return void t.push(r);e=r}const r=e.match(/^(\*|[-\w]+)\s*()\s*(\*|[-\w]+)$/);if(null==r||r.length<4)return i.push(`The provided transition expression "${e}" is not supported`),t;const n=r[1],s=r[2],o=r[3];t.push(dd(n,o)),"<"!=s[0]||"*"==n&&"*"==o||t.push(dd(o,n))}(e,i,t)):i.push(e),i}const hd=new Set(["true","1"]),ud=new Set(["false","0"]);function dd(e,t){const i=hd.has(e)||ud.has(e),r=hd.has(t)||ud.has(t);return(n,s)=>{let o="*"==e||e==n,a="*"==t||t==s;return!o&&i&&"boolean"==typeof n&&(o=n?hd.has(e):ud.has(e)),!a&&r&&"boolean"==typeof s&&(a=s?hd.has(t):ud.has(t)),o&&a}}const fd=new RegExp("s*:selfs*,?","g");function pd(e,t,i){return new _d(e).build(t,i)}class _d{constructor(e){this._driver=e}build(e,t){const i=new md(t);return this._resetContextStyleTimingState(i),ad(this,Qu(e),i)}_resetContextStyleTimingState(e){e.currentQuerySelector="",e.collectedStyles={},e.collectedStyles[""]={},e.currentTime=0}visitTrigger(e,t){let i=t.queryCount=0,r=t.depCount=0;const n=[],s=[];return"@"==e.name.charAt(0)&&t.errors.push("animation triggers cannot be prefixed with an `@` sign (e.g. trigger('@foo', [...]))"),e.definitions.forEach(e=>{if(this._resetContextStyleTimingState(t),0==e.type){const i=e,r=i.name;r.toString().split(/\s*,\s*/).forEach(e=>{i.name=e,n.push(this.visitState(i,t))}),i.name=r}else if(1==e.type){const n=this.visitTransition(e,t);i+=n.queryCount,r+=n.depCount,s.push(n)}else t.errors.push("only state() and transition() definitions can sit inside of a trigger()")}),{type:7,name:e.name,states:n,transitions:s,queryCount:i,depCount:r,options:null}}visitState(e,t){const i=this.visitStyle(e.styles,t),r=e.options&&e.options.params||null;if(i.containsDynamicStyles){const n=new Set,s=r||{};if(i.styles.forEach(e=>{if(gd(e)){const t=e;Object.keys(t).forEach(e=>{ed(t[e]).forEach(e=>{s.hasOwnProperty(e)||n.add(e)})})}}),n.size){const i=id(n.values());t.errors.push(`state("${e.name}", ...) must define default values for all the following style substitutions: ${i.join(", ")}`)}}return{type:0,name:e.name,style:i,options:r?{params:r}:null}}visitTransition(e,t){t.queryCount=0,t.depCount=0;const i=ad(this,Qu(e.animation),t);return{type:1,matchers:cd(e.expr,t.errors),animation:i,queryCount:t.queryCount,depCount:t.depCount,options:vd(e.options)}}visitSequence(e,t){return{type:2,steps:e.steps.map(e=>ad(this,e,t)),options:vd(e.options)}}visitGroup(e,t){const i=t.currentTime;let r=0;const n=e.steps.map(e=>{t.currentTime=i;const n=ad(this,e,t);return r=Math.max(r,t.currentTime),n});return t.currentTime=r,{type:3,steps:n,options:vd(e.options)}}visitAnimate(e,t){const i=function(e,t){let i=null;if(e.hasOwnProperty("duration"))i=e;else if("number"==typeof e)return yd(Wu(e,t).duration,0,"");const r=e;if(r.split(/\s+/).some(e=>"{"==e.charAt(0)&&"{"==e.charAt(1))){const e=yd(0,0,"");return e.dynamic=!0,e.strValue=r,e}return i=i||Wu(r,t),yd(i.duration,i.delay,i.easing)}(e.timings,t.errors);let r;t.currentAnimateTimings=i;let n=e.styles?e.styles:pu({});if(5==n.type)r=this.visitKeyframes(n,t);else{let n=e.styles,s=!1;if(!n){s=!0;const e={};i.easing&&(e.easing=i.easing),n=pu(e)}t.currentTime+=i.duration+i.delay;const o=this.visitStyle(n,t);o.isEmptyStep=s,r=o}return t.currentAnimateTimings=null,{type:4,timings:i,style:r,options:null}}visitStyle(e,t){const i=this._makeStyleAst(e,t);return this._validateStyleAst(i,t),i}_makeStyleAst(e,t){const i=[];Array.isArray(e.styles)?e.styles.forEach(e=>{"string"==typeof e?"*"==e?i.push(e):t.errors.push(`The provided style string value ${e} is not allowed.`):i.push(e)}):i.push(e.styles);let r=!1,n=null;return i.forEach(e=>{if(gd(e)){const t=e,i=t.easing;if(i&&(n=i,delete t.easing),!r)for(let e in t)if(t[e].toString().indexOf("{{")>=0){r=!0;break}}}),{type:6,styles:i,easing:n,offset:e.offset,containsDynamicStyles:r,options:null}}_validateStyleAst(e,t){const i=t.currentAnimateTimings;let r=t.currentTime,n=t.currentTime;i&&n>0&&(n-=i.duration+i.delay),e.styles.forEach(e=>{"string"!=typeof e&&Object.keys(e).forEach(i=>{if(!this._driver.validateStyleProperty(i))return void t.errors.push(`The provided animation property "${i}" is not a supported CSS property for animations`);const s=t.collectedStyles[t.currentQuerySelector],o=s[i];let a=!0;o&&(n!=r&&n>=o.startTime&&r<=o.endTime&&(t.errors.push(`The CSS property "${i}" that exists between the times of "${o.startTime}ms" and "${o.endTime}ms" is also being animated in a parallel animation between the times of "${n}ms" and "${r}ms"`),a=!1),n=o.startTime),a&&(s[i]={startTime:n,endTime:r}),t.options&&function(e,t,i){const r=t.params||{},n=ed(e);n.length&&n.forEach(e=>{r.hasOwnProperty(e)||i.push(`Unable to resolve the local animation param ${e} in the given list of values`)})}(e[i],t.options,t.errors)})})}visitKeyframes(e,t){const i={type:5,styles:[],options:null};if(!t.currentAnimateTimings)return t.errors.push("keyframes() must be placed inside of a call to animate()"),i;let r=0;const n=[];let s=!1,o=!1,a=0;const l=e.steps.map(e=>{const i=this._makeStyleAst(e,t);let l=null!=i.offset?i.offset:function(e){if("string"==typeof e)return null;let t=null;if(Array.isArray(e))e.forEach(e=>{if(gd(e)&&e.hasOwnProperty("offset")){const i=e;t=parseFloat(i.offset),delete i.offset}});else if(gd(e)&&e.hasOwnProperty("offset")){const i=e;t=parseFloat(i.offset),delete i.offset}return t}(i.styles),c=0;return null!=l&&(r++,c=i.offset=l),o=o||c<0||c>1,s=s||c0&&r{const s=h>0?r==u?1:h*r:n[r],o=s*p;t.currentTime=d+f.delay+o,f.duration=o,this._validateStyleAst(e,t),e.offset=s,i.styles.push(e)}),i}visitReference(e,t){return{type:8,animation:ad(this,Qu(e.animation),t),options:vd(e.options)}}visitAnimateChild(e,t){return t.depCount++,{type:9,options:vd(e.options)}}visitAnimateRef(e,t){return{type:10,animation:this.visitReference(e.animation,t),options:vd(e.options)}}visitQuery(e,t){const i=t.currentQuerySelector,r=e.options||{};t.queryCount++,t.currentQuery=e;const[n,s]=function(e){const t=!!e.split(/\s*,\s*/).find(e=>":self"==e);return t&&(e=e.replace(fd,"")),[e=e.replace(/@\*/g,".ng-trigger").replace(/@\w+/g,e=>".ng-trigger-"+e.substr(1)).replace(/:animating/g,".ng-animating"),t]}(e.selector);t.currentQuerySelector=i.length?i+" "+n:n,Ou(t.collectedStyles,t.currentQuerySelector,{});const o=ad(this,Qu(e.animation),t);return t.currentQuery=null,t.currentQuerySelector=i,{type:11,selector:n,limit:r.limit||0,optional:!!r.optional,includeSelf:s,animation:o,originalSelector:e.selector,options:vd(e.options)}}visitStagger(e,t){t.currentQuery||t.errors.push("stagger() can only be used inside of query()");const i="full"===e.timings?{duration:0,delay:0,easing:"full"}:Wu(e.timings,t.errors,!0);return{type:12,animation:ad(this,Qu(e.animation),t),timings:i,options:null}}}class md{constructor(e){this.errors=e,this.queryCount=0,this.depCount=0,this.currentTransition=null,this.currentQuery=null,this.currentQuerySelector=null,this.currentAnimateTimings=null,this.currentTime=0,this.collectedStyles={},this.options=null}}function gd(e){return!Array.isArray(e)&&"object"==typeof e}function vd(e){var t;return e?(e=$u(e)).params&&(e.params=(t=e.params)?$u(t):null):e={},e}function yd(e,t,i){return{duration:e,delay:t,easing:i}}function bd(e,t,i,r,n,s,o=null,a=!1){return{type:1,element:e,keyframes:t,preStyleProps:i,postStyleProps:r,duration:n,delay:s,totalTime:n+s,easing:o,subTimeline:a}}class wd{constructor(){this._map=new Map}consume(e){let t=this._map.get(e);return t?this._map.delete(e):t=[],t}append(e,t){let i=this._map.get(e);i||this._map.set(e,i=[]),i.push(...t)}has(e){return this._map.has(e)}clear(){this._map.clear()}}const Cd=new RegExp(":enter","g"),Sd=new RegExp(":leave","g");function kd(e,t,i,r,n,s={},o={},a,l,c=[]){return(new xd).buildKeyframes(e,t,i,r,n,s,o,a,l,c)}class xd{buildKeyframes(e,t,i,r,n,s,o,a,l,c=[]){l=l||new wd;const h=new Ad(e,t,l,r,n,c,[]);h.options=a,h.currentTimeline.setStyles([s],null,h.errors,a),ad(this,i,h);const u=h.timelines.filter(e=>e.containsAnimation());if(u.length&&Object.keys(o).length){const e=u[u.length-1];e.allowOnlyTimelineStyles()||e.setStyles([o],null,h.errors,a)}return u.length?u.map(e=>e.buildKeyframes()):[bd(t,[],[],[],0,0,"",!1)]}visitTrigger(e,t){}visitState(e,t){}visitTransition(e,t){}visitAnimateChild(e,t){const i=t.subInstructions.consume(t.element);if(i){const r=t.createSubContext(e.options),n=t.currentTimeline.currentTime,s=this._visitSubInstructions(i,r,r.options);n!=s&&t.transformIntoNewTimeline(s)}t.previousNode=e}visitAnimateRef(e,t){const i=t.createSubContext(e.options);i.transformIntoNewTimeline(),this.visitReference(e.animation,i),t.transformIntoNewTimeline(i.currentTimeline.currentTime),t.previousNode=e}_visitSubInstructions(e,t,i){let r=t.currentTimeline.currentTime;const n=null!=i.duration?qu(i.duration):null,s=null!=i.delay?qu(i.delay):null;return 0!==n&&e.forEach(e=>{const i=t.appendInstructionToTimeline(e,n,s);r=Math.max(r,i.duration+i.delay)}),r}visitReference(e,t){t.updateOptions(e.options,!0),ad(this,e.animation,t),t.previousNode=e}visitSequence(e,t){const i=t.subContextCount;let r=t;const n=e.options;if(n&&(n.params||n.delay)&&(r=t.createSubContext(n),r.transformIntoNewTimeline(),null!=n.delay)){6==r.previousNode.type&&(r.currentTimeline.snapshotCurrentStyles(),r.previousNode=Ed);const e=qu(n.delay);r.delayNextStep(e)}e.steps.length&&(e.steps.forEach(e=>ad(this,e,r)),r.currentTimeline.applyStylesToKeyframe(),r.subContextCount>i&&r.transformIntoNewTimeline()),t.previousNode=e}visitGroup(e,t){const i=[];let r=t.currentTimeline.currentTime;const n=e.options&&e.options.delay?qu(e.options.delay):0;e.steps.forEach(s=>{const o=t.createSubContext(e.options);n&&o.delayNextStep(n),ad(this,s,o),r=Math.max(r,o.currentTimeline.currentTime),i.push(o.currentTimeline)}),i.forEach(e=>t.currentTimeline.mergeTimelineCollectedStyles(e)),t.transformIntoNewTimeline(r),t.previousNode=e}_visitTiming(e,t){if(e.dynamic){const i=e.strValue;return Wu(t.params?td(i,t.params,t.errors):i,t.errors)}return{duration:e.duration,delay:e.delay,easing:e.easing}}visitAnimate(e,t){const i=t.currentAnimateTimings=this._visitTiming(e.timings,t),r=t.currentTimeline;i.delay&&(t.incrementTime(i.delay),r.snapshotCurrentStyles());const n=e.style;5==n.type?this.visitKeyframes(n,t):(t.incrementTime(i.duration),this.visitStyle(n,t),r.applyStylesToKeyframe()),t.currentAnimateTimings=null,t.previousNode=e}visitStyle(e,t){const i=t.currentTimeline,r=t.currentAnimateTimings;!r&&i.getCurrentStyleProperties().length&&i.forwardFrame();const n=r&&r.easing||e.easing;e.isEmptyStep?i.applyEmptyStep(n):i.setStyles(e.styles,n,t.errors,t.options),t.previousNode=e}visitKeyframes(e,t){const i=t.currentAnimateTimings,r=t.currentTimeline.duration,n=i.duration,s=t.createSubContext().currentTimeline;s.easing=i.easing,e.styles.forEach(e=>{s.forwardTime((e.offset||0)*n),s.setStyles(e.styles,e.easing,t.errors,t.options),s.applyStylesToKeyframe()}),t.currentTimeline.mergeTimelineCollectedStyles(s),t.transformIntoNewTimeline(r+n),t.previousNode=e}visitQuery(e,t){const i=t.currentTimeline.currentTime,r=e.options||{},n=r.delay?qu(r.delay):0;n&&(6===t.previousNode.type||0==i&&t.currentTimeline.getCurrentStyleProperties().length)&&(t.currentTimeline.snapshotCurrentStyles(),t.previousNode=Ed);let s=i;const o=t.invokeQuery(e.selector,e.originalSelector,e.limit,e.includeSelf,!!r.optional,t.errors);t.currentQueryTotal=o.length;let a=null;o.forEach((i,r)=>{t.currentQueryIndex=r;const o=t.createSubContext(e.options,i);n&&o.delayNextStep(n),i===t.element&&(a=o.currentTimeline),ad(this,e.animation,o),o.currentTimeline.applyStylesToKeyframe(),s=Math.max(s,o.currentTimeline.currentTime)}),t.currentQueryIndex=0,t.currentQueryTotal=0,t.transformIntoNewTimeline(s),a&&(t.currentTimeline.mergeTimelineCollectedStyles(a),t.currentTimeline.snapshotCurrentStyles()),t.previousNode=e}visitStagger(e,t){const i=t.parentContext,r=t.currentTimeline,n=e.timings,s=Math.abs(n.duration),o=s*(t.currentQueryTotal-1);let a=s*t.currentQueryIndex;switch(n.duration<0?"reverse":n.easing){case"reverse":a=o-a;break;case"full":a=i.currentStaggerTime}const l=t.currentTimeline;a&&l.delayNextStep(a);const c=l.currentTime;ad(this,e.animation,t),t.previousNode=e,i.currentStaggerTime=r.currentTime-c+(r.startTime-i.currentTimeline.startTime)}}const Ed={};class Ad{constructor(e,t,i,r,n,s,o,a){this._driver=e,this.element=t,this.subInstructions=i,this._enterClassName=r,this._leaveClassName=n,this.errors=s,this.timelines=o,this.parentContext=null,this.currentAnimateTimings=null,this.previousNode=Ed,this.subContextCount=0,this.options={},this.currentQueryIndex=0,this.currentQueryTotal=0,this.currentStaggerTime=0,this.currentTimeline=a||new Od(this._driver,t,0),o.push(this.currentTimeline)}get params(){return this.options.params}updateOptions(e,t){if(!e)return;const i=e;let r=this.options;null!=i.duration&&(r.duration=qu(i.duration)),null!=i.delay&&(r.delay=qu(i.delay));const n=i.params;if(n){let e=r.params;e||(e=this.options.params={}),Object.keys(n).forEach(i=>{t&&e.hasOwnProperty(i)||(e[i]=td(n[i],e,this.errors))})}}_copyOptions(){const e={};if(this.options){const t=this.options.params;if(t){const i=e.params={};Object.keys(t).forEach(e=>{i[e]=t[e]})}}return e}createSubContext(e=null,t,i){const r=t||this.element,n=new Ad(this._driver,r,this.subInstructions,this._enterClassName,this._leaveClassName,this.errors,this.timelines,this.currentTimeline.fork(r,i||0));return n.previousNode=this.previousNode,n.currentAnimateTimings=this.currentAnimateTimings,n.options=this._copyOptions(),n.updateOptions(e),n.currentQueryIndex=this.currentQueryIndex,n.currentQueryTotal=this.currentQueryTotal,n.parentContext=this,this.subContextCount++,n}transformIntoNewTimeline(e){return this.previousNode=Ed,this.currentTimeline=this.currentTimeline.fork(this.element,e),this.timelines.push(this.currentTimeline),this.currentTimeline}appendInstructionToTimeline(e,t,i){const r={duration:null!=t?t:e.duration,delay:this.currentTimeline.currentTime+(null!=i?i:0)+e.delay,easing:""},n=new Td(this._driver,e.element,e.keyframes,e.preStyleProps,e.postStyleProps,r,e.stretchStartingKeyframe);return this.timelines.push(n),r}incrementTime(e){this.currentTimeline.forwardTime(this.currentTimeline.duration+e)}delayNextStep(e){e>0&&this.currentTimeline.delayNextStep(e)}invokeQuery(e,t,i,r,n,s){let o=[];if(r&&o.push(this.element),e.length>0){e=(e=e.replace(Cd,"."+this._enterClassName)).replace(Sd,"."+this._leaveClassName);let t=this._driver.query(this.element,e,1!=i);0!==i&&(t=i<0?t.slice(t.length+i,t.length):t.slice(0,i)),o.push(...t)}return n||0!=o.length||s.push(`\`query("${t}")\` returned zero elements. (Use \`query("${t}", { optional: true })\` if you wish to allow this.)`),o}}class Od{constructor(e,t,i,r){this._driver=e,this.element=t,this.startTime=i,this._elementTimelineStylesLookup=r,this.duration=0,this._previousKeyframe={},this._currentKeyframe={},this._keyframes=new Map,this._styleSummary={},this._pendingStyles={},this._backFill={},this._currentEmptyStepKeyframe=null,this._elementTimelineStylesLookup||(this._elementTimelineStylesLookup=new Map),this._localTimelineStyles=Object.create(this._backFill,{}),this._globalTimelineStyles=this._elementTimelineStylesLookup.get(t),this._globalTimelineStyles||(this._globalTimelineStyles=this._localTimelineStyles,this._elementTimelineStylesLookup.set(t,this._localTimelineStyles)),this._loadKeyframe()}containsAnimation(){switch(this._keyframes.size){case 0:return!1;case 1:return this.getCurrentStyleProperties().length>0;default:return!0}}getCurrentStyleProperties(){return Object.keys(this._currentKeyframe)}get currentTime(){return this.startTime+this.duration}delayNextStep(e){const t=1==this._keyframes.size&&Object.keys(this._pendingStyles).length;this.duration||t?(this.forwardTime(this.currentTime+e),t&&this.snapshotCurrentStyles()):this.startTime+=e}fork(e,t){return this.applyStylesToKeyframe(),new Od(this._driver,e,t||this.currentTime,this._elementTimelineStylesLookup)}_loadKeyframe(){this._currentKeyframe&&(this._previousKeyframe=this._currentKeyframe),this._currentKeyframe=this._keyframes.get(this.duration),this._currentKeyframe||(this._currentKeyframe=Object.create(this._backFill,{}),this._keyframes.set(this.duration,this._currentKeyframe))}forwardFrame(){this.duration+=1,this._loadKeyframe()}forwardTime(e){this.applyStylesToKeyframe(),this.duration=e,this._loadKeyframe()}_updateStyle(e,t){this._localTimelineStyles[e]=t,this._globalTimelineStyles[e]=t,this._styleSummary[e]={time:this.currentTime,value:t}}allowOnlyTimelineStyles(){return this._currentEmptyStepKeyframe!==this._currentKeyframe}applyEmptyStep(e){e&&(this._previousKeyframe.easing=e),Object.keys(this._globalTimelineStyles).forEach(e=>{this._backFill[e]=this._globalTimelineStyles[e]||"*",this._currentKeyframe[e]="*"}),this._currentEmptyStepKeyframe=this._currentKeyframe}setStyles(e,t,i,r){t&&(this._previousKeyframe.easing=t);const n=r&&r.params||{},s=function(e,t){const i={};let r;return e.forEach(e=>{"*"===e?(r=r||Object.keys(t),r.forEach(e=>{i[e]="*"})):Ku(e,!1,i)}),i}(e,this._globalTimelineStyles);Object.keys(s).forEach(e=>{const t=td(s[e],n,i);this._pendingStyles[e]=t,this._localTimelineStyles.hasOwnProperty(e)||(this._backFill[e]=this._globalTimelineStyles.hasOwnProperty(e)?this._globalTimelineStyles[e]:"*"),this._updateStyle(e,t)})}applyStylesToKeyframe(){const e=this._pendingStyles,t=Object.keys(e);0!=t.length&&(this._pendingStyles={},t.forEach(t=>{this._currentKeyframe[t]=e[t]}),Object.keys(this._localTimelineStyles).forEach(e=>{this._currentKeyframe.hasOwnProperty(e)||(this._currentKeyframe[e]=this._localTimelineStyles[e])}))}snapshotCurrentStyles(){Object.keys(this._localTimelineStyles).forEach(e=>{const t=this._localTimelineStyles[e];this._pendingStyles[e]=t,this._updateStyle(e,t)})}getFinalKeyframe(){return this._keyframes.get(this.duration)}get properties(){const e=[];for(let t in this._currentKeyframe)e.push(t);return e}mergeTimelineCollectedStyles(e){Object.keys(e._styleSummary).forEach(t=>{const i=this._styleSummary[t],r=e._styleSummary[t];(!i||r.time>i.time)&&this._updateStyle(t,r.value)})}buildKeyframes(){this.applyStylesToKeyframe();const e=new Set,t=new Set,i=1===this._keyframes.size&&0===this.duration;let r=[];this._keyframes.forEach((n,s)=>{const o=Ku(n,!0);Object.keys(o).forEach(i=>{const r=o[i];"!"==r?e.add(i):"*"==r&&t.add(i)}),i||(o.offset=s/this.duration),r.push(o)});const n=e.size?id(e.values()):[],s=t.size?id(t.values()):[];if(i){const e=r[0],t=$u(e);e.offset=0,t.offset=1,r=[e,t]}return bd(this.element,r,n,s,this.duration,this.startTime,this.easing,!1)}}class Td extends Od{constructor(e,t,i,r,n,s,o=!1){super(e,t,s.delay),this.element=t,this.keyframes=i,this.preStyleProps=r,this.postStyleProps=n,this._stretchStartingKeyframe=o,this.timings={duration:s.duration,delay:s.delay,easing:s.easing}}containsAnimation(){return this.keyframes.length>1}buildKeyframes(){let e=this.keyframes,{delay:t,duration:i,easing:r}=this.timings;if(this._stretchStartingKeyframe&&t){const n=[],s=i+t,o=t/s,a=Ku(e[0],!1);a.offset=0,n.push(a);const l=Ku(e[0],!1);l.offset=Rd(o),n.push(l);const c=e.length-1;for(let r=1;r<=c;r++){let o=Ku(e[r],!1);o.offset=Rd((t+o.offset*i)/s),n.push(o)}i=s,t=0,r="",e=n}return bd(this.element,e,this.preStyleProps,this.postStyleProps,i,t,r,!0)}}function Rd(e,t=3){const i=Math.pow(10,t-1);return Math.round(e*i)/i}class Ld{}class Pd extends Ld{normalizePropertyName(e,t){return nd(e)}normalizeStyleValue(e,t,i,r){let n="";const s=i.toString().trim();if(Dd[t]&&0!==i&&"0"!==i)if("number"==typeof i)n="px";else{const t=i.match(/^[+-]?[\d\.]+([a-z]*)$/);t&&0==t[1].length&&r.push(`Please provide a CSS unit value for ${e}:${i}`)}return s+n}}const Dd=(()=>function(e){const t={};return e.forEach(e=>t[e]=!0),t}("width,height,minWidth,minHeight,maxWidth,maxHeight,left,top,bottom,right,fontSize,outlineWidth,outlineOffset,paddingTop,paddingLeft,paddingBottom,paddingRight,marginTop,marginLeft,marginBottom,marginRight,borderRadius,borderWidth,borderTopWidth,borderLeftWidth,borderRightWidth,borderBottomWidth,textIndent,perspective".split(",")))();function Id(e,t,i,r,n,s,o,a,l,c,h,u,d){return{type:0,element:e,triggerName:t,isRemovalTransition:n,fromState:i,fromStyles:s,toState:r,toStyles:o,timelines:a,queriedElements:l,preStyleProps:c,postStyleProps:h,totalTime:u,errors:d}}const Md={};class Fd{constructor(e,t,i){this._triggerName=e,this.ast=t,this._stateStyles=i}match(e,t,i,r){return function(e,t,i,r,n){return e.some(e=>e(t,i,r,n))}(this.ast.matchers,e,t,i,r)}buildStyles(e,t,i){const r=this._stateStyles["*"],n=this._stateStyles[e],s=r?r.buildStyles(t,i):{};return n?n.buildStyles(t,i):s}build(e,t,i,r,n,s,o,a,l,c){const h=[],u=this.ast.options&&this.ast.options.params||Md,d=this.buildStyles(i,o&&o.params||Md,h),f=a&&a.params||Md,p=this.buildStyles(r,f,h),_=new Set,m=new Map,g=new Map,v="void"===r,y={params:Object.assign(Object.assign({},u),f)},b=c?[]:kd(e,t,this.ast.animation,n,s,d,p,y,l,h);let w=0;if(b.forEach(e=>{w=Math.max(e.duration+e.delay,w)}),h.length)return Id(t,this._triggerName,i,r,v,d,p,[],[],m,g,w,h);b.forEach(e=>{const i=e.element,r=Ou(m,i,{});e.preStyleProps.forEach(e=>r[e]=!0);const n=Ou(g,i,{});e.postStyleProps.forEach(e=>n[e]=!0),i!==t&&_.add(i)});const C=id(_.values());return Id(t,this._triggerName,i,r,v,d,p,b,C,m,g,w)}}class Nd{constructor(e,t){this.styles=e,this.defaultParams=t}buildStyles(e,t){const i={},r=$u(this.defaultParams);return Object.keys(e).forEach(t=>{const i=e[t];null!=i&&(r[t]=i)}),this.styles.styles.forEach(e=>{if("string"!=typeof e){const n=e;Object.keys(n).forEach(e=>{let s=n[e];s.length>1&&(s=td(s,r,t)),i[e]=s})}}),i}}class jd{constructor(e,t){this.name=e,this.ast=t,this.transitionFactories=[],this.states={},t.states.forEach(e=>{this.states[e.name]=new Nd(e.style,e.options&&e.options.params||{})}),Bd(this.states,"true","1"),Bd(this.states,"false","0"),t.transitions.forEach(t=>{this.transitionFactories.push(new Fd(e,t,this.states))}),this.fallbackTransition=new Fd(e,{type:1,animation:{type:2,steps:[],options:null},matchers:[(e,t)=>!0],options:null,queryCount:0,depCount:0},this.states)}get containsQueries(){return this.ast.queryCount>0}matchTransition(e,t,i,r){return this.transitionFactories.find(n=>n.match(e,t,i,r))||null}matchStyles(e,t,i){return this.fallbackTransition.buildStyles(e,t,i)}}function Bd(e,t,i){e.hasOwnProperty(t)?e.hasOwnProperty(i)||(e[i]=e[t]):e.hasOwnProperty(i)&&(e[t]=e[i])}const Hd=new wd;class Ud{constructor(e,t,i){this.bodyNode=e,this._driver=t,this._normalizer=i,this._animations={},this._playersById={},this.players=[]}register(e,t){const i=[],r=pd(this._driver,t,i);if(i.length)throw new Error("Unable to build the animation due to the following errors: "+i.join("\n"));this._animations[e]=r}_buildPlayer(e,t,i){const r=e.element,n=ku(0,this._normalizer,0,e.keyframes,t,i);return this._driver.animate(r,n,e.duration,e.delay,e.easing,[],!0)}create(e,t,i={}){const r=[],n=this._animations[e];let s;const o=new Map;if(n?(s=kd(this._driver,t,n,"ng-enter","ng-leave",{},{},i,Hd,r),s.forEach(e=>{const t=Ou(o,e.element,{});e.postStyleProps.forEach(e=>t[e]=null)})):(r.push("The requested animation doesn't exist or has already been destroyed"),s=[]),r.length)throw new Error("Unable to create the animation due to the following errors: "+r.join("\n"));o.forEach((e,t)=>{Object.keys(e).forEach(i=>{e[i]=this._driver.computeStyle(t,i,"*")})});const a=Su(s.map(e=>{const t=o.get(e.element);return this._buildPlayer(e,{},t)}));return this._playersById[e]=a,a.onDestroy(()=>this.destroy(e)),this.players.push(a),a}destroy(e){const t=this._getPlayer(e);t.destroy(),delete this._playersById[e];const i=this.players.indexOf(t);i>=0&&this.players.splice(i,1)}_getPlayer(e){const t=this._playersById[e];if(!t)throw new Error("Unable to find the timeline player referenced by "+e);return t}listen(e,t,i,r){const n=Au(t,"","","");return xu(this._getPlayer(e),i,n,r),()=>{}}command(e,t,i,r){if("register"==i)return void this.register(e,r[0]);if("create"==i)return void this.create(e,t,r[0]||{});const n=this._getPlayer(e);switch(i){case"play":n.play();break;case"pause":n.pause();break;case"reset":n.reset();break;case"restart":n.restart();break;case"finish":n.finish();break;case"init":n.init();break;case"setPosition":n.setPosition(parseFloat(r[0]));break;case"destroy":this.destroy(e)}}}const Vd=[],qd={namespaceId:"",setForRemoval:!1,setForMove:!1,hasAnimation:!1,removedBeforeQueried:!1},zd={namespaceId:"",setForMove:!1,setForRemoval:!1,hasAnimation:!1,removedBeforeQueried:!0};class Wd{constructor(e,t=""){this.namespaceId=t;const i=e&&e.hasOwnProperty("value");if(this.value=null!=(r=i?e.value:e)?r:null,i){const t=$u(e);delete t.value,this.options=t}else this.options={};var r;this.options.params||(this.options.params={})}get params(){return this.options.params}absorbOptions(e){const t=e.params;if(t){const e=this.options.params;Object.keys(t).forEach(i=>{null==e[i]&&(e[i]=t[i])})}}}const $d=new Wd("void");class Kd{constructor(e,t,i){this.id=e,this.hostElement=t,this._engine=i,this.players=[],this._triggers={},this._queue=[],this._elementListeners=new Map,this._hostClassName="ng-tns-"+e,ef(t,this._hostClassName)}listen(e,t,i,r){if(!this._triggers.hasOwnProperty(t))throw new Error(`Unable to listen on the animation trigger event "${i}" because the animation trigger "${t}" doesn't exist!`);if(null==i||0==i.length)throw new Error(`Unable to listen on the animation trigger "${t}" because the provided event is undefined!`);if("start"!=(n=i)&&"done"!=n)throw new Error(`The provided animation trigger event "${i}" for the animation trigger "${t}" is not supported!`);var n;const s=Ou(this._elementListeners,e,[]),o={name:t,phase:i,callback:r};s.push(o);const a=Ou(this._engine.statesByElement,e,{});return a.hasOwnProperty(t)||(ef(e,"ng-trigger"),ef(e,"ng-trigger-"+t),a[t]=$d),()=>{this._engine.afterFlush(()=>{const e=s.indexOf(o);e>=0&&s.splice(e,1),this._triggers[t]||delete a[t]})}}register(e,t){return!this._triggers[e]&&(this._triggers[e]=t,!0)}_getTrigger(e){const t=this._triggers[e];if(!t)throw new Error(`The provided animation trigger "${e}" has not been registered!`);return t}trigger(e,t,i,r=!0){const n=this._getTrigger(t),s=new Zd(this.id,t,e);let o=this._engine.statesByElement.get(e);o||(ef(e,"ng-trigger"),ef(e,"ng-trigger-"+t),this._engine.statesByElement.set(e,o={}));let a=o[t];const l=new Wd(i,this.id);if(!(i&&i.hasOwnProperty("value"))&&a&&l.absorbOptions(a.options),o[t]=l,a||(a=$d),"void"!==l.value&&a.value===l.value){if(!function(e,t){const i=Object.keys(e),r=Object.keys(t);if(i.length!=r.length)return!1;for(let n=0;n{Xu(e,i),Yu(e,r)})}return}const c=Ou(this._engine.playersByElement,e,[]);c.forEach(e=>{e.namespaceId==this.id&&e.triggerName==t&&e.queued&&e.destroy()});let h=n.matchTransition(a.value,l.value,e,l.params),u=!1;if(!h){if(!r)return;h=n.fallbackTransition,u=!0}return this._engine.totalQueuedPlayers++,this._queue.push({element:e,triggerName:t,transition:h,fromState:a,toState:l,player:s,isFallbackTransition:u}),u||(ef(e,"ng-animate-queued"),s.onStart(()=>{tf(e,"ng-animate-queued")})),s.onDone(()=>{let t=this.players.indexOf(s);t>=0&&this.players.splice(t,1);const i=this._engine.playersByElement.get(e);if(i){let e=i.indexOf(s);e>=0&&i.splice(e,1)}}),this.players.push(s),c.push(s),s}deregister(e){delete this._triggers[e],this._engine.statesByElement.forEach((t,i)=>{delete t[e]}),this._elementListeners.forEach((t,i)=>{this._elementListeners.set(i,t.filter(t=>t.name!=e))})}clearElementCache(e){this._engine.statesByElement.delete(e),this._elementListeners.delete(e);const t=this._engine.playersByElement.get(e);t&&(t.forEach(e=>e.destroy()),this._engine.playersByElement.delete(e))}_signalRemovalForInnerTriggers(e,t){const i=this._engine.driver.query(e,".ng-trigger",!0);i.forEach(e=>{if(e.__ng_removed)return;const i=this._engine.fetchNamespacesByElement(e);i.size?i.forEach(i=>i.triggerLeaveAnimation(e,t,!1,!0)):this.clearElementCache(e)}),this._engine.afterFlushAnimationsDone(()=>i.forEach(e=>this.clearElementCache(e)))}triggerLeaveAnimation(e,t,i,r){const n=this._engine.statesByElement.get(e);if(n){const s=[];if(Object.keys(n).forEach(t=>{if(this._triggers[t]){const i=this.trigger(e,t,"void",r);i&&s.push(i)}}),s.length)return this._engine.markElementAsRemoved(this.id,e,!0,t),i&&Su(s).onDone(()=>this._engine.processLeaveNode(e)),!0}return!1}prepareLeaveAnimationListeners(e){const t=this._elementListeners.get(e);if(t){const i=new Set;t.forEach(t=>{const r=t.name;if(i.has(r))return;i.add(r);const n=this._triggers[r].fallbackTransition,s=this._engine.statesByElement.get(e)[r]||$d,o=new Wd("void"),a=new Zd(this.id,r,e);this._engine.totalQueuedPlayers++,this._queue.push({element:e,triggerName:r,transition:n,fromState:s,toState:o,player:a,isFallbackTransition:!0})})}}removeNode(e,t){const i=this._engine;if(e.childElementCount&&this._signalRemovalForInnerTriggers(e,t),this.triggerLeaveAnimation(e,t,!0))return;let r=!1;if(i.totalAnimations){const t=i.players.length?i.playersByQueriedElement.get(e):[];if(t&&t.length)r=!0;else{let t=e;for(;t=t.parentNode;)if(i.statesByElement.get(t)){r=!0;break}}}if(this.prepareLeaveAnimationListeners(e),r)i.markElementAsRemoved(this.id,e,!1,t);else{const r=e.__ng_removed;r&&r!==qd||(i.afterFlush(()=>this.clearElementCache(e)),i.destroyInnerAnimations(e),i._onRemovalComplete(e,t))}}insertNode(e,t){ef(e,this._hostClassName)}drainQueuedTransitions(e){const t=[];return this._queue.forEach(i=>{const r=i.player;if(r.destroyed)return;const n=i.element,s=this._elementListeners.get(n);s&&s.forEach(t=>{if(t.name==i.triggerName){const r=Au(n,i.triggerName,i.fromState.value,i.toState.value);r._data=e,xu(i.player,t.phase,r,t.callback)}}),r.markedForDestroy?this._engine.afterFlush(()=>{r.destroy()}):t.push(i)}),this._queue=[],t.sort((e,t)=>{const i=e.transition.ast.depCount,r=t.transition.ast.depCount;return 0==i||0==r?i-r:this._engine.driver.containsElement(e.element,t.element)?1:-1})}destroy(e){this.players.forEach(e=>e.destroy()),this._signalRemovalForInnerTriggers(this.hostElement,e)}elementContainsData(e){let t=!1;return this._elementListeners.has(e)&&(t=!0),t=!!this._queue.find(t=>t.element===e)||t,t}}class Gd{constructor(e,t,i){this.bodyNode=e,this.driver=t,this._normalizer=i,this.players=[],this.newHostElements=new Map,this.playersByElement=new Map,this.playersByQueriedElement=new Map,this.statesByElement=new Map,this.disabledNodes=new Set,this.totalAnimations=0,this.totalQueuedPlayers=0,this._namespaceLookup={},this._namespaceList=[],this._flushFns=[],this._whenQuietFns=[],this.namespacesByHostElement=new Map,this.collectedEnterElements=[],this.collectedLeaveElements=[],this.onRemovalComplete=(e,t)=>{}}_onRemovalComplete(e,t){this.onRemovalComplete(e,t)}get queuedPlayers(){const e=[];return this._namespaceList.forEach(t=>{t.players.forEach(t=>{t.queued&&e.push(t)})}),e}createNamespace(e,t){const i=new Kd(e,t,this);return t.parentNode?this._balanceNamespaceList(i,t):(this.newHostElements.set(t,i),this.collectEnterElement(t)),this._namespaceLookup[e]=i}_balanceNamespaceList(e,t){const i=this._namespaceList.length-1;if(i>=0){let r=!1;for(let n=i;n>=0;n--)if(this.driver.containsElement(this._namespaceList[n].hostElement,t)){this._namespaceList.splice(n+1,0,e),r=!0;break}r||this._namespaceList.splice(0,0,e)}else this._namespaceList.push(e);return this.namespacesByHostElement.set(t,e),e}register(e,t){let i=this._namespaceLookup[e];return i||(i=this.createNamespace(e,t)),i}registerTrigger(e,t,i){let r=this._namespaceLookup[e];r&&r.register(t,i)&&this.totalAnimations++}destroy(e,t){if(!e)return;const i=this._fetchNamespace(e);this.afterFlush(()=>{this.namespacesByHostElement.delete(i.hostElement),delete this._namespaceLookup[e];const t=this._namespaceList.indexOf(i);t>=0&&this._namespaceList.splice(t,1)}),this.afterFlushAnimationsDone(()=>i.destroy(t))}_fetchNamespace(e){return this._namespaceLookup[e]}fetchNamespacesByElement(e){const t=new Set,i=this.statesByElement.get(e);if(i){const e=Object.keys(i);for(let r=0;r=0&&this.collectedLeaveElements.splice(e,1)}if(e){const r=this._fetchNamespace(e);r&&r.insertNode(t,i)}r&&this.collectEnterElement(t)}collectEnterElement(e){this.collectedEnterElements.push(e)}markElementAsDisabled(e,t){t?this.disabledNodes.has(e)||(this.disabledNodes.add(e),ef(e,"ng-animate-disabled")):this.disabledNodes.has(e)&&(this.disabledNodes.delete(e),tf(e,"ng-animate-disabled"))}removeNode(e,t,i,r){if(Yd(t)){const n=e?this._fetchNamespace(e):null;if(n?n.removeNode(t,r):this.markElementAsRemoved(e,t,!1,r),i){const i=this.namespacesByHostElement.get(t);i&&i.id!==e&&i.removeNode(t,r)}}else this._onRemovalComplete(t,r)}markElementAsRemoved(e,t,i,r){this.collectedLeaveElements.push(t),t.__ng_removed={namespaceId:e,setForRemoval:r,hasAnimation:i,removedBeforeQueried:!1}}listen(e,t,i,r,n){return Yd(t)?this._fetchNamespace(e).listen(t,i,r,n):()=>{}}_buildInstruction(e,t,i,r,n){return e.transition.build(this.driver,e.element,e.fromState.value,e.toState.value,i,r,e.fromState.options,e.toState.options,t,n)}destroyInnerAnimations(e){let t=this.driver.query(e,".ng-trigger",!0);t.forEach(e=>this.destroyActiveAnimationsForElement(e)),0!=this.playersByQueriedElement.size&&(t=this.driver.query(e,".ng-animating",!0),t.forEach(e=>this.finishActiveQueriedAnimationOnElement(e)))}destroyActiveAnimationsForElement(e){const t=this.playersByElement.get(e);t&&t.forEach(e=>{e.queued?e.markedForDestroy=!0:e.destroy()})}finishActiveQueriedAnimationOnElement(e){const t=this.playersByQueriedElement.get(e);t&&t.forEach(e=>e.finish())}whenRenderingDone(){return new Promise(e=>{if(this.players.length)return Su(this.players).onDone(()=>e());e()})}processLeaveNode(e){const t=e.__ng_removed;if(t&&t.setForRemoval){if(e.__ng_removed=qd,t.namespaceId){this.destroyInnerAnimations(e);const i=this._fetchNamespace(t.namespaceId);i&&i.clearElementCache(e)}this._onRemovalComplete(e,t.setForRemoval)}this.driver.matchesElement(e,".ng-animate-disabled")&&this.markElementAsDisabled(e,!1),this.driver.query(e,".ng-animate-disabled",!0).forEach(e=>{this.markElementAsDisabled(e,!1)})}flush(e=-1){let t=[];if(this.newHostElements.size&&(this.newHostElements.forEach((e,t)=>this._balanceNamespaceList(e,t)),this.newHostElements.clear()),this.totalAnimations&&this.collectedEnterElements.length)for(let i=0;ie()),this._flushFns=[],this._whenQuietFns.length){const e=this._whenQuietFns;this._whenQuietFns=[],t.length?Su(t).onDone(()=>{e.forEach(e=>e())}):e.forEach(e=>e())}}reportError(e){throw new Error("Unable to process animations due to the following failed trigger transitions\n "+e.join("\n"))}_flushAnimations(e,t){const i=new wd,r=[],n=new Map,s=[],o=new Map,a=new Map,l=new Map,c=new Set;this.disabledNodes.forEach(e=>{c.add(e);const t=this.driver.query(e,".ng-animate-queued",!0);for(let i=0;i{const i="ng-enter"+p++;f.set(t,i),e.forEach(e=>ef(e,i))});const _=[],m=new Set,g=new Set;for(let R=0;Rm.add(e)):g.add(e))}const v=new Map,y=Jd(u,Array.from(m));y.forEach((e,t)=>{const i="ng-leave"+p++;v.set(t,i),e.forEach(e=>ef(e,i))}),e.push(()=>{d.forEach((e,t)=>{const i=f.get(t);e.forEach(e=>tf(e,i))}),y.forEach((e,t)=>{const i=v.get(t);e.forEach(e=>tf(e,i))}),_.forEach(e=>{this.processLeaveNode(e)})});const b=[],w=[];for(let R=this._namespaceList.length-1;R>=0;R--)this._namespaceList[R].drainQueuedTransitions(t).forEach(e=>{const t=e.player,n=e.element;if(b.push(t),this.collectedEnterElements.length){const e=n.__ng_removed;if(e&&e.setForMove)return void t.destroy()}const c=!h||!this.driver.containsElement(h,n),u=v.get(n),d=f.get(n),p=this._buildInstruction(e,i,d,u,c);if(p.errors&&p.errors.length)w.push(p);else{if(c)return t.onStart(()=>Xu(n,p.fromStyles)),t.onDestroy(()=>Yu(n,p.toStyles)),void r.push(t);if(e.isFallbackTransition)return t.onStart(()=>Xu(n,p.fromStyles)),t.onDestroy(()=>Yu(n,p.toStyles)),void r.push(t);p.timelines.forEach(e=>e.stretchStartingKeyframe=!0),i.append(n,p.timelines),s.push({instruction:p,player:t,element:n}),p.queriedElements.forEach(e=>Ou(o,e,[]).push(t)),p.preStyleProps.forEach((e,t)=>{const i=Object.keys(e);if(i.length){let e=a.get(t);e||a.set(t,e=new Set),i.forEach(t=>e.add(t))}}),p.postStyleProps.forEach((e,t)=>{const i=Object.keys(e);let r=l.get(t);r||l.set(t,r=new Set),i.forEach(e=>r.add(e))})}});if(w.length){const e=[];w.forEach(t=>{e.push(`@${t.triggerName} has failed due to:\n`),t.errors.forEach(t=>e.push(`- ${t}\n`))}),b.forEach(e=>e.destroy()),this.reportError(e)}const C=new Map,S=new Map;s.forEach(e=>{const t=e.element;i.has(t)&&(S.set(t,t),this._beforeAnimationBuild(e.player.namespaceId,e.instruction,C))}),r.forEach(e=>{const t=e.element;this._getPreviousPlayers(t,!1,e.namespaceId,e.triggerName,null).forEach(e=>{Ou(C,t,[]).push(e),e.destroy()})});const k=_.filter(e=>nf(e,a,l)),x=new Map;Qd(x,this.driver,g,l,"*").forEach(e=>{nf(e,a,l)&&k.push(e)});const E=new Map;d.forEach((e,t)=>{Qd(E,this.driver,new Set(e),a,"!")}),k.forEach(e=>{const t=x.get(e),i=E.get(e);x.set(e,Object.assign(Object.assign({},t),i))});const A=[],O=[],T={};s.forEach(e=>{const{element:t,player:s,instruction:o}=e;if(i.has(t)){if(c.has(t))return s.onDestroy(()=>Yu(t,o.toStyles)),s.disabled=!0,s.overrideTotalTime(o.totalTime),void r.push(s);let e=T;if(S.size>1){let i=t;const r=[];for(;i=i.parentNode;){const t=S.get(i);if(t){e=t;break}r.push(i)}r.forEach(t=>S.set(t,e))}const i=this._buildAnimation(s.namespaceId,o,C,n,E,x);if(s.setRealPlayer(i),e===T)A.push(s);else{const t=this.playersByElement.get(e);t&&t.length&&(s.parentPlayer=Su(t)),r.push(s)}}else Xu(t,o.fromStyles),s.onDestroy(()=>Yu(t,o.toStyles)),O.push(s),c.has(t)&&r.push(s)}),O.forEach(e=>{const t=n.get(e.element);if(t&&t.length){const i=Su(t);e.setRealPlayer(i)}}),r.forEach(e=>{e.parentPlayer?e.syncPlayerEvents(e.parentPlayer):e.destroy()});for(let R=0;R<_.length;R++){const e=_[R],t=e.__ng_removed;if(tf(e,"ng-leave"),t&&t.hasAnimation)continue;let i=[];if(o.size){let t=o.get(e);t&&t.length&&i.push(...t);let r=this.driver.query(e,".ng-animating",!0);for(let e=0;e!e.destroyed);r.length?rf(this,e,r):this.processLeaveNode(e)}return _.length=0,A.forEach(e=>{this.players.push(e),e.onDone(()=>{e.destroy();const t=this.players.indexOf(e);this.players.splice(t,1)}),e.play()}),A}elementContainsData(e,t){let i=!1;const r=t.__ng_removed;return r&&r.setForRemoval&&(i=!0),this.playersByElement.has(t)&&(i=!0),this.playersByQueriedElement.has(t)&&(i=!0),this.statesByElement.has(t)&&(i=!0),this._fetchNamespace(e).elementContainsData(t)||i}afterFlush(e){this._flushFns.push(e)}afterFlushAnimationsDone(e){this._whenQuietFns.push(e)}_getPreviousPlayers(e,t,i,r,n){let s=[];if(t){const t=this.playersByQueriedElement.get(e);t&&(s=t)}else{const t=this.playersByElement.get(e);if(t){const e=!n||"void"==n;t.forEach(t=>{t.queued||(e||t.triggerName==r)&&s.push(t)})}}return(i||r)&&(s=s.filter(e=>!(i&&i!=e.namespaceId||r&&r!=e.triggerName))),s}_beforeAnimationBuild(e,t,i){const r=t.element,n=t.isRemovalTransition?void 0:e,s=t.isRemovalTransition?void 0:t.triggerName;for(const o of t.timelines){const e=o.element,a=e!==r,l=Ou(i,e,[]);this._getPreviousPlayers(e,a,n,s,t.toState).forEach(e=>{const t=e.getRealPlayer();t.beforeDestroy&&t.beforeDestroy(),e.destroy(),l.push(e)})}Xu(r,t.fromStyles)}_buildAnimation(e,t,i,r,n,s){const o=t.triggerName,a=t.element,l=[],c=new Set,h=new Set,u=t.timelines.map(t=>{const u=t.element;c.add(u);const d=u.__ng_removed;if(d&&d.removedBeforeQueried)return new bu(t.duration,t.delay);const f=u!==a,p=function(e){const t=[];return function e(t,i){for(let r=0;re.getRealPlayer())).filter(e=>!!e.element&&e.element===u),_=n.get(u),m=s.get(u),g=ku(0,this._normalizer,0,t.keyframes,_,m),v=this._buildPlayer(t,g,p);if(t.subTimeline&&r&&h.add(u),f){const t=new Zd(e,o,u);t.setRealPlayer(v),l.push(t)}return v});l.forEach(e=>{Ou(this.playersByQueriedElement,e.element,[]).push(e),e.onDone(()=>function(e,t,i){let r;if(e instanceof Map){if(r=e.get(t),r){if(r.length){const e=r.indexOf(i);r.splice(e,1)}0==r.length&&e.delete(t)}}else if(r=e[t],r){if(r.length){const e=r.indexOf(i);r.splice(e,1)}0==r.length&&delete e[t]}return r}(this.playersByQueriedElement,e.element,e))}),c.forEach(e=>ef(e,"ng-animating"));const d=Su(u);return d.onDestroy(()=>{c.forEach(e=>tf(e,"ng-animating")),Yu(a,t.toStyles)}),h.forEach(e=>{Ou(r,e,[]).push(d)}),d}_buildPlayer(e,t,i){return t.length>0?this.driver.animate(e.element,t,e.duration,e.delay,e.easing,i):new bu(e.duration,e.delay)}}class Zd{constructor(e,t,i){this.namespaceId=e,this.triggerName=t,this.element=i,this._player=new bu,this._containsRealPlayer=!1,this._queuedCallbacks={},this.destroyed=!1,this.markedForDestroy=!1,this.disabled=!1,this.queued=!0,this.totalTime=0}setRealPlayer(e){this._containsRealPlayer||(this._player=e,Object.keys(this._queuedCallbacks).forEach(t=>{this._queuedCallbacks[t].forEach(i=>xu(e,t,void 0,i))}),this._queuedCallbacks={},this._containsRealPlayer=!0,this.overrideTotalTime(e.totalTime),this.queued=!1)}getRealPlayer(){return this._player}overrideTotalTime(e){this.totalTime=e}syncPlayerEvents(e){const t=this._player;t.triggerCallback&&e.onStart(()=>t.triggerCallback("start")),e.onDone(()=>this.finish()),e.onDestroy(()=>this.destroy())}_queueEvent(e,t){Ou(this._queuedCallbacks,e,[]).push(t)}onDone(e){this.queued&&this._queueEvent("done",e),this._player.onDone(e)}onStart(e){this.queued&&this._queueEvent("start",e),this._player.onStart(e)}onDestroy(e){this.queued&&this._queueEvent("destroy",e),this._player.onDestroy(e)}init(){this._player.init()}hasStarted(){return!this.queued&&this._player.hasStarted()}play(){!this.queued&&this._player.play()}pause(){!this.queued&&this._player.pause()}restart(){!this.queued&&this._player.restart()}finish(){this._player.finish()}destroy(){this.destroyed=!0,this._player.destroy()}reset(){!this.queued&&this._player.reset()}setPosition(e){this.queued||this._player.setPosition(e)}getPosition(){return this.queued?0:this._player.getPosition()}triggerCallback(e){const t=this._player;t.triggerCallback&&t.triggerCallback(e)}}function Yd(e){return e&&1===e.nodeType}function Xd(e,t){const i=e.style.display;return e.style.display=null!=t?t:"none",i}function Qd(e,t,i,r,n){const s=[];i.forEach(e=>s.push(Xd(e)));const o=[];r.forEach((i,r)=>{const s={};i.forEach(e=>{const i=s[e]=t.computeStyle(r,e,n);i&&0!=i.length||(r.__ng_removed=zd,o.push(r))}),e.set(r,s)});let a=0;return i.forEach(e=>Xd(e,s[a++])),o}function Jd(e,t){const i=new Map;if(e.forEach(e=>i.set(e,[])),0==t.length)return i;const r=new Set(t),n=new Map;return t.forEach(e=>{const t=function e(t){if(!t)return 1;let s=n.get(t);if(s)return s;const o=t.parentNode;return s=i.has(o)?o:r.has(o)?1:e(o),n.set(t,s),s}(e);1!==t&&i.get(t).push(e)}),i}function ef(e,t){if(e.classList)e.classList.add(t);else{let i=e.$$classes;i||(i=e.$$classes={}),i[t]=!0}}function tf(e,t){if(e.classList)e.classList.remove(t);else{let i=e.$$classes;i&&delete i[t]}}function rf(e,t,i){Su(i).onDone(()=>e.processLeaveNode(t))}function nf(e,t,i){const r=i.get(e);if(!r)return!1;let n=t.get(e);return n?r.forEach(e=>n.add(e)):t.set(e,r),i.delete(e),!0}class sf{constructor(e,t,i){this.bodyNode=e,this._driver=t,this._triggerCache={},this.onRemovalComplete=(e,t)=>{},this._transitionEngine=new Gd(e,t,i),this._timelineEngine=new Ud(e,t,i),this._transitionEngine.onRemovalComplete=(e,t)=>this.onRemovalComplete(e,t)}registerTrigger(e,t,i,r,n){const s=e+"-"+r;let o=this._triggerCache[s];if(!o){const e=[],t=pd(this._driver,n,e);if(e.length)throw new Error(`The animation trigger "${r}" has failed to build due to the following errors:\n - ${e.join("\n - ")}`);o=function(e,t){return new jd(e,t)}(r,t),this._triggerCache[s]=o}this._transitionEngine.registerTrigger(t,r,o)}register(e,t){this._transitionEngine.register(e,t)}destroy(e,t){this._transitionEngine.destroy(e,t)}onInsert(e,t,i,r){this._transitionEngine.insertNode(e,t,i,r)}onRemove(e,t,i,r){this._transitionEngine.removeNode(e,t,r||!1,i)}disableAnimations(e,t){this._transitionEngine.markElementAsDisabled(e,t)}process(e,t,i,r){if("@"==i.charAt(0)){const[e,n]=Tu(i);this._timelineEngine.command(e,t,n,r)}else this._transitionEngine.trigger(e,t,i,r)}listen(e,t,i,r,n){if("@"==i.charAt(0)){const[e,r]=Tu(i);return this._timelineEngine.listen(e,t,r,n)}return this._transitionEngine.listen(e,t,i,r,n)}flush(e=-1){this._transitionEngine.flush(e)}get players(){return this._transitionEngine.players.concat(this._timelineEngine.players)}whenRenderingDone(){return this._transitionEngine.whenRenderingDone()}}function of(e,t){let i=null,r=null;return Array.isArray(t)&&t.length?(i=lf(t[0]),t.length>1&&(r=lf(t[t.length-1]))):t&&(i=lf(t)),i||r?new af(e,i,r):null}let af=(()=>{class e{constructor(t,i,r){this._element=t,this._startStyles=i,this._endStyles=r,this._state=0;let n=e.initialStylesByElement.get(t);n||e.initialStylesByElement.set(t,n={}),this._initialStyles=n}start(){this._state<1&&(this._startStyles&&Yu(this._element,this._startStyles,this._initialStyles),this._state=1)}finish(){this.start(),this._state<2&&(Yu(this._element,this._initialStyles),this._endStyles&&(Yu(this._element,this._endStyles),this._endStyles=null),this._state=1)}destroy(){this.finish(),this._state<3&&(e.initialStylesByElement.delete(this._element),this._startStyles&&(Xu(this._element,this._startStyles),this._endStyles=null),this._endStyles&&(Xu(this._element,this._endStyles),this._endStyles=null),Yu(this._element,this._initialStyles),this._state=3)}}return e.initialStylesByElement=new WeakMap,e})();function lf(e){let t=null;const i=Object.keys(e);for(let r=0;rthis._handleCallback(e)}apply(){!function(e,t){const i=mf(e,"").trim();i.length&&(function(e,t){let i=0;for(let r=0;r=this._delay&&i>=this._duration&&this.finish()}finish(){this._finished||(this._finished=!0,this._onDoneFn(),pf(this._element,this._eventFn,!0))}destroy(){this._destroyed||(this._destroyed=!0,this.finish(),function(e,t){const i=mf(e,"").split(","),r=ff(i,t);r>=0&&(i.splice(r,1),_f(e,"",i.join(",")))}(this._element,this._name))}}function uf(e,t,i){_f(e,"PlayState",i,df(e,t))}function df(e,t){const i=mf(e,"");return i.indexOf(",")>0?ff(i.split(","),t):ff([i],t)}function ff(e,t){for(let i=0;i=0)return i;return-1}function pf(e,t,i){i?e.removeEventListener("animationend",t):e.addEventListener("animationend",t)}function _f(e,t,i,r){const n="animation"+t;if(null!=r){const t=e.style[n];if(t.length){const e=t.split(",");e[r]=i,i=e.join(",")}}e.style[n]=i}function mf(e,t){return e.style["animation"+t]}class gf{constructor(e,t,i,r,n,s,o,a){this.element=e,this.keyframes=t,this.animationName=i,this._duration=r,this._delay=n,this._finalStyles=o,this._specialStyles=a,this._onDoneFns=[],this._onStartFns=[],this._onDestroyFns=[],this._started=!1,this.currentSnapshot={},this._state=0,this.easing=s||"linear",this.totalTime=r+n,this._buildStyler()}onStart(e){this._onStartFns.push(e)}onDone(e){this._onDoneFns.push(e)}onDestroy(e){this._onDestroyFns.push(e)}destroy(){this.init(),this._state>=4||(this._state=4,this._styler.destroy(),this._flushStartFns(),this._flushDoneFns(),this._specialStyles&&this._specialStyles.destroy(),this._onDestroyFns.forEach(e=>e()),this._onDestroyFns=[])}_flushDoneFns(){this._onDoneFns.forEach(e=>e()),this._onDoneFns=[]}_flushStartFns(){this._onStartFns.forEach(e=>e()),this._onStartFns=[]}finish(){this.init(),this._state>=3||(this._state=3,this._styler.finish(),this._flushStartFns(),this._specialStyles&&this._specialStyles.finish(),this._flushDoneFns())}setPosition(e){this._styler.setPosition(e)}getPosition(){return this._styler.getPosition()}hasStarted(){return this._state>=2}init(){this._state>=1||(this._state=1,this._styler.apply(),this._delay&&this._styler.pause())}play(){this.init(),this.hasStarted()||(this._flushStartFns(),this._state=2,this._specialStyles&&this._specialStyles.start()),this._styler.resume()}pause(){this.init(),this._styler.pause()}restart(){this.reset(),this.play()}reset(){this._styler.destroy(),this._buildStyler(),this._styler.apply()}_buildStyler(){this._styler=new hf(this.element,this.animationName,this._duration,this._delay,this.easing,"forwards",()=>this.finish())}triggerCallback(e){const t="start"==e?this._onStartFns:this._onDoneFns;t.forEach(e=>e()),t.length=0}beforeDestroy(){this.init();const e={};if(this.hasStarted()){const t=this._state>=3;Object.keys(this._finalStyles).forEach(i=>{"offset"!=i&&(e[i]=t?this._finalStyles[i]:ld(this.element,i))})}this.currentSnapshot=e}}class vf extends bu{constructor(e,t){super(),this.element=e,this._startingStyles={},this.__initialized=!1,this._styles=Hu(t)}init(){!this.__initialized&&this._startingStyles&&(this.__initialized=!0,Object.keys(this._styles).forEach(e=>{this._startingStyles[e]=this.element.style[e]}),super.init())}play(){this._startingStyles&&(this.init(),Object.keys(this._styles).forEach(e=>this.element.style.setProperty(e,this._styles[e])),super.play())}destroy(){this._startingStyles&&(Object.keys(this._startingStyles).forEach(e=>{const t=this._startingStyles[e];t?this.element.style.setProperty(e,t):this.element.style.removeProperty(e)}),this._startingStyles=null,super.destroy())}}class yf{constructor(){this._count=0,this._head=document.querySelector("head"),this._warningIssued=!1}validateStyleProperty(e){return Fu(e)}matchesElement(e,t){return Nu(e,t)}containsElement(e,t){return ju(e,t)}query(e,t,i){return Bu(e,t,i)}computeStyle(e,t,i){return window.getComputedStyle(e)[t]}buildKeyframeElement(e,t,i){i=i.map(e=>Hu(e));let r=`@keyframes ${t} {\n`,n="";i.forEach(e=>{n=" ";const t=parseFloat(e.offset);r+=`${n}${100*t}% {\n`,n+=" ",Object.keys(e).forEach(t=>{const i=e[t];switch(t){case"offset":return;case"easing":return void(i&&(r+=`${n}animation-timing-function: ${i};\n`));default:return void(r+=`${n}${t}: ${i};\n`)}}),r+=n+"}\n"}),r+="}\n";const s=document.createElement("style");return s.innerHTML=r,s}animate(e,t,i,r,n,s=[],o){o&&this._notifyFaultyScrubber();const a=s.filter(e=>e instanceof gf),l={};sd(i,r)&&a.forEach(e=>{let t=e.currentSnapshot;Object.keys(t).forEach(e=>l[e]=t[e])});const c=function(e){let t={};return e&&(Array.isArray(e)?e:[e]).forEach(e=>{Object.keys(e).forEach(i=>{"offset"!=i&&"easing"!=i&&(t[i]=e[i])})}),t}(t=od(e,t,l));if(0==i)return new vf(e,c);const h="gen_css_kf_"+this._count++,u=this.buildKeyframeElement(e,h,t);document.querySelector("head").appendChild(u);const d=of(e,t),f=new gf(e,t,h,i,r,n,c,d);return f.onDestroy(()=>{var e;(e=u).parentNode.removeChild(e)}),f}_notifyFaultyScrubber(){this._warningIssued||(console.warn("@angular/animations: please load the web-animations.js polyfill to allow programmatic access...\n"," visit http://bit.ly/IWukam to learn more about using the web-animation-js polyfill."),this._warningIssued=!0)}}class bf{constructor(e,t,i,r){this.element=e,this.keyframes=t,this.options=i,this._specialStyles=r,this._onDoneFns=[],this._onStartFns=[],this._onDestroyFns=[],this._initialized=!1,this._finished=!1,this._started=!1,this._destroyed=!1,this.time=0,this.parentPlayer=null,this.currentSnapshot={},this._duration=i.duration,this._delay=i.delay||0,this.time=this._duration+this._delay}_onFinish(){this._finished||(this._finished=!0,this._onDoneFns.forEach(e=>e()),this._onDoneFns=[])}init(){this._buildPlayer(),this._preparePlayerBeforeStart()}_buildPlayer(){if(this._initialized)return;this._initialized=!0;const e=this.keyframes;this.domPlayer=this._triggerWebAnimation(this.element,e,this.options),this._finalKeyframe=e.length?e[e.length-1]:{},this.domPlayer.addEventListener("finish",()=>this._onFinish())}_preparePlayerBeforeStart(){this._delay?this._resetDomPlayerState():this.domPlayer.pause()}_triggerWebAnimation(e,t,i){return e.animate(t,i)}onStart(e){this._onStartFns.push(e)}onDone(e){this._onDoneFns.push(e)}onDestroy(e){this._onDestroyFns.push(e)}play(){this._buildPlayer(),this.hasStarted()||(this._onStartFns.forEach(e=>e()),this._onStartFns=[],this._started=!0,this._specialStyles&&this._specialStyles.start()),this.domPlayer.play()}pause(){this.init(),this.domPlayer.pause()}finish(){this.init(),this._specialStyles&&this._specialStyles.finish(),this._onFinish(),this.domPlayer.finish()}reset(){this._resetDomPlayerState(),this._destroyed=!1,this._finished=!1,this._started=!1}_resetDomPlayerState(){this.domPlayer&&this.domPlayer.cancel()}restart(){this.reset(),this.play()}hasStarted(){return this._started}destroy(){this._destroyed||(this._destroyed=!0,this._resetDomPlayerState(),this._onFinish(),this._specialStyles&&this._specialStyles.destroy(),this._onDestroyFns.forEach(e=>e()),this._onDestroyFns=[])}setPosition(e){this.domPlayer.currentTime=e*this.time}getPosition(){return this.domPlayer.currentTime/this.time}get totalTime(){return this._delay+this._duration}beforeDestroy(){const e={};this.hasStarted()&&Object.keys(this._finalKeyframe).forEach(t=>{"offset"!=t&&(e[t]=this._finished?this._finalKeyframe[t]:ld(this.element,t))}),this.currentSnapshot=e}triggerCallback(e){const t="start"==e?this._onStartFns:this._onDoneFns;t.forEach(e=>e()),t.length=0}}class wf{constructor(){this._isNativeImpl=/\{\s*\[native\s+code\]\s*\}/.test(Cf().toString()),this._cssKeyframesDriver=new yf}validateStyleProperty(e){return Fu(e)}matchesElement(e,t){return Nu(e,t)}containsElement(e,t){return ju(e,t)}query(e,t,i){return Bu(e,t,i)}computeStyle(e,t,i){return window.getComputedStyle(e)[t]}overrideWebAnimationsSupport(e){this._isNativeImpl=e}animate(e,t,i,r,n,s=[],o){if(!o&&!this._isNativeImpl)return this._cssKeyframesDriver.animate(e,t,i,r,n,s);const a={duration:i,delay:r,fill:0==r?"both":"forwards"};n&&(a.easing=n);const l={},c=s.filter(e=>e instanceof bf);sd(i,r)&&c.forEach(e=>{let t=e.currentSnapshot;Object.keys(t).forEach(e=>l[e]=t[e])});const h=of(e,t=od(e,t=t.map(e=>Ku(e,!1)),l));return new bf(e,t,a,h)}}function Cf(){return"undefined"!=typeof window&&void 0!==window.document&&Element.prototype.animate||{}}let Sf=(()=>{class e extends hu{constructor(e,t){super(),this._nextAnimationId=0,this._renderer=e.createRenderer(t.body,{id:"0",encapsulation:ht.None,styles:[],data:{animation:[]}})}build(e){const t=this._nextAnimationId.toString();this._nextAnimationId++;const i=Array.isArray(e)?fu(e):e;return Ef(this._renderer,null,t,"register",[i]),new kf(t,this._renderer)}}return e.\u0275fac=function(t){return new(t||e)(Ze(Ia),Ze(Kc))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();class kf extends class{}{constructor(e,t){super(),this._id=e,this._renderer=t}create(e,t){return new xf(this._id,e,t||{},this._renderer)}}class xf{constructor(e,t,i,r){this.id=e,this.element=t,this._renderer=r,this.parentPlayer=null,this._started=!1,this.totalTime=0,this._command("create",i)}_listen(e,t){return this._renderer.listen(this.element,`@@${this.id}:${e}`,t)}_command(e,...t){return Ef(this._renderer,this.element,this.id,e,t)}onDone(e){this._listen("done",e)}onStart(e){this._listen("start",e)}onDestroy(e){this._listen("destroy",e)}init(){this._command("init")}hasStarted(){return this._started}play(){this._command("play"),this._started=!0}pause(){this._command("pause")}restart(){this._command("restart")}finish(){this._command("finish")}destroy(){this._command("destroy")}reset(){this._command("reset")}setPosition(e){this._command("setPosition",e)}getPosition(){return 0}}function Ef(e,t,i,r,n){return e.setProperty(t,`@@${i}:${r}`,n)}let Af=(()=>{class e{constructor(e,t,i){this.delegate=e,this.engine=t,this._zone=i,this._currentId=0,this._microtaskId=1,this._animationCallbacksBuffer=[],this._rendererCache=new Map,this._cdRecurDepth=0,this.promise=Promise.resolve(0),t.onRemovalComplete=(e,t)=>{t&&t.parentNode(e)&&t.removeChild(e.parentNode,e)}}createRenderer(e,t){const i=this.delegate.createRenderer(e,t);if(!(e&&t&&t.data&&t.data.animation)){let e=this._rendererCache.get(i);return e||(e=new Of("",i,this.engine),this._rendererCache.set(i,e)),e}const r=t.id,n=t.id+"-"+this._currentId;this._currentId++,this.engine.register(n,e);const s=t=>{Array.isArray(t)?t.forEach(s):this.engine.registerTrigger(r,n,e,t.name,t)};return t.data.animation.forEach(s),new Tf(this,n,i,this.engine)}begin(){this._cdRecurDepth++,this.delegate.begin&&this.delegate.begin()}_scheduleCountTask(){this.promise.then(()=>{this._microtaskId++})}scheduleListenerCallback(e,t,i){e>=0&&et(i)):(0==this._animationCallbacksBuffer.length&&Promise.resolve(null).then(()=>{this._zone.run(()=>{this._animationCallbacksBuffer.forEach(e=>{const[t,i]=e;t(i)}),this._animationCallbacksBuffer=[]})}),this._animationCallbacksBuffer.push([t,i]))}end(){this._cdRecurDepth--,0==this._cdRecurDepth&&this._zone.runOutsideAngular(()=>{this._scheduleCountTask(),this.engine.flush(this._microtaskId)}),this.delegate.end&&this.delegate.end()}whenRenderingDone(){return this.engine.whenRenderingDone()}}return e.\u0275fac=function(t){return new(t||e)(Ze(Ia),Ze(sf),Ze(mc))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();class Of{constructor(e,t,i){this.namespaceId=e,this.delegate=t,this.engine=i,this.destroyNode=this.delegate.destroyNode?e=>t.destroyNode(e):null}get data(){return this.delegate.data}destroy(){this.engine.destroy(this.namespaceId,this.delegate),this.delegate.destroy()}createElement(e,t){return this.delegate.createElement(e,t)}createComment(e){return this.delegate.createComment(e)}createText(e){return this.delegate.createText(e)}appendChild(e,t){this.delegate.appendChild(e,t),this.engine.onInsert(this.namespaceId,t,e,!1)}insertBefore(e,t,i){this.delegate.insertBefore(e,t,i),this.engine.onInsert(this.namespaceId,t,e,!0)}removeChild(e,t,i){this.engine.onRemove(this.namespaceId,t,this.delegate,i)}selectRootElement(e,t){return this.delegate.selectRootElement(e,t)}parentNode(e){return this.delegate.parentNode(e)}nextSibling(e){return this.delegate.nextSibling(e)}setAttribute(e,t,i,r){this.delegate.setAttribute(e,t,i,r)}removeAttribute(e,t,i){this.delegate.removeAttribute(e,t,i)}addClass(e,t){this.delegate.addClass(e,t)}removeClass(e,t){this.delegate.removeClass(e,t)}setStyle(e,t,i,r){this.delegate.setStyle(e,t,i,r)}removeStyle(e,t,i){this.delegate.removeStyle(e,t,i)}setProperty(e,t,i){"@"==t.charAt(0)&&"@.disabled"==t?this.disableAnimations(e,!!i):this.delegate.setProperty(e,t,i)}setValue(e,t){this.delegate.setValue(e,t)}listen(e,t,i){return this.delegate.listen(e,t,i)}disableAnimations(e,t){this.engine.disableAnimations(e,t)}}class Tf extends Of{constructor(e,t,i,r){super(t,i,r),this.factory=e,this.namespaceId=t}setProperty(e,t,i){"@"==t.charAt(0)?"."==t.charAt(1)&&"@.disabled"==t?this.disableAnimations(e,i=void 0===i||!!i):this.engine.process(this.namespaceId,e,t.substr(1),i):this.delegate.setProperty(e,t,i)}listen(e,t,i){if("@"==t.charAt(0)){const r=function(e){switch(e){case"body":return document.body;case"document":return document;case"window":return window;default:return e}}(e);let n=t.substr(1),s="";return"@"!=n.charAt(0)&&([n,s]=function(e){const t=e.indexOf(".");return[e.substring(0,t),e.substr(t+1)]}(n)),this.engine.listen(this.namespaceId,r,n,s,e=>{this.factory.scheduleListenerCallback(e._data||-1,i,e)})}return this.delegate.listen(e,t,i)}}let Rf=(()=>{class e extends sf{constructor(e,t,i){super(e.body,t,i)}}return e.\u0275fac=function(t){return new(t||e)(Ze(Kc),Ze(Vu),Ze(Ld))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const Lf=new Be("AnimationModuleType"),Pf=[{provide:Vu,useFactory:function(){return"function"==typeof Cf()?new wf:new yf}},{provide:Lf,useValue:"BrowserAnimations"},{provide:hu,useClass:Sf},{provide:Ld,useFactory:function(){return new Pd}},{provide:sf,useClass:Rf},{provide:Ia,useFactory:function(e,t,i){return new Af(e,t,i)},deps:[Xh,sf,mc]}];let Df=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:Pf,imports:[cu]}),e})();const If=new v(e=>e.complete());function Mf(e){return e?function(e){return new v(t=>e.schedule(()=>t.complete()))}(e):If}function Ff(e){return new v(t=>{let i;try{i=e()}catch(r){return void t.error(r)}return(i?B(i):Mf()).subscribe(t)})}function Nf(e,t){return new v(t?i=>t.schedule(jf,0,{error:e,subscriber:i}):t=>t.error(e))}function jf({error:e,subscriber:t}){t.error(e)}function Bf(e,t,i,n){return r(i)&&(n=i,i=void 0),n?Bf(e,t,i).pipe(M(e=>l(e)?n(...e):n(e))):new v(r=>{!function e(t,i,r,n,s){let o;if(function(e){return e&&"function"==typeof e.addEventListener&&"function"==typeof e.removeEventListener}(t)){const e=t;t.addEventListener(i,r,s),o=()=>e.removeEventListener(i,r,s)}else if(function(e){return e&&"function"==typeof e.on&&"function"==typeof e.off}(t)){const e=t;t.on(i,r),o=()=>e.off(i,r)}else if(function(e){return e&&"function"==typeof e.addListener&&"function"==typeof e.removeListener}(t)){const e=t;t.addListener(i,r),o=()=>e.removeListener(i,r)}else{if(!t||!t.length)throw new TypeError("Invalid event target");for(let o=0,a=t.length;o1?Array.prototype.slice.call(arguments):e)}),r,i)})}function Hf(...e){let t=e[e.length-1];return x(t)?(e.pop(),j(e,t)):z(e)}function Uf(){return q(1)}function Vf(...e){return Uf()(Hf(...e))}function qf(){}const zf=new v(qf);function Wf(e,t){return function(i){return i.lift(new $f(e,t))}}class $f{constructor(e,t){this.predicate=e,this.thisArg=t}call(e,t){return t.subscribe(new Kf(e,this.predicate,this.thisArg))}}class Kf extends p{constructor(e,t,i){super(e),this.predicate=t,this.thisArg=i,this.count=0}_next(e){let t;try{t=this.predicate.call(this.thisArg,e,this.count++)}catch(i){return void this.destination.error(i)}t&&this.destination.next(e)}}function Gf(e,t){return"function"==typeof t?i=>i.pipe(Gf((i,r)=>B(e(i,r)).pipe(M((e,n)=>t(i,e,r,n))))):t=>t.lift(new Zf(e))}class Zf{constructor(e){this.project=e}call(e,t){return t.subscribe(new Yf(e,this.project))}}class Yf extends I{constructor(e,t){super(e),this.project=t,this.index=0}_next(e){let t;const i=this.index++;try{t=this.project(e,i)}catch(r){return void this.destination.error(r)}this._innerSub(t,e,i)}_innerSub(e,t,i){const r=this.innerSubscription;r&&r.unsubscribe();const n=new E(this,t,i),s=this.destination;s.add(n),this.innerSubscription=D(this,e,void 0,void 0,n),this.innerSubscription!==n&&s.add(this.innerSubscription)}_complete(){const{innerSubscription:e}=this;e&&!e.closed||super._complete(),this.unsubscribe()}_unsubscribe(){this.innerSubscription=null}notifyComplete(e){this.destination.remove(e),this.innerSubscription=null,this.isStopped&&super._complete()}notifyNext(e,t,i,r,n){this.destination.next(t)}}const Xf=(()=>{function e(){return Error.call(this),this.message="argument out of range",this.name="ArgumentOutOfRangeError",this}return e.prototype=Object.create(Error.prototype),e})();function Qf(e){return t=>0===e?Mf():t.lift(new Jf(e))}class Jf{constructor(e){if(this.total=e,this.total<0)throw new Xf}call(e,t){return t.subscribe(new ep(e,this.total))}}class ep extends p{constructor(e,t){super(e),this.total=t,this.count=0}_next(e){const t=this.total,i=++this.count;i<=t&&(this.destination.next(e),i===t&&(this.destination.complete(),this.unsubscribe()))}}function tp(e,t,i){return function(r){return r.lift(new ip(e,t,i))}}class ip{constructor(e,t,i){this.nextOrObserver=e,this.error=t,this.complete=i}call(e,t){return t.subscribe(new rp(e,this.nextOrObserver,this.error,this.complete))}}class rp extends p{constructor(e,t,i,n){super(e),this._tapNext=qf,this._tapError=qf,this._tapComplete=qf,this._tapError=i||qf,this._tapComplete=n||qf,r(t)?(this._context=this,this._tapNext=t):t&&(this._context=t,this._tapNext=t.next||qf,this._tapError=t.error||qf,this._tapComplete=t.complete||qf)}_next(e){try{this._tapNext.call(this._context,e)}catch(t){return void this.destination.error(t)}this.destination.next(e)}_error(e){try{this._tapError.call(this._context,e)}catch(e){return void this.destination.error(e)}this.destination.error(e)}_complete(){try{this._tapComplete.call(this._context)}catch(e){return void this.destination.error(e)}return this.destination.complete()}}class np extends u{constructor(e,t){super()}schedule(e,t=0){return this}}class sp extends np{constructor(e,t){super(e,t),this.scheduler=e,this.work=t,this.pending=!1}schedule(e,t=0){if(this.closed)return this;this.state=e;const i=this.id,r=this.scheduler;return null!=i&&(this.id=this.recycleAsyncId(r,i,t)),this.pending=!0,this.delay=t,this.id=this.id||this.requestAsyncId(r,this.id,t),this}requestAsyncId(e,t,i=0){return setInterval(e.flush.bind(e,this),i)}recycleAsyncId(e,t,i=0){if(null!==i&&this.delay===i&&!1===this.pending)return t;clearInterval(t)}execute(e,t){if(this.closed)return new Error("executing a cancelled action");this.pending=!1;const i=this._execute(e,t);if(i)return i;!1===this.pending&&null!=this.id&&(this.id=this.recycleAsyncId(this.scheduler,this.id,null))}_execute(e,t){let i=!1,r=void 0;try{this.work(e)}catch(n){i=!0,r=!!n&&n||new Error(n)}if(i)return this.unsubscribe(),r}_unsubscribe(){const e=this.id,t=this.scheduler,i=t.actions,r=i.indexOf(this);this.work=null,this.state=null,this.pending=!1,this.scheduler=null,-1!==r&&i.splice(r,1),null!=e&&(this.id=this.recycleAsyncId(t,e,null)),this.delay=null}}let op=(()=>{class e{constructor(t,i=e.now){this.SchedulerAction=t,this.now=i}schedule(e,t=0,i){return new this.SchedulerAction(this,e).schedule(i,t)}}return e.now=()=>Date.now(),e})();class ap extends op{constructor(e,t=op.now){super(e,()=>ap.delegate&&ap.delegate!==this?ap.delegate.now():t()),this.actions=[],this.active=!1,this.scheduled=void 0}schedule(e,t=0,i){return ap.delegate&&ap.delegate!==this?ap.delegate.schedule(e,t,i):super.schedule(e,t,i)}flush(e){const{actions:t}=this;if(this.active)return void t.push(e);let i;this.active=!0;do{if(i=e.execute(e.state,e.delay))break}while(e=t.shift());if(this.active=!1,i){for(;e=t.shift();)e.unsubscribe();throw i}}}const lp=new ap(sp);let cp=(()=>{class e{constructor(e,t,i){this.kind=e,this.value=t,this.error=i,this.hasValue="N"===e}observe(e){switch(this.kind){case"N":return e.next&&e.next(this.value);case"E":return e.error&&e.error(this.error);case"C":return e.complete&&e.complete()}}do(e,t,i){switch(this.kind){case"N":return e&&e(this.value);case"E":return t&&t(this.error);case"C":return i&&i()}}accept(e,t,i){return e&&"function"==typeof e.next?this.observe(e):this.do(e,t,i)}toObservable(){switch(this.kind){case"N":return Hf(this.value);case"E":return Nf(this.error);case"C":return Mf()}throw new Error("unexpected notification kind value")}static createNext(t){return void 0!==t?new e("N",t):e.undefinedValueNotification}static createError(t){return new e("E",void 0,t)}static createComplete(){return e.completeNotification}}return e.completeNotification=new e("C"),e.undefinedValueNotification=new e("N",void 0),e})();class hp{constructor(e,t){this.delay=e,this.scheduler=t}call(e,t){return t.subscribe(new up(e,this.delay,this.scheduler))}}class up extends p{constructor(e,t,i){super(e),this.delay=t,this.scheduler=i,this.queue=[],this.active=!1,this.errored=!1}static dispatch(e){const t=e.source,i=t.queue,r=e.scheduler,n=e.destination;for(;i.length>0&&i[0].time-r.now()<=0;)i.shift().notification.observe(n);if(i.length>0){const t=Math.max(0,i[0].time-r.now());this.schedule(e,t)}else this.unsubscribe(),t.active=!1}_schedule(e){this.active=!0,this.destination.add(e.schedule(up.dispatch,this.delay,{source:this,destination:this.destination,scheduler:e}))}scheduleNotification(e){if(!0===this.errored)return;const t=this.scheduler,i=new dp(t.now()+this.delay,e);this.queue.push(i),!1===this.active&&this._schedule(t)}_next(e){this.scheduleNotification(cp.createNext(e))}_error(e){this.errored=!0,this.queue=[],this.destination.error(e),this.unsubscribe()}_complete(){this.scheduleNotification(cp.createComplete()),this.unsubscribe()}}class dp{constructor(e,t){this.time=e,this.notification=t}}const fp="Service workers are disabled or not supported by this browser";class pp{constructor(e){if(this.serviceWorker=e,e){const t=Bf(e,"controllerchange").pipe(M(()=>e.controller)),i=Vf(Ff(()=>Hf(e.controller)),t);this.worker=i.pipe(Wf(e=>!!e)),this.registration=this.worker.pipe(Gf(()=>e.getRegistration()));const r=Bf(e,"message").pipe(M(e=>e.data)).pipe(Wf(e=>e&&e.type)).pipe(Q(new S));r.connect(),this.events=r}else this.worker=this.events=this.registration=Ff(()=>Nf(new Error("Service workers are disabled or not supported by this browser")))}postMessage(e,t){return this.worker.pipe(Qf(1),tp(i=>{i.postMessage(Object.assign({action:e},t))})).toPromise().then(()=>{})}postMessageWithStatus(e,t,i){const r=this.waitForStatus(i),n=this.postMessage(e,t);return Promise.all([r,n]).then(()=>{})}generateNonce(){return Math.round(1e7*Math.random())}eventsOfType(e){return this.events.pipe(Wf(t=>t.type===e))}nextEventOfType(e){return this.eventsOfType(e).pipe(Qf(1))}waitForStatus(e){return this.eventsOfType("STATUS").pipe(Wf(t=>t.nonce===e),Qf(1),M(e=>{if(!e.status)throw new Error(e.error)})).toPromise()}get isEnabled(){return!!this.serviceWorker}}let _p=(()=>{class e{constructor(e){if(this.sw=e,this.subscriptionChanges=new S,!e.isEnabled)return this.messages=zf,this.notificationClicks=zf,void(this.subscription=zf);this.messages=this.sw.eventsOfType("PUSH").pipe(M(e=>e.data)),this.notificationClicks=this.sw.eventsOfType("NOTIFICATION_CLICK").pipe(M(e=>e.data)),this.pushManager=this.sw.registration.pipe(M(e=>e.pushManager));const t=this.pushManager.pipe(Gf(e=>e.getSubscription()));this.subscription=W(t,this.subscriptionChanges)}get isEnabled(){return this.sw.isEnabled}requestSubscription(e){if(!this.sw.isEnabled)return Promise.reject(new Error(fp));const t={userVisibleOnly:!0};let i=this.decodeBase64(e.serverPublicKey.replace(/_/g,"/").replace(/-/g,"+")),r=new Uint8Array(new ArrayBuffer(i.length));for(let n=0;ne.subscribe(t)),Qf(1)).toPromise().then(e=>(this.subscriptionChanges.next(e),e))}unsubscribe(){return this.sw.isEnabled?this.subscription.pipe(Qf(1),Gf(e=>{if(null===e)throw new Error("Not subscribed to push notifications.");return e.unsubscribe().then(e=>{if(!e)throw new Error("Unsubscribe failed!");this.subscriptionChanges.next(null)})})).toPromise():Promise.reject(new Error(fp))}decodeBase64(e){return atob(e)}}return e.\u0275fac=function(t){return new(t||e)(Ze(pp))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),mp=(()=>{class e{constructor(e){if(this.sw=e,!e.isEnabled)return this.available=zf,void(this.activated=zf);this.available=this.sw.eventsOfType("UPDATE_AVAILABLE"),this.activated=this.sw.eventsOfType("UPDATE_ACTIVATED")}get isEnabled(){return this.sw.isEnabled}checkForUpdate(){if(!this.sw.isEnabled)return Promise.reject(new Error(fp));const e=this.sw.generateNonce();return this.sw.postMessageWithStatus("CHECK_FOR_UPDATES",{statusNonce:e},e)}activateUpdate(){if(!this.sw.isEnabled)return Promise.reject(new Error(fp));const e=this.sw.generateNonce();return this.sw.postMessageWithStatus("ACTIVATE_UPDATE",{statusNonce:e},e)}}return e.\u0275fac=function(t){return new(t||e)(Ze(pp))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();class gp{}const vp=new Be("NGSW_REGISTER_SCRIPT");function yp(e,t,i,r){return()=>{if(!Ph(r)||!("serviceWorker"in navigator)||!1===i.enabled)return;let n;if(navigator.serviceWorker.addEventListener("controllerchange",()=>{null!==navigator.serviceWorker.controller&&navigator.serviceWorker.controller.postMessage({action:"INITIALIZE"})}),"function"==typeof i.registrationStrategy)n=i.registrationStrategy();else{const[t,...r]=(i.registrationStrategy||"registerWhenStable:30000").split(":");switch(t){case"registerImmediately":n=Hf(null);break;case"registerWithDelay":n=bp(+r[0]||0);break;case"registerWhenStable":n=r[0]?W(wp(e),bp(+r[0])):wp(e);break;default:throw new Error("Unknown ServiceWorker registration strategy: "+i.registrationStrategy)}}e.get(mc).runOutsideAngular(()=>n.pipe(Qf(1)).subscribe(()=>navigator.serviceWorker.register(t,{scope:i.scope}).catch(e=>console.error("Service worker registration failed with:",e))))}}function bp(e){return Hf(null).pipe(function(e,t=lp){var i;const r=(i=e)instanceof Date&&!isNaN(+i)?+e-t.now():Math.abs(e);return e=>e.lift(new hp(r,t))}(e))}function wp(e){return e.get(Mc).isStable.pipe(Wf(e=>e))}function Cp(e,t){return new pp(Ph(t)&&!1!==e.enabled?navigator.serviceWorker:void 0)}let Sp=(()=>{class e{static register(t,i={}){return{ngModule:e,providers:[{provide:vp,useValue:t},{provide:gp,useValue:i},{provide:pp,useFactory:Cp,deps:[gp,tc]},{provide:Zl,useFactory:yp,deps:[ao,vp,gp,tc],multi:!0}]}}}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[_p,mp]}),e})();function kp(e,t){return H(e,t,1)}class xp{}class Ep{}class Ap{constructor(e){this.normalizedNames=new Map,this.lazyUpdate=null,e?this.lazyInit="string"==typeof e?()=>{this.headers=new Map,e.split("\n").forEach(e=>{const t=e.indexOf(":");if(t>0){const i=e.slice(0,t),r=i.toLowerCase(),n=e.slice(t+1).trim();this.maybeSetNormalizedName(i,r),this.headers.has(r)?this.headers.get(r).push(n):this.headers.set(r,[n])}})}:()=>{this.headers=new Map,Object.keys(e).forEach(t=>{let i=e[t];const r=t.toLowerCase();"string"==typeof i&&(i=[i]),i.length>0&&(this.headers.set(r,i),this.maybeSetNormalizedName(t,r))})}:this.headers=new Map}has(e){return this.init(),this.headers.has(e.toLowerCase())}get(e){this.init();const t=this.headers.get(e.toLowerCase());return t&&t.length>0?t[0]:null}keys(){return this.init(),Array.from(this.normalizedNames.values())}getAll(e){return this.init(),this.headers.get(e.toLowerCase())||null}append(e,t){return this.clone({name:e,value:t,op:"a"})}set(e,t){return this.clone({name:e,value:t,op:"s"})}delete(e,t){return this.clone({name:e,value:t,op:"d"})}maybeSetNormalizedName(e,t){this.normalizedNames.has(t)||this.normalizedNames.set(t,e)}init(){this.lazyInit&&(this.lazyInit instanceof Ap?this.copyFrom(this.lazyInit):this.lazyInit(),this.lazyInit=null,this.lazyUpdate&&(this.lazyUpdate.forEach(e=>this.applyUpdate(e)),this.lazyUpdate=null))}copyFrom(e){e.init(),Array.from(e.headers.keys()).forEach(t=>{this.headers.set(t,e.headers.get(t)),this.normalizedNames.set(t,e.normalizedNames.get(t))})}clone(e){const t=new Ap;return t.lazyInit=this.lazyInit&&this.lazyInit instanceof Ap?this.lazyInit:this,t.lazyUpdate=(this.lazyUpdate||[]).concat([e]),t}applyUpdate(e){const t=e.name.toLowerCase();switch(e.op){case"a":case"s":let i=e.value;if("string"==typeof i&&(i=[i]),0===i.length)return;this.maybeSetNormalizedName(e.name,t);const r=("a"===e.op?this.headers.get(t):void 0)||[];r.push(...i),this.headers.set(t,r);break;case"d":const n=e.value;if(n){let e=this.headers.get(t);if(!e)return;e=e.filter(e=>-1===n.indexOf(e)),0===e.length?(this.headers.delete(t),this.normalizedNames.delete(t)):this.headers.set(t,e)}else this.headers.delete(t),this.normalizedNames.delete(t)}}forEach(e){this.init(),Array.from(this.normalizedNames.keys()).forEach(t=>e(this.normalizedNames.get(t),this.headers.get(t)))}}class Op{encodeKey(e){return Tp(e)}encodeValue(e){return Tp(e)}decodeKey(e){return decodeURIComponent(e)}decodeValue(e){return decodeURIComponent(e)}}function Tp(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/gi,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%2B/gi,"+").replace(/%3D/gi,"=").replace(/%3F/gi,"?").replace(/%2F/gi,"/")}class Rp{constructor(e={}){if(this.updates=null,this.cloneFrom=null,this.encoder=e.encoder||new Op,e.fromString){if(e.fromObject)throw new Error("Cannot specify both fromString and fromObject.");this.map=function(e,t){const i=new Map;return e.length>0&&e.split("&").forEach(e=>{const r=e.indexOf("="),[n,s]=-1==r?[t.decodeKey(e),""]:[t.decodeKey(e.slice(0,r)),t.decodeValue(e.slice(r+1))],o=i.get(n)||[];o.push(s),i.set(n,o)}),i}(e.fromString,this.encoder)}else e.fromObject?(this.map=new Map,Object.keys(e.fromObject).forEach(t=>{const i=e.fromObject[t];this.map.set(t,Array.isArray(i)?i:[i])})):this.map=null}has(e){return this.init(),this.map.has(e)}get(e){this.init();const t=this.map.get(e);return t?t[0]:null}getAll(e){return this.init(),this.map.get(e)||null}keys(){return this.init(),Array.from(this.map.keys())}append(e,t){return this.clone({param:e,value:t,op:"a"})}set(e,t){return this.clone({param:e,value:t,op:"s"})}delete(e,t){return this.clone({param:e,value:t,op:"d"})}toString(){return this.init(),this.keys().map(e=>{const t=this.encoder.encodeKey(e);return this.map.get(e).map(e=>t+"="+this.encoder.encodeValue(e)).join("&")}).filter(e=>""!==e).join("&")}clone(e){const t=new Rp({encoder:this.encoder});return t.cloneFrom=this.cloneFrom||this,t.updates=(this.updates||[]).concat([e]),t}init(){null===this.map&&(this.map=new Map),null!==this.cloneFrom&&(this.cloneFrom.init(),this.cloneFrom.keys().forEach(e=>this.map.set(e,this.cloneFrom.map.get(e))),this.updates.forEach(e=>{switch(e.op){case"a":case"s":const t=("a"===e.op?this.map.get(e.param):void 0)||[];t.push(e.value),this.map.set(e.param,t);break;case"d":if(void 0===e.value){this.map.delete(e.param);break}{let t=this.map.get(e.param)||[];const i=t.indexOf(e.value);-1!==i&&t.splice(i,1),t.length>0?this.map.set(e.param,t):this.map.delete(e.param)}}}),this.cloneFrom=this.updates=null)}}function Lp(e){return"undefined"!=typeof ArrayBuffer&&e instanceof ArrayBuffer}function Pp(e){return"undefined"!=typeof Blob&&e instanceof Blob}function Dp(e){return"undefined"!=typeof FormData&&e instanceof FormData}class Ip{constructor(e,t,i,r){let n;if(this.url=t,this.body=null,this.reportProgress=!1,this.withCredentials=!1,this.responseType="json",this.method=e.toUpperCase(),function(e){switch(e){case"DELETE":case"GET":case"HEAD":case"OPTIONS":case"JSONP":return!1;default:return!0}}(this.method)||r?(this.body=void 0!==i?i:null,n=r):n=i,n&&(this.reportProgress=!!n.reportProgress,this.withCredentials=!!n.withCredentials,n.responseType&&(this.responseType=n.responseType),n.headers&&(this.headers=n.headers),n.params&&(this.params=n.params)),this.headers||(this.headers=new Ap),this.params){const e=this.params.toString();if(0===e.length)this.urlWithParams=t;else{const i=t.indexOf("?");this.urlWithParams=t+(-1===i?"?":it.set(i,e.setHeaders[i]),a)),e.setParams&&(l=Object.keys(e.setParams).reduce((t,i)=>t.set(i,e.setParams[i]),l)),new Ip(t,i,n,{params:l,headers:a,reportProgress:o,responseType:r,withCredentials:s})}}var Mp=function(e){return e[e.Sent=0]="Sent",e[e.UploadProgress=1]="UploadProgress",e[e.ResponseHeader=2]="ResponseHeader",e[e.DownloadProgress=3]="DownloadProgress",e[e.Response=4]="Response",e[e.User=5]="User",e}({});class Fp{constructor(e,t=200,i="OK"){this.headers=e.headers||new Ap,this.status=void 0!==e.status?e.status:t,this.statusText=e.statusText||i,this.url=e.url||null,this.ok=this.status>=200&&this.status<300}}class Np extends Fp{constructor(e={}){super(e),this.type=Mp.ResponseHeader}clone(e={}){return new Np({headers:e.headers||this.headers,status:void 0!==e.status?e.status:this.status,statusText:e.statusText||this.statusText,url:e.url||this.url||void 0})}}class jp extends Fp{constructor(e={}){super(e),this.type=Mp.Response,this.body=void 0!==e.body?e.body:null}clone(e={}){return new jp({body:void 0!==e.body?e.body:this.body,headers:e.headers||this.headers,status:void 0!==e.status?e.status:this.status,statusText:e.statusText||this.statusText,url:e.url||this.url||void 0})}}class Bp extends Fp{constructor(e){super(e,0,"Unknown Error"),this.name="HttpErrorResponse",this.ok=!1,this.message=this.status>=200&&this.status<300?"Http failure during parsing for "+(e.url||"(unknown url)"):`Http failure response for ${e.url||"(unknown url)"}: ${e.status} ${e.statusText}`,this.error=e.error||null}}function Hp(e,t){return{body:t,headers:e.headers,observe:e.observe,params:e.params,reportProgress:e.reportProgress,responseType:e.responseType,withCredentials:e.withCredentials}}let Up=(()=>{class e{constructor(e){this.handler=e}request(e,t,i={}){let r;if(e instanceof Ip)r=e;else{let n=void 0;n=i.headers instanceof Ap?i.headers:new Ap(i.headers);let s=void 0;i.params&&(s=i.params instanceof Rp?i.params:new Rp({fromObject:i.params})),r=new Ip(e,t,void 0!==i.body?i.body:null,{headers:n,params:s,reportProgress:i.reportProgress,responseType:i.responseType||"json",withCredentials:i.withCredentials})}const n=Hf(r).pipe(kp(e=>this.handler.handle(e)));if(e instanceof Ip||"events"===i.observe)return n;const s=n.pipe(Wf(e=>e instanceof jp));switch(i.observe||"body"){case"body":switch(r.responseType){case"arraybuffer":return s.pipe(M(e=>{if(null!==e.body&&!(e.body instanceof ArrayBuffer))throw new Error("Response is not an ArrayBuffer.");return e.body}));case"blob":return s.pipe(M(e=>{if(null!==e.body&&!(e.body instanceof Blob))throw new Error("Response is not a Blob.");return e.body}));case"text":return s.pipe(M(e=>{if(null!==e.body&&"string"!=typeof e.body)throw new Error("Response is not a string.");return e.body}));case"json":default:return s.pipe(M(e=>e.body))}case"response":return s;default:throw new Error(`Unreachable: unhandled observe type ${i.observe}}`)}}delete(e,t={}){return this.request("DELETE",e,t)}get(e,t={}){return this.request("GET",e,t)}head(e,t={}){return this.request("HEAD",e,t)}jsonp(e,t){return this.request("JSONP",e,{params:(new Rp).append(t,"JSONP_CALLBACK"),observe:"body",responseType:"json"})}options(e,t={}){return this.request("OPTIONS",e,t)}patch(e,t,i={}){return this.request("PATCH",e,Hp(i,t))}post(e,t,i={}){return this.request("POST",e,Hp(i,t))}put(e,t,i={}){return this.request("PUT",e,Hp(i,t))}}return e.\u0275fac=function(t){return new(t||e)(Ze(xp))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();class Vp{constructor(e,t){this.next=e,this.interceptor=t}handle(e){return this.interceptor.intercept(e,this.next)}}const qp=new Be("HTTP_INTERCEPTORS");let zp=(()=>{class e{intercept(e,t){return t.handle(e)}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const Wp=/^\)\]\}',?\n/;class $p{}let Kp=(()=>{class e{constructor(){}build(){return new XMLHttpRequest}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),Gp=(()=>{class e{constructor(e){this.xhrFactory=e}handle(e){if("JSONP"===e.method)throw new Error("Attempted to construct Jsonp request without JsonpClientModule installed.");return new v(t=>{const i=this.xhrFactory.build();if(i.open(e.method,e.urlWithParams),e.withCredentials&&(i.withCredentials=!0),e.headers.forEach((e,t)=>i.setRequestHeader(e,t.join(","))),e.headers.has("Accept")||i.setRequestHeader("Accept","application/json, text/plain, */*"),!e.headers.has("Content-Type")){const t=e.detectContentTypeHeader();null!==t&&i.setRequestHeader("Content-Type",t)}if(e.responseType){const t=e.responseType.toLowerCase();i.responseType="json"!==t?t:"text"}const r=e.serializeBody();let n=null;const s=()=>{if(null!==n)return n;const t=1223===i.status?204:i.status,r=i.statusText||"OK",s=new Ap(i.getAllResponseHeaders()),o=function(e){return"responseURL"in e&&e.responseURL?e.responseURL:/^X-Request-URL:/m.test(e.getAllResponseHeaders())?e.getResponseHeader("X-Request-URL"):null}(i)||e.url;return n=new Np({headers:s,status:t,statusText:r,url:o}),n},o=()=>{let{headers:r,status:n,statusText:o,url:a}=s(),l=null;204!==n&&(l=void 0===i.response?i.responseText:i.response),0===n&&(n=l?200:0);let c=n>=200&&n<300;if("json"===e.responseType&&"string"==typeof l){const e=l;l=l.replace(Wp,"");try{l=""!==l?JSON.parse(l):null}catch(h){l=e,c&&(c=!1,l={error:h,text:l})}}c?(t.next(new jp({body:l,headers:r,status:n,statusText:o,url:a||void 0})),t.complete()):t.error(new Bp({error:l,headers:r,status:n,statusText:o,url:a||void 0}))},a=e=>{const{url:r}=s(),n=new Bp({error:e,status:i.status||0,statusText:i.statusText||"Unknown Error",url:r||void 0});t.error(n)};let l=!1;const c=r=>{l||(t.next(s()),l=!0);let n={type:Mp.DownloadProgress,loaded:r.loaded};r.lengthComputable&&(n.total=r.total),"text"===e.responseType&&i.responseText&&(n.partialText=i.responseText),t.next(n)},h=e=>{let i={type:Mp.UploadProgress,loaded:e.loaded};e.lengthComputable&&(i.total=e.total),t.next(i)};return i.addEventListener("load",o),i.addEventListener("error",a),e.reportProgress&&(i.addEventListener("progress",c),null!==r&&i.upload&&i.upload.addEventListener("progress",h)),i.send(r),t.next({type:Mp.Sent}),()=>{i.removeEventListener("error",a),i.removeEventListener("load",o),e.reportProgress&&(i.removeEventListener("progress",c),null!==r&&i.upload&&i.upload.removeEventListener("progress",h)),i.readyState!==i.DONE&&i.abort()}})}}return e.\u0275fac=function(t){return new(t||e)(Ze($p))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const Zp=new Be("XSRF_COOKIE_NAME"),Yp=new Be("XSRF_HEADER_NAME");class Xp{}let Qp=(()=>{class e{constructor(e,t,i){this.doc=e,this.platform=t,this.cookieName=i,this.lastCookieString="",this.lastToken=null,this.parseCount=0}getToken(){if("server"===this.platform)return null;const e=this.doc.cookie||"";return e!==this.lastCookieString&&(this.parseCount++,this.lastToken=ph(e,this.cookieName),this.lastCookieString=e),this.lastToken}}return e.\u0275fac=function(t){return new(t||e)(Ze(Kc),Ze(tc),Ze(Zp))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),Jp=(()=>{class e{constructor(e,t){this.tokenService=e,this.headerName=t}intercept(e,t){const i=e.url.toLowerCase();if("GET"===e.method||"HEAD"===e.method||i.startsWith("http://")||i.startsWith("https://"))return t.handle(e);const r=this.tokenService.getToken();return null===r||e.headers.has(this.headerName)||(e=e.clone({headers:e.headers.set(this.headerName,r)})),t.handle(e)}}return e.\u0275fac=function(t){return new(t||e)(Ze(Xp),Ze(Yp))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),e_=(()=>{class e{constructor(e,t){this.backend=e,this.injector=t,this.chain=null}handle(e){if(null===this.chain){const e=this.injector.get(qp,[]);this.chain=e.reduceRight((e,t)=>new Vp(e,t),this.backend)}return this.chain.handle(e)}}return e.\u0275fac=function(t){return new(t||e)(Ze(Ep),Ze(ao))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),t_=(()=>{class e{static disable(){return{ngModule:e,providers:[{provide:Jp,useClass:zp}]}}static withOptions(t={}){return{ngModule:e,providers:[t.cookieName?{provide:Zp,useValue:t.cookieName}:[],t.headerName?{provide:Yp,useValue:t.headerName}:[]]}}}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[Jp,{provide:qp,useExisting:Jp,multi:!0},{provide:Xp,useClass:Qp},{provide:Zp,useValue:"XSRF-TOKEN"},{provide:Yp,useValue:"X-XSRF-TOKEN"}]}),e})(),i_=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[Up,{provide:xp,useClass:e_},Gp,{provide:Ep,useExisting:Gp},Kp,{provide:$p,useExisting:Kp}],imports:[[t_.withOptions({cookieName:"XSRF-TOKEN",headerName:"X-XSRF-TOKEN"})]]}),e})();function r_(e){return null!=e&&""+e!="false"}function n_(e,t=0){return function(e){return!isNaN(parseFloat(e))&&!isNaN(Number(e))}(e)?Number(e):t}function s_(e){return Array.isArray(e)?e:[e]}function o_(e){return null==e?"":"string"==typeof e?e:e+"px"}function a_(e){return e instanceof Pa?e.nativeElement:e}class l_{constructor(e,t){this.compare=e,this.keySelector=t}call(e,t){return t.subscribe(new c_(e,this.compare,this.keySelector))}}class c_ extends p{constructor(e,t,i){super(e),this.keySelector=i,this.hasKey=!1,"function"==typeof t&&(this.compare=t)}compare(e,t){return e===t}_next(e){let t;try{const{keySelector:i}=this;t=i?i(e):e}catch(r){return this.destination.error(r)}let i=!1;if(this.hasKey)try{const{compare:e}=this;i=e(this.key,t)}catch(r){return this.destination.error(r)}else this.hasKey=!0;i||(this.key=t,this.destination.next(e))}}class h_{constructor(e){this.durationSelector=e}call(e,t){return t.subscribe(new u_(e,this.durationSelector))}}class u_ extends I{constructor(e,t){super(e),this.durationSelector=t,this.hasValue=!1}_next(e){if(this.value=e,this.hasValue=!0,!this.throttled){let i;try{const{durationSelector:t}=this;i=t(e)}catch(t){return this.destination.error(t)}const r=D(this,i);!r||r.closed?this.clearThrottle():this.add(this.throttled=r)}}clearThrottle(){const{value:e,hasValue:t,throttled:i}=this;i&&(this.remove(i),this.throttled=null,i.unsubscribe()),t&&(this.value=null,this.hasValue=!1,this.destination.next(e))}notifyNext(e,t,i,r){this.clearThrottle()}notifyComplete(){this.clearThrottle()}}function d_(e){return!l(e)&&e-parseFloat(e)+1>=0}function f_(e){const{index:t,period:i,subscriber:r}=e;if(r.next(t),!r.closed){if(-1===i)return r.complete();e.index=t+1,this.schedule(e,i)}}function p_(e,t=lp){return i=()=>function(e=0,t,i){let r=-1;return d_(t)?r=Number(t)<1?1:Number(t):x(t)&&(i=t),x(i)||(i=lp),new v(t=>{const n=d_(e)?e:+e-i.now();return i.schedule(f_,n,{index:0,period:r,subscriber:t})})}(e,t),function(e){return e.lift(new h_(i))};var i}function __(e){return t=>t.lift(new m_(e))}class m_{constructor(e){this.notifier=e}call(e,t){const i=new g_(e),r=D(i,this.notifier);return r&&!i.seenValue?(i.add(r),t.subscribe(i)):i}}class g_ extends I{constructor(e){super(e),this.seenValue=!1}notifyNext(e,t,i,r,n){this.seenValue=!0,this.complete()}notifyComplete(){}}function v_(...e){const t=e[e.length-1];return x(t)?(e.pop(),i=>Vf(e,i,t)):t=>Vf(e,t)}class y_ extends sp{constructor(e,t){super(e,t),this.scheduler=e,this.work=t}schedule(e,t=0){return t>0?super.schedule(e,t):(this.delay=t,this.state=e,this.scheduler.flush(this),this)}execute(e,t){return t>0||this.closed?super.execute(e,t):this._execute(e,t)}requestAsyncId(e,t,i=0){return null!==i&&i>0||null===i&&this.delay>0?super.requestAsyncId(e,t,i):e.flush(this)}}class b_ extends ap{}const w_=new b_(y_);class C_ extends p{constructor(e,t,i=0){super(e),this.scheduler=t,this.delay=i}static dispatch(e){const{notification:t,destination:i}=e;t.observe(i),this.unsubscribe()}scheduleMessage(e){this.destination.add(this.scheduler.schedule(C_.dispatch,this.delay,new S_(e,this.destination)))}_next(e){this.scheduleMessage(cp.createNext(e))}_error(e){this.scheduleMessage(cp.createError(e)),this.unsubscribe()}_complete(){this.scheduleMessage(cp.createComplete()),this.unsubscribe()}}class S_{constructor(e,t){this.notification=e,this.destination=t}}class k_ extends S{constructor(e=Number.POSITIVE_INFINITY,t=Number.POSITIVE_INFINITY,i){super(),this.scheduler=i,this._events=[],this._infiniteTimeWindow=!1,this._bufferSize=e<1?1:e,this._windowTime=t<1?1:t,t===Number.POSITIVE_INFINITY?(this._infiniteTimeWindow=!0,this.next=this.nextInfiniteTimeWindow):this.next=this.nextTimeWindow}nextInfiniteTimeWindow(e){const t=this._events;t.push(e),t.length>this._bufferSize&&t.shift(),super.next(e)}nextTimeWindow(e){this._events.push(new x_(this._getNow(),e)),this._trimBufferThenGetEvents(),super.next(e)}_subscribe(e){const t=this._infiniteTimeWindow,i=t?this._events:this._trimBufferThenGetEvents(),r=this.scheduler,n=i.length;let s;if(this.closed)throw new b;if(this.isStopped||this.hasError?s=u.EMPTY:(this.observers.push(e),s=new w(this,e)),r&&e.add(e=new C_(e,r)),t)for(let o=0;ot&&(s=Math.max(s,n-t)),s>0&&r.splice(0,s),r}}class x_{constructor(e,t){this.time=e,this.value=t}}function E_(e,t,i){let r;return r=e&&"object"==typeof e?e:{bufferSize:e,windowTime:t,refCount:!1,scheduler:i},e=>e.lift(function({bufferSize:e=Number.POSITIVE_INFINITY,windowTime:t=Number.POSITIVE_INFINITY,refCount:i,scheduler:r}){let n,s,o=0,a=!1,l=!1;return function(c){o++,n&&!a||(a=!1,n=new k_(e,t,r),s=c.subscribe({next(e){n.next(e)},error(e){a=!0,n.error(e)},complete(){l=!0,s=void 0,n.complete()}}));const h=n.subscribe(this);this.add(()=>{o--,h.unsubscribe(),s&&!l&&i&&0===o&&(s.unsubscribe(),s=void 0,n=void 0)})}}(r))}let A_;try{A_="undefined"!=typeof Intl&&Intl.v8BreakIterator}catch(px){A_=!1}let O_,T_=(()=>{class e{constructor(e){this._platformId=e,this.isBrowser=this._platformId?Ph(this._platformId):"object"==typeof document&&!!document,this.EDGE=this.isBrowser&&/(edge)/i.test(navigator.userAgent),this.TRIDENT=this.isBrowser&&/(msie|trident)/i.test(navigator.userAgent),this.BLINK=this.isBrowser&&!(!window.chrome&&!A_)&&"undefined"!=typeof CSS&&!this.EDGE&&!this.TRIDENT,this.WEBKIT=this.isBrowser&&/AppleWebKit/i.test(navigator.userAgent)&&!this.BLINK&&!this.EDGE&&!this.TRIDENT,this.IOS=this.isBrowser&&/iPad|iPhone|iPod/.test(navigator.userAgent)&&!("MSStream"in window),this.FIREFOX=this.isBrowser&&/(firefox|minefield)/i.test(navigator.userAgent),this.ANDROID=this.isBrowser&&/android/i.test(navigator.userAgent)&&!this.TRIDENT,this.SAFARI=this.isBrowser&&/safari/i.test(navigator.userAgent)&&this.WEBKIT}}return e.\u0275fac=function(t){return new(t||e)(Ze(tc))},e.\u0275prov=ue({factory:function(){return new e(Ze(tc))},token:e,providedIn:"root"}),e})(),R_=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)}}),e})();const L_=["color","button","checkbox","date","datetime-local","email","file","hidden","image","month","number","password","radio","range","reset","search","submit","tel","text","time","url","week"];function P_(){if(O_)return O_;if("object"!=typeof document||!document)return O_=new Set(L_),O_;let e=document.createElement("input");return O_=new Set(L_.filter(t=>(e.setAttribute("type",t),e.type===t))),O_}let D_,I_;function M_(e){return function(){if(null==D_&&"undefined"!=typeof window)try{window.addEventListener("test",null,Object.defineProperty({},"passive",{get:()=>D_=!0}))}finally{D_=D_||!1}return D_}()?e:!!e.capture}function F_(e){if(function(){if(null==I_){const e="undefined"!=typeof document?document.head:null;I_=!(!e||!e.createShadowRoot&&!e.attachShadow)}return I_}()){const t=e.getRootNode?e.getRootNode():null;if("undefined"!=typeof ShadowRoot&&ShadowRoot&&t instanceof ShadowRoot)return t}return null}const N_=new Be("cdk-dir-doc",{providedIn:"root",factory:function(){return Ye(Kc)}});let j_=(()=>{class e{constructor(e){if(this.value="ltr",this.change=new El,e){const t=e.documentElement?e.documentElement.dir:null,i=(e.body?e.body.dir:null)||t;this.value="ltr"===i||"rtl"===i?i:"ltr"}}ngOnDestroy(){this.change.complete()}}return e.\u0275fac=function(t){return new(t||e)(Ze(N_,8))},e.\u0275prov=ue({factory:function(){return new e(Ze(N_,8))},token:e,providedIn:"root"}),e})(),B_=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)}}),e})();class H_{constructor(e=!1,t,i=!0){this._multiple=e,this._emitChanges=i,this._selection=new Set,this._deselectedToEmit=[],this._selectedToEmit=[],this.changed=new S,t&&t.length&&(e?t.forEach(e=>this._markSelected(e)):this._markSelected(t[0]),this._selectedToEmit.length=0)}get selected(){return this._selected||(this._selected=Array.from(this._selection.values())),this._selected}select(...e){this._verifyValueAssignment(e),e.forEach(e=>this._markSelected(e)),this._emitChangeEvent()}deselect(...e){this._verifyValueAssignment(e),e.forEach(e=>this._unmarkSelected(e)),this._emitChangeEvent()}toggle(e){this.isSelected(e)?this.deselect(e):this.select(e)}clear(){this._unmarkAll(),this._emitChangeEvent()}isSelected(e){return this._selection.has(e)}isEmpty(){return 0===this._selection.size}hasValue(){return!this.isEmpty()}sort(e){this._multiple&&this.selected&&this._selected.sort(e)}isMultipleSelection(){return this._multiple}_emitChangeEvent(){this._selected=null,(this._selectedToEmit.length||this._deselectedToEmit.length)&&(this.changed.next({source:this,added:this._selectedToEmit,removed:this._deselectedToEmit}),this._deselectedToEmit=[],this._selectedToEmit=[])}_markSelected(e){this.isSelected(e)||(this._multiple||this._unmarkAll(),this._selection.add(e),this._emitChanges&&this._selectedToEmit.push(e))}_unmarkSelected(e){this.isSelected(e)&&(this._selection.delete(e),this._emitChanges&&this._deselectedToEmit.push(e))}_unmarkAll(){this.isEmpty()||this._selection.forEach(e=>this._unmarkSelected(e))}_verifyValueAssignment(e){}}let U_=(()=>{class e{constructor(e,t,i){this._ngZone=e,this._platform=t,this._scrolled=new S,this._globalSubscription=null,this._scrolledCount=0,this.scrollContainers=new Map,this._document=i}register(e){this.scrollContainers.has(e)||this.scrollContainers.set(e,e.elementScrolled().subscribe(()=>this._scrolled.next(e)))}deregister(e){const t=this.scrollContainers.get(e);t&&(t.unsubscribe(),this.scrollContainers.delete(e))}scrolled(e=20){return this._platform.isBrowser?new v(t=>{this._globalSubscription||this._addGlobalListener();const i=e>0?this._scrolled.pipe(p_(e)).subscribe(t):this._scrolled.subscribe(t);return this._scrolledCount++,()=>{i.unsubscribe(),this._scrolledCount--,this._scrolledCount||this._removeGlobalListener()}}):Hf()}ngOnDestroy(){this._removeGlobalListener(),this.scrollContainers.forEach((e,t)=>this.deregister(t)),this._scrolled.complete()}ancestorScrolled(e,t){const i=this.getAncestorScrollContainers(e);return this.scrolled(t).pipe(Wf(e=>!e||i.indexOf(e)>-1))}getAncestorScrollContainers(e){const t=[];return this.scrollContainers.forEach((i,r)=>{this._scrollableContainsElement(r,e)&&t.push(r)}),t}_getDocument(){return this._document||document}_getWindow(){return this._getDocument().defaultView||window}_scrollableContainsElement(e,t){let i=t.nativeElement,r=e.getElementRef().nativeElement;do{if(i==r)return!0}while(i=i.parentElement);return!1}_addGlobalListener(){this._globalSubscription=this._ngZone.runOutsideAngular(()=>Bf(this._getWindow().document,"scroll").subscribe(()=>this._scrolled.next()))}_removeGlobalListener(){this._globalSubscription&&(this._globalSubscription.unsubscribe(),this._globalSubscription=null)}}return e.\u0275fac=function(t){return new(t||e)(Ze(mc),Ze(T_),Ze(Kc,8))},e.\u0275prov=ue({factory:function(){return new e(Ze(mc),Ze(T_),Ze(Kc,8))},token:e,providedIn:"root"}),e})(),V_=(()=>{class e{constructor(e,t,i){this._platform=e,this._change=new S,this._changeListener=e=>{this._change.next(e)},this._document=i,t.runOutsideAngular(()=>{if(e.isBrowser){const e=this._getWindow();e.addEventListener("resize",this._changeListener),e.addEventListener("orientationchange",this._changeListener)}this.change().subscribe(()=>this._updateViewportSize())})}ngOnDestroy(){if(this._platform.isBrowser){const e=this._getWindow();e.removeEventListener("resize",this._changeListener),e.removeEventListener("orientationchange",this._changeListener)}this._change.complete()}getViewportSize(){this._viewportSize||this._updateViewportSize();const e={width:this._viewportSize.width,height:this._viewportSize.height};return this._platform.isBrowser||(this._viewportSize=null),e}getViewportRect(){const e=this.getViewportScrollPosition(),{width:t,height:i}=this.getViewportSize();return{top:e.top,left:e.left,bottom:e.top+i,right:e.left+t,height:i,width:t}}getViewportScrollPosition(){if(!this._platform.isBrowser)return{top:0,left:0};const e=this._getDocument(),t=this._getWindow(),i=e.documentElement,r=i.getBoundingClientRect();return{top:-r.top||e.body.scrollTop||t.scrollY||i.scrollTop||0,left:-r.left||e.body.scrollLeft||t.scrollX||i.scrollLeft||0}}change(e=20){return e>0?this._change.pipe(p_(e)):this._change}_getDocument(){return this._document||document}_getWindow(){return this._getDocument().defaultView||window}_updateViewportSize(){const e=this._getWindow();this._viewportSize=this._platform.isBrowser?{width:e.innerWidth,height:e.innerHeight}:{width:0,height:0}}}return e.\u0275fac=function(t){return new(t||e)(Ze(T_),Ze(mc),Ze(Kc,8))},e.\u0275prov=ue({factory:function(){return new e(Ze(T_),Ze(mc),Ze(Kc,8))},token:e,providedIn:"root"}),e})(),q_=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)}}),e})(),z_=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[B_,R_,q_],B_,q_]}),e})();class W_{attach(e){return this._attachedHost=e,e.attach(this)}detach(){let e=this._attachedHost;null!=e&&(this._attachedHost=null,e.detach())}get isAttached(){return null!=this._attachedHost}setAttachedHost(e){this._attachedHost=e}}class $_ extends W_{constructor(e,t,i,r){super(),this.component=e,this.viewContainerRef=t,this.injector=i,this.componentFactoryResolver=r}}class K_ extends W_{constructor(e,t,i){super(),this.templateRef=e,this.viewContainerRef=t,this.context=i}get origin(){return this.templateRef.elementRef}attach(e,t=this.context){return this.context=t,super.attach(e)}detach(){return this.context=void 0,super.detach()}}class G_ extends W_{constructor(e){super(),this.element=e instanceof Pa?e.nativeElement:e}}class Z_{constructor(){this._isDisposed=!1,this.attachDomPortal=null}hasAttached(){return!!this._attachedPortal}attach(e){return e instanceof $_?(this._attachedPortal=e,this.attachComponentPortal(e)):e instanceof K_?(this._attachedPortal=e,this.attachTemplatePortal(e)):this.attachDomPortal&&e instanceof G_?(this._attachedPortal=e,this.attachDomPortal(e)):void 0}detach(){this._attachedPortal&&(this._attachedPortal.setAttachedHost(null),this._attachedPortal=null),this._invokeDisposeFn()}dispose(){this.hasAttached()&&this.detach(),this._invokeDisposeFn(),this._isDisposed=!0}setDisposeFn(e){this._disposeFn=e}_invokeDisposeFn(){this._disposeFn&&(this._disposeFn(),this._disposeFn=null)}}class Y_ extends Z_{constructor(e,t,i,r,n){super(),this.outletElement=e,this._componentFactoryResolver=t,this._appRef=i,this._defaultInjector=r,this.attachDomPortal=e=>{const t=e.element,i=this._document.createComment("dom-portal");t.parentNode.insertBefore(i,t),this.outletElement.appendChild(t),super.setDisposeFn(()=>{i.parentNode&&i.parentNode.replaceChild(t,i)})},this._document=n}attachComponentPortal(e){const t=(e.componentFactoryResolver||this._componentFactoryResolver).resolveComponentFactory(e.component);let i;return e.viewContainerRef?(i=e.viewContainerRef.createComponent(t,e.viewContainerRef.length,e.injector||e.viewContainerRef.injector),this.setDisposeFn(()=>i.destroy())):(i=t.create(e.injector||this._defaultInjector),this._appRef.attachView(i.hostView),this.setDisposeFn(()=>{this._appRef.detachView(i.hostView),i.destroy()})),this.outletElement.appendChild(this._getComponentRootNode(i)),i}attachTemplatePortal(e){let t=e.viewContainerRef,i=t.createEmbeddedView(e.templateRef,e.context);return i.rootNodes.forEach(e=>this.outletElement.appendChild(e)),i.detectChanges(),this.setDisposeFn(()=>{let e=t.indexOf(i);-1!==e&&t.remove(e)}),i}dispose(){super.dispose(),null!=this.outletElement.parentNode&&this.outletElement.parentNode.removeChild(this.outletElement)}_getComponentRootNode(e){return e.hostView.rootNodes[0]}}let X_=(()=>{class e extends Z_{constructor(e,t,i){super(),this._componentFactoryResolver=e,this._viewContainerRef=t,this._isInitialized=!1,this.attached=new El,this.attachDomPortal=e=>{const t=e.element,i=this._document.createComment("dom-portal");e.setAttachedHost(this),t.parentNode.insertBefore(i,t),this._getRootNode().appendChild(t),super.setDisposeFn(()=>{i.parentNode&&i.parentNode.replaceChild(t,i)})},this._document=i}get portal(){return this._attachedPortal}set portal(e){(!this.hasAttached()||e||this._isInitialized)&&(this.hasAttached()&&super.detach(),e&&super.attach(e),this._attachedPortal=e)}get attachedRef(){return this._attachedRef}ngOnInit(){this._isInitialized=!0}ngOnDestroy(){super.dispose(),this._attachedPortal=null,this._attachedRef=null}attachComponentPortal(e){e.setAttachedHost(this);const t=null!=e.viewContainerRef?e.viewContainerRef:this._viewContainerRef,i=(e.componentFactoryResolver||this._componentFactoryResolver).resolveComponentFactory(e.component),r=t.createComponent(i,t.length,e.injector||t.injector);return t!==this._viewContainerRef&&this._getRootNode().appendChild(r.hostView.rootNodes[0]),super.setDisposeFn(()=>r.destroy()),this._attachedPortal=e,this._attachedRef=r,this.attached.emit(r),r}attachTemplatePortal(e){e.setAttachedHost(this);const t=this._viewContainerRef.createEmbeddedView(e.templateRef,e.context);return super.setDisposeFn(()=>this._viewContainerRef.clear()),this._attachedPortal=e,this._attachedRef=t,this.attached.emit(t),t}_getRootNode(){const e=this._viewContainerRef.element.nativeElement;return e.nodeType===e.ELEMENT_NODE?e:e.parentNode}}return e.\u0275fac=function(t){return new(t||e)(Co(La),Co(nl),Co(Kc))},e.\u0275dir=bt({type:e,selectors:[["","cdkPortalOutlet",""]],inputs:{portal:["cdkPortalOutlet","portal"]},outputs:{attached:"attached"},exportAs:["cdkPortalOutlet"],features:[ma]}),e})(),Q_=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)}}),e})();class J_{constructor(e,t){this.predicate=e,this.inclusive=t}call(e,t){return t.subscribe(new em(e,this.predicate,this.inclusive))}}class em extends p{constructor(e,t,i){super(e),this.predicate=t,this.inclusive=i,this.index=0}_next(e){const t=this.destination;let i;try{i=this.predicate(e,this.index++)}catch(r){return void t.error(r)}this.nextOrComplete(e,i)}nextOrComplete(e,t){const i=this.destination;Boolean(t)?i.next(e):(this.inclusive&&i.next(e),i.complete())}}function tm(e,...t){return t.length?t.some(t=>e[t]):e.altKey||e.shiftKey||e.ctrlKey||e.metaKey}class im{constructor(e,t){this._viewportRuler=e,this._previousHTMLStyles={top:"",left:""},this._isEnabled=!1,this._document=t}attach(){}enable(){if(this._canBeEnabled()){const e=this._document.documentElement;this._previousScrollPosition=this._viewportRuler.getViewportScrollPosition(),this._previousHTMLStyles.left=e.style.left||"",this._previousHTMLStyles.top=e.style.top||"",e.style.left=o_(-this._previousScrollPosition.left),e.style.top=o_(-this._previousScrollPosition.top),e.classList.add("cdk-global-scrollblock"),this._isEnabled=!0}}disable(){if(this._isEnabled){const e=this._document.documentElement,t=e.style,i=this._document.body.style,r=t.scrollBehavior||"",n=i.scrollBehavior||"";this._isEnabled=!1,t.left=this._previousHTMLStyles.left,t.top=this._previousHTMLStyles.top,e.classList.remove("cdk-global-scrollblock"),t.scrollBehavior=i.scrollBehavior="auto",window.scroll(this._previousScrollPosition.left,this._previousScrollPosition.top),t.scrollBehavior=r,i.scrollBehavior=n}}_canBeEnabled(){if(this._document.documentElement.classList.contains("cdk-global-scrollblock")||this._isEnabled)return!1;const e=this._document.body,t=this._viewportRuler.getViewportSize();return e.scrollHeight>t.height||e.scrollWidth>t.width}}class rm{constructor(e,t,i,r){this._scrollDispatcher=e,this._ngZone=t,this._viewportRuler=i,this._config=r,this._scrollSubscription=null,this._detach=()=>{this.disable(),this._overlayRef.hasAttached()&&this._ngZone.run(()=>this._overlayRef.detach())}}attach(e){this._overlayRef=e}enable(){if(this._scrollSubscription)return;const e=this._scrollDispatcher.scrolled(0);this._config&&this._config.threshold&&this._config.threshold>1?(this._initialScrollPosition=this._viewportRuler.getViewportScrollPosition().top,this._scrollSubscription=e.subscribe(()=>{const e=this._viewportRuler.getViewportScrollPosition().top;Math.abs(e-this._initialScrollPosition)>this._config.threshold?this._detach():this._overlayRef.updatePosition()})):this._scrollSubscription=e.subscribe(this._detach)}disable(){this._scrollSubscription&&(this._scrollSubscription.unsubscribe(),this._scrollSubscription=null)}detach(){this.disable(),this._overlayRef=null}}class nm{enable(){}disable(){}attach(){}}function sm(e,t){return t.some(t=>e.bottomt.bottom||e.rightt.right)}function om(e,t){return t.some(t=>e.topt.bottom||e.leftt.right)}class am{constructor(e,t,i,r){this._scrollDispatcher=e,this._viewportRuler=t,this._ngZone=i,this._config=r,this._scrollSubscription=null}attach(e){this._overlayRef=e}enable(){this._scrollSubscription||(this._scrollSubscription=this._scrollDispatcher.scrolled(this._config?this._config.scrollThrottle:0).subscribe(()=>{if(this._overlayRef.updatePosition(),this._config&&this._config.autoClose){const e=this._overlayRef.overlayElement.getBoundingClientRect(),{width:t,height:i}=this._viewportRuler.getViewportSize();sm(e,[{width:t,height:i,bottom:i,right:t,top:0,left:0}])&&(this.disable(),this._ngZone.run(()=>this._overlayRef.detach()))}}))}disable(){this._scrollSubscription&&(this._scrollSubscription.unsubscribe(),this._scrollSubscription=null)}detach(){this.disable(),this._overlayRef=null}}let lm=(()=>{class e{constructor(e,t,i,r){this._scrollDispatcher=e,this._viewportRuler=t,this._ngZone=i,this.noop=()=>new nm,this.close=e=>new rm(this._scrollDispatcher,this._ngZone,this._viewportRuler,e),this.block=()=>new im(this._viewportRuler,this._document),this.reposition=e=>new am(this._scrollDispatcher,this._viewportRuler,this._ngZone,e),this._document=r}}return e.\u0275fac=function(t){return new(t||e)(Ze(U_),Ze(V_),Ze(mc),Ze(Kc))},e.\u0275prov=ue({factory:function(){return new e(Ze(U_),Ze(V_),Ze(mc),Ze(Kc))},token:e,providedIn:"root"}),e})();class cm{constructor(e){if(this.scrollStrategy=new nm,this.panelClass="",this.hasBackdrop=!1,this.backdropClass="cdk-overlay-dark-backdrop",this.disposeOnNavigation=!1,e){const t=Object.keys(e);for(const i of t)void 0!==e[i]&&(this[i]=e[i])}}}class hm{constructor(e,t,i,r,n){this.offsetX=i,this.offsetY=r,this.panelClass=n,this.originX=e.originX,this.originY=e.originY,this.overlayX=t.overlayX,this.overlayY=t.overlayY}}class um{constructor(e,t){this.connectionPair=e,this.scrollableViewProperties=t}}let dm=(()=>{class e{constructor(e){this._attachedOverlays=[],this._document=e}ngOnDestroy(){this.detach()}add(e){this.remove(e),this._attachedOverlays.push(e)}remove(e){const t=this._attachedOverlays.indexOf(e);t>-1&&this._attachedOverlays.splice(t,1),0===this._attachedOverlays.length&&this.detach()}}return e.\u0275fac=function(t){return new(t||e)(Ze(Kc))},e.\u0275prov=ue({factory:function(){return new e(Ze(Kc))},token:e,providedIn:"root"}),e})(),fm=(()=>{class e extends dm{constructor(e){super(e),this._keydownListener=e=>{const t=this._attachedOverlays;for(let i=t.length-1;i>-1;i--)if(t[i]._keydownEvents.observers.length>0){t[i]._keydownEvents.next(e);break}}}add(e){super.add(e),this._isAttached||(this._document.body.addEventListener("keydown",this._keydownListener),this._isAttached=!0)}detach(){this._isAttached&&(this._document.body.removeEventListener("keydown",this._keydownListener),this._isAttached=!1)}}return e.\u0275fac=function(t){return new(t||e)(Ze(Kc))},e.\u0275prov=ue({factory:function(){return new e(Ze(Kc))},token:e,providedIn:"root"}),e})(),pm=(()=>{class e extends dm{constructor(e,t){super(e),this._platform=t,this._cursorStyleIsSet=!1,this._clickListener=e=>{const t=e.composedPath?e.composedPath()[0]:e.target,i=this._attachedOverlays.slice();for(let r=i.length-1;r>-1;r--){const n=i[r];if(!(n._outsidePointerEvents.observers.length<1)&&n.hasAttached()){if(n.overlayElement.contains(t))break;n._outsidePointerEvents.next(e)}}}}add(e){super.add(e),this._isAttached||(this._document.body.addEventListener("click",this._clickListener,!0),this._document.body.addEventListener("contextmenu",this._clickListener,!0),this._platform.IOS&&!this._cursorStyleIsSet&&(this._cursorOriginalValue=this._document.body.style.cursor,this._document.body.style.cursor="pointer",this._cursorStyleIsSet=!0),this._isAttached=!0)}detach(){this._isAttached&&(this._document.body.removeEventListener("click",this._clickListener,!0),this._document.body.removeEventListener("contextmenu",this._clickListener,!0),this._platform.IOS&&this._cursorStyleIsSet&&(this._document.body.style.cursor=this._cursorOriginalValue,this._cursorStyleIsSet=!1),this._isAttached=!1)}}return e.\u0275fac=function(t){return new(t||e)(Ze(Kc),Ze(T_))},e.\u0275prov=ue({factory:function(){return new e(Ze(Kc),Ze(T_))},token:e,providedIn:"root"}),e})();const _m=!("undefined"==typeof window||!window||!window.__karma__&&!window.jasmine);let mm=(()=>{class e{constructor(e,t){this._platform=t,this._document=e}ngOnDestroy(){const e=this._containerElement;e&&e.parentNode&&e.parentNode.removeChild(e)}getContainerElement(){return this._containerElement||this._createContainer(),this._containerElement}_createContainer(){const e=this._platform?this._platform.isBrowser:"undefined"!=typeof window;if(e||_m){const e=this._document.querySelectorAll('.cdk-overlay-container[platform="server"], .cdk-overlay-container[platform="test"]');for(let t=0;tthis._backdropClick.next(e),this._keydownEvents=new S,this._outsidePointerEvents=new S,r.scrollStrategy&&(this._scrollStrategy=r.scrollStrategy,this._scrollStrategy.attach(this)),this._positionStrategy=r.positionStrategy}get overlayElement(){return this._pane}get backdropElement(){return this._backdropElement}get hostElement(){return this._host}attach(e){let t=this._portalOutlet.attach(e);return!this._host.parentElement&&this._previousHostParent&&this._previousHostParent.appendChild(this._host),this._positionStrategy&&this._positionStrategy.attach(this),this._updateStackingOrder(),this._updateElementSize(),this._updateElementDirection(),this._scrollStrategy&&this._scrollStrategy.enable(),this._ngZone.onStable.pipe(Qf(1)).subscribe(()=>{this.hasAttached()&&this.updatePosition()}),this._togglePointerEvents(!0),this._config.hasBackdrop&&this._attachBackdrop(),this._config.panelClass&&this._toggleClasses(this._pane,this._config.panelClass,!0),this._attachments.next(),this._keyboardDispatcher.add(this),this._config.disposeOnNavigation&&this._location&&(this._locationChanges=this._location.subscribe(()=>this.dispose())),this._outsideClickDispatcher&&this._outsideClickDispatcher.add(this),t}detach(){if(!this.hasAttached())return;this.detachBackdrop(),this._togglePointerEvents(!1),this._positionStrategy&&this._positionStrategy.detach&&this._positionStrategy.detach(),this._scrollStrategy&&this._scrollStrategy.disable();const e=this._portalOutlet.detach();return this._detachments.next(),this._keyboardDispatcher.remove(this),this._detachContentWhenStable(),this._locationChanges.unsubscribe(),this._outsideClickDispatcher&&this._outsideClickDispatcher.remove(this),e}dispose(){const e=this.hasAttached();this._positionStrategy&&this._positionStrategy.dispose(),this._disposeScrollStrategy(),this.detachBackdrop(),this._locationChanges.unsubscribe(),this._keyboardDispatcher.remove(this),this._portalOutlet.dispose(),this._attachments.complete(),this._backdropClick.complete(),this._keydownEvents.complete(),this._outsidePointerEvents.complete(),this._outsideClickDispatcher&&this._outsideClickDispatcher.remove(this),this._host&&this._host.parentNode&&(this._host.parentNode.removeChild(this._host),this._host=null),this._previousHostParent=this._pane=null,e&&this._detachments.next(),this._detachments.complete()}hasAttached(){return this._portalOutlet.hasAttached()}backdropClick(){return this._backdropClick}attachments(){return this._attachments}detachments(){return this._detachments}keydownEvents(){return this._keydownEvents}outsidePointerEvents(){return this._outsidePointerEvents}getConfig(){return this._config}updatePosition(){this._positionStrategy&&this._positionStrategy.apply()}updatePositionStrategy(e){e!==this._positionStrategy&&(this._positionStrategy&&this._positionStrategy.dispose(),this._positionStrategy=e,this.hasAttached()&&(e.attach(this),this.updatePosition()))}updateSize(e){this._config=Object.assign(Object.assign({},this._config),e),this._updateElementSize()}setDirection(e){this._config=Object.assign(Object.assign({},this._config),{direction:e}),this._updateElementDirection()}addPanelClass(e){this._pane&&this._toggleClasses(this._pane,e,!0)}removePanelClass(e){this._pane&&this._toggleClasses(this._pane,e,!1)}getDirection(){const e=this._config.direction;return e?"string"==typeof e?e:e.value:"ltr"}updateScrollStrategy(e){e!==this._scrollStrategy&&(this._disposeScrollStrategy(),this._scrollStrategy=e,this.hasAttached()&&(e.attach(this),e.enable()))}_updateElementDirection(){this._host.setAttribute("dir",this.getDirection())}_updateElementSize(){if(!this._pane)return;const e=this._pane.style;e.width=o_(this._config.width),e.height=o_(this._config.height),e.minWidth=o_(this._config.minWidth),e.minHeight=o_(this._config.minHeight),e.maxWidth=o_(this._config.maxWidth),e.maxHeight=o_(this._config.maxHeight)}_togglePointerEvents(e){this._pane.style.pointerEvents=e?"auto":"none"}_attachBackdrop(){this._backdropElement=this._document.createElement("div"),this._backdropElement.classList.add("cdk-overlay-backdrop"),this._config.backdropClass&&this._toggleClasses(this._backdropElement,this._config.backdropClass,!0),this._host.parentElement.insertBefore(this._backdropElement,this._host),this._backdropElement.addEventListener("click",this._backdropClickHandler),"undefined"!=typeof requestAnimationFrame?this._ngZone.runOutsideAngular(()=>{requestAnimationFrame(()=>{this._backdropElement&&this._backdropElement.classList.add("cdk-overlay-backdrop-showing")})}):this._backdropElement.classList.add("cdk-overlay-backdrop-showing")}_updateStackingOrder(){this._host.nextSibling&&this._host.parentNode.appendChild(this._host)}detachBackdrop(){let e,t=this._backdropElement;if(!t)return;let i=()=>{t&&(t.removeEventListener("click",this._backdropClickHandler),t.removeEventListener("transitionend",i),t.parentNode&&t.parentNode.removeChild(t)),this._backdropElement==t&&(this._backdropElement=null),this._config.backdropClass&&this._toggleClasses(t,this._config.backdropClass,!1),clearTimeout(e)};t.classList.remove("cdk-overlay-backdrop-showing"),this._ngZone.runOutsideAngular(()=>{t.addEventListener("transitionend",i)}),t.style.pointerEvents="none",e=this._ngZone.runOutsideAngular(()=>setTimeout(i,500))}_toggleClasses(e,t,i){const r=e.classList;s_(t).forEach(e=>{e&&(i?r.add(e):r.remove(e))})}_detachContentWhenStable(){this._ngZone.runOutsideAngular(()=>{const e=this._ngZone.onStable.pipe(__(W(this._attachments,this._detachments))).subscribe(()=>{this._pane&&this._host&&0!==this._pane.children.length||(this._pane&&this._config.panelClass&&this._toggleClasses(this._pane,this._config.panelClass,!1),this._host&&this._host.parentElement&&(this._previousHostParent=this._host.parentElement,this._previousHostParent.removeChild(this._host)),e.unsubscribe())})})}_disposeScrollStrategy(){const e=this._scrollStrategy;e&&(e.disable(),e.detach&&e.detach())}}const vm=/([A-Za-z%]+)$/;class ym{constructor(e,t,i,r,n){this._viewportRuler=t,this._document=i,this._platform=r,this._overlayContainer=n,this._lastBoundingBoxSize={width:0,height:0},this._isPushed=!1,this._canPush=!0,this._growAfterOpen=!1,this._hasFlexibleDimensions=!0,this._positionLocked=!1,this._viewportMargin=0,this._scrollables=[],this._preferredPositions=[],this._positionChanges=new S,this._resizeSubscription=u.EMPTY,this._offsetX=0,this._offsetY=0,this._appliedPanelClasses=[],this.positionChanges=this._positionChanges,this.setOrigin(e)}get positions(){return this._preferredPositions}attach(e){this._validatePositions(),e.hostElement.classList.add("cdk-overlay-connected-position-bounding-box"),this._overlayRef=e,this._boundingBox=e.hostElement,this._pane=e.overlayElement,this._isDisposed=!1,this._isInitialRender=!0,this._lastPosition=null,this._resizeSubscription.unsubscribe(),this._resizeSubscription=this._viewportRuler.change().subscribe(()=>{this._isInitialRender=!0,this.apply()})}apply(){if(this._isDisposed||!this._platform.isBrowser)return;if(!this._isInitialRender&&this._positionLocked&&this._lastPosition)return void this.reapplyLastPosition();this._clearPanelClasses(),this._resetOverlayElementStyles(),this._resetBoundingBoxStyles(),this._viewportRect=this._getNarrowedViewportRect(),this._originRect=this._getOriginRect(),this._overlayRect=this._pane.getBoundingClientRect();const e=this._originRect,t=this._overlayRect,i=this._viewportRect,r=[];let n;for(let s of this._preferredPositions){let o=this._getOriginPoint(e,s),a=this._getOverlayPoint(o,t,s),l=this._getOverlayFit(a,t,i,s);if(l.isCompletelyWithinViewport)return this._isPushed=!1,void this._applyPosition(s,o);this._canFitWithFlexibleDimensions(l,a,i)?r.push({position:s,origin:o,overlayRect:t,boundingBoxRect:this._calculateBoundingBoxRect(o,s)}):(!n||n.overlayFit.visibleAreat&&(t=r,e=i)}return this._isPushed=!1,void this._applyPosition(e.position,e.origin)}if(this._canPush)return this._isPushed=!0,void this._applyPosition(n.position,n.originPoint);this._applyPosition(n.position,n.originPoint)}detach(){this._clearPanelClasses(),this._lastPosition=null,this._previousPushAmount=null,this._resizeSubscription.unsubscribe()}dispose(){this._isDisposed||(this._boundingBox&&bm(this._boundingBox.style,{top:"",left:"",right:"",bottom:"",height:"",width:"",alignItems:"",justifyContent:""}),this._pane&&this._resetOverlayElementStyles(),this._overlayRef&&this._overlayRef.hostElement.classList.remove("cdk-overlay-connected-position-bounding-box"),this.detach(),this._positionChanges.complete(),this._overlayRef=this._boundingBox=null,this._isDisposed=!0)}reapplyLastPosition(){if(!this._isDisposed&&(!this._platform||this._platform.isBrowser)){this._originRect=this._getOriginRect(),this._overlayRect=this._pane.getBoundingClientRect(),this._viewportRect=this._getNarrowedViewportRect();const e=this._lastPosition||this._preferredPositions[0],t=this._getOriginPoint(this._originRect,e);this._applyPosition(e,t)}}withScrollableContainers(e){return this._scrollables=e,this}withPositions(e){return this._preferredPositions=e,-1===e.indexOf(this._lastPosition)&&(this._lastPosition=null),this._validatePositions(),this}withViewportMargin(e){return this._viewportMargin=e,this}withFlexibleDimensions(e=!0){return this._hasFlexibleDimensions=e,this}withGrowAfterOpen(e=!0){return this._growAfterOpen=e,this}withPush(e=!0){return this._canPush=e,this}withLockedPosition(e=!0){return this._positionLocked=e,this}setOrigin(e){return this._origin=e,this}withDefaultOffsetX(e){return this._offsetX=e,this}withDefaultOffsetY(e){return this._offsetY=e,this}withTransformOriginOn(e){return this._transformOriginSelector=e,this}_getOriginPoint(e,t){let i,r;if("center"==t.originX)i=e.left+e.width/2;else{const r=this._isRtl()?e.right:e.left,n=this._isRtl()?e.left:e.right;i="start"==t.originX?r:n}return r="center"==t.originY?e.top+e.height/2:"top"==t.originY?e.top:e.bottom,{x:i,y:r}}_getOverlayPoint(e,t,i){let r,n;return r="center"==i.overlayX?-t.width/2:"start"===i.overlayX?this._isRtl()?-t.width:0:this._isRtl()?0:-t.width,n="center"==i.overlayY?-t.height/2:"top"==i.overlayY?0:-t.height,{x:e.x+r,y:e.y+n}}_getOverlayFit(e,t,i,r){let{x:n,y:s}=e,o=this._getOffset(r,"x"),a=this._getOffset(r,"y");o&&(n+=o),a&&(s+=a);let l=0-s,c=s+t.height-i.height,h=this._subtractOverflows(t.width,0-n,n+t.width-i.width),u=this._subtractOverflows(t.height,l,c),d=h*u;return{visibleArea:d,isCompletelyWithinViewport:t.width*t.height===d,fitsInViewportVertically:u===t.height,fitsInViewportHorizontally:h==t.width}}_canFitWithFlexibleDimensions(e,t,i){if(this._hasFlexibleDimensions){const r=i.bottom-t.y,n=i.right-t.x,s=wm(this._overlayRef.getConfig().minHeight),o=wm(this._overlayRef.getConfig().minWidth),a=e.fitsInViewportHorizontally||null!=o&&o<=n;return(e.fitsInViewportVertically||null!=s&&s<=r)&&a}return!1}_pushOverlayOnScreen(e,t,i){if(this._previousPushAmount&&this._positionLocked)return{x:e.x+this._previousPushAmount.x,y:e.y+this._previousPushAmount.y};const r=this._viewportRect,n=Math.max(e.x+t.width-r.width,0),s=Math.max(e.y+t.height-r.height,0),o=Math.max(r.top-i.top-e.y,0),a=Math.max(r.left-i.left-e.x,0);let l=0,c=0;return l=t.width<=r.width?a||-n:e.xr&&!this._isInitialRender&&!this._growAfterOpen&&(s=e.y-r/2)}if("end"===t.overlayX&&!r||"start"===t.overlayX&&r)c=i.width-e.x+this._viewportMargin,a=e.x-this._viewportMargin;else if("start"===t.overlayX&&!r||"end"===t.overlayX&&r)l=e.x,a=i.right-e.x;else{const t=Math.min(i.right-e.x+i.left,e.x),r=this._lastBoundingBoxSize.width;a=2*t,l=e.x-t,a>r&&!this._isInitialRender&&!this._growAfterOpen&&(l=e.x-r/2)}return{top:s,left:l,bottom:o,right:c,width:a,height:n}}_setBoundingBoxStyles(e,t){const i=this._calculateBoundingBoxRect(e,t);this._isInitialRender||this._growAfterOpen||(i.height=Math.min(i.height,this._lastBoundingBoxSize.height),i.width=Math.min(i.width,this._lastBoundingBoxSize.width));const r={};if(this._hasExactPosition())r.top=r.left="0",r.bottom=r.right=r.maxHeight=r.maxWidth="",r.width=r.height="100%";else{const e=this._overlayRef.getConfig().maxHeight,n=this._overlayRef.getConfig().maxWidth;r.height=o_(i.height),r.top=o_(i.top),r.bottom=o_(i.bottom),r.width=o_(i.width),r.left=o_(i.left),r.right=o_(i.right),r.alignItems="center"===t.overlayX?"center":"end"===t.overlayX?"flex-end":"flex-start",r.justifyContent="center"===t.overlayY?"center":"bottom"===t.overlayY?"flex-end":"flex-start",e&&(r.maxHeight=o_(e)),n&&(r.maxWidth=o_(n))}this._lastBoundingBoxSize=i,bm(this._boundingBox.style,r)}_resetBoundingBoxStyles(){bm(this._boundingBox.style,{top:"0",left:"0",right:"0",bottom:"0",height:"",width:"",alignItems:"",justifyContent:""})}_resetOverlayElementStyles(){bm(this._pane.style,{top:"",left:"",bottom:"",right:"",position:"",transform:""})}_setOverlayElementStyles(e,t){const i={},r=this._hasExactPosition(),n=this._hasFlexibleDimensions,s=this._overlayRef.getConfig();if(r){const r=this._viewportRuler.getViewportScrollPosition();bm(i,this._getExactOverlayY(t,e,r)),bm(i,this._getExactOverlayX(t,e,r))}else i.position="static";let o="",a=this._getOffset(t,"x"),l=this._getOffset(t,"y");a&&(o+=`translateX(${a}px) `),l&&(o+=`translateY(${l}px)`),i.transform=o.trim(),s.maxHeight&&(r?i.maxHeight=o_(s.maxHeight):n&&(i.maxHeight="")),s.maxWidth&&(r?i.maxWidth=o_(s.maxWidth):n&&(i.maxWidth="")),bm(this._pane.style,i)}_getExactOverlayY(e,t,i){let r={top:"",bottom:""},n=this._getOverlayPoint(t,this._overlayRect,e);this._isPushed&&(n=this._pushOverlayOnScreen(n,this._overlayRect,i));let s=this._overlayContainer.getContainerElement().getBoundingClientRect().top;return n.y-=s,"bottom"===e.overlayY?r.bottom=this._document.documentElement.clientHeight-(n.y+this._overlayRect.height)+"px":r.top=o_(n.y),r}_getExactOverlayX(e,t,i){let r,n={left:"",right:""},s=this._getOverlayPoint(t,this._overlayRect,e);return this._isPushed&&(s=this._pushOverlayOnScreen(s,this._overlayRect,i)),r=this._isRtl()?"end"===e.overlayX?"left":"right":"end"===e.overlayX?"right":"left","right"===r?n.right=this._document.documentElement.clientWidth-(s.x+this._overlayRect.width)+"px":n.left=o_(s.x),n}_getScrollVisibility(){const e=this._getOriginRect(),t=this._pane.getBoundingClientRect(),i=this._scrollables.map(e=>e.getElementRef().nativeElement.getBoundingClientRect());return{isOriginClipped:om(e,i),isOriginOutsideView:sm(e,i),isOverlayClipped:om(t,i),isOverlayOutsideView:sm(t,i)}}_subtractOverflows(e,...t){return t.reduce((e,t)=>e-Math.max(t,0),e)}_getNarrowedViewportRect(){const e=this._document.documentElement.clientWidth,t=this._document.documentElement.clientHeight,i=this._viewportRuler.getViewportScrollPosition();return{top:i.top+this._viewportMargin,left:i.left+this._viewportMargin,right:i.left+e-this._viewportMargin,bottom:i.top+t-this._viewportMargin,width:e-2*this._viewportMargin,height:t-2*this._viewportMargin}}_isRtl(){return"rtl"===this._overlayRef.getDirection()}_hasExactPosition(){return!this._hasFlexibleDimensions||this._isPushed}_getOffset(e,t){return"x"===t?null==e.offsetX?this._offsetX:e.offsetX:null==e.offsetY?this._offsetY:e.offsetY}_validatePositions(){}_addPanelClasses(e){this._pane&&s_(e).forEach(e=>{""!==e&&-1===this._appliedPanelClasses.indexOf(e)&&(this._appliedPanelClasses.push(e),this._pane.classList.add(e))})}_clearPanelClasses(){this._pane&&(this._appliedPanelClasses.forEach(e=>{this._pane.classList.remove(e)}),this._appliedPanelClasses=[])}_getOriginRect(){const e=this._origin;if(e instanceof Pa)return e.nativeElement.getBoundingClientRect();if(e instanceof Element)return e.getBoundingClientRect();const t=e.width||0,i=e.height||0;return{top:e.y,bottom:e.y+i,left:e.x,right:e.x+t,height:i,width:t}}}function bm(e,t){for(let i in t)t.hasOwnProperty(i)&&(e[i]=t[i]);return e}function wm(e){if("number"!=typeof e&&null!=e){const[t,i]=e.split(vm);return i&&"px"!==i?null:parseFloat(t)}return e||null}class Cm{constructor(e,t,i,r,n,s,o){this._preferredPositions=[],this._positionStrategy=new ym(i,r,n,s,o).withFlexibleDimensions(!1).withPush(!1).withViewportMargin(0),this.withFallbackPosition(e,t),this.onPositionChange=this._positionStrategy.positionChanges}get positions(){return this._preferredPositions}attach(e){this._overlayRef=e,this._positionStrategy.attach(e),this._direction&&(e.setDirection(this._direction),this._direction=null)}dispose(){this._positionStrategy.dispose()}detach(){this._positionStrategy.detach()}apply(){this._positionStrategy.apply()}recalculateLastPosition(){this._positionStrategy.reapplyLastPosition()}withScrollableContainers(e){this._positionStrategy.withScrollableContainers(e)}withFallbackPosition(e,t,i,r){const n=new hm(e,t,i,r);return this._preferredPositions.push(n),this._positionStrategy.withPositions(this._preferredPositions),this}withDirection(e){return this._overlayRef?this._overlayRef.setDirection(e):this._direction=e,this}withOffsetX(e){return this._positionStrategy.withDefaultOffsetX(e),this}withOffsetY(e){return this._positionStrategy.withDefaultOffsetY(e),this}withLockedPosition(e){return this._positionStrategy.withLockedPosition(e),this}withPositions(e){return this._preferredPositions=e.slice(),this._positionStrategy.withPositions(this._preferredPositions),this}setOrigin(e){return this._positionStrategy.setOrigin(e),this}}class Sm{constructor(){this._cssPosition="static",this._topOffset="",this._bottomOffset="",this._leftOffset="",this._rightOffset="",this._alignItems="",this._justifyContent="",this._width="",this._height=""}attach(e){const t=e.getConfig();this._overlayRef=e,this._width&&!t.width&&e.updateSize({width:this._width}),this._height&&!t.height&&e.updateSize({height:this._height}),e.hostElement.classList.add("cdk-global-overlay-wrapper"),this._isDisposed=!1}top(e=""){return this._bottomOffset="",this._topOffset=e,this._alignItems="flex-start",this}left(e=""){return this._rightOffset="",this._leftOffset=e,this._justifyContent="flex-start",this}bottom(e=""){return this._topOffset="",this._bottomOffset=e,this._alignItems="flex-end",this}right(e=""){return this._leftOffset="",this._rightOffset=e,this._justifyContent="flex-end",this}width(e=""){return this._overlayRef?this._overlayRef.updateSize({width:e}):this._width=e,this}height(e=""){return this._overlayRef?this._overlayRef.updateSize({height:e}):this._height=e,this}centerHorizontally(e=""){return this.left(e),this._justifyContent="center",this}centerVertically(e=""){return this.top(e),this._alignItems="center",this}apply(){if(!this._overlayRef||!this._overlayRef.hasAttached())return;const e=this._overlayRef.overlayElement.style,t=this._overlayRef.hostElement.style,i=this._overlayRef.getConfig(),{width:r,height:n,maxWidth:s,maxHeight:o}=i,a=!("100%"!==r&&"100vw"!==r||s&&"100%"!==s&&"100vw"!==s),l=!("100%"!==n&&"100vh"!==n||o&&"100%"!==o&&"100vh"!==o);e.position=this._cssPosition,e.marginLeft=a?"0":this._leftOffset,e.marginTop=l?"0":this._topOffset,e.marginBottom=this._bottomOffset,e.marginRight=this._rightOffset,a?t.justifyContent="flex-start":"center"===this._justifyContent?t.justifyContent="center":"rtl"===this._overlayRef.getConfig().direction?"flex-start"===this._justifyContent?t.justifyContent="flex-end":"flex-end"===this._justifyContent&&(t.justifyContent="flex-start"):t.justifyContent=this._justifyContent,t.alignItems=l?"flex-start":this._alignItems}dispose(){if(this._isDisposed||!this._overlayRef)return;const e=this._overlayRef.overlayElement.style,t=this._overlayRef.hostElement,i=t.style;t.classList.remove("cdk-global-overlay-wrapper"),i.justifyContent=i.alignItems=e.marginTop=e.marginBottom=e.marginLeft=e.marginRight=e.position="",this._overlayRef=null,this._isDisposed=!0}}let km=(()=>{class e{constructor(e,t,i,r){this._viewportRuler=e,this._document=t,this._platform=i,this._overlayContainer=r}global(){return new Sm}connectedTo(e,t,i){return new Cm(t,i,e,this._viewportRuler,this._document,this._platform,this._overlayContainer)}flexibleConnectedTo(e){return new ym(e,this._viewportRuler,this._document,this._platform,this._overlayContainer)}}return e.\u0275fac=function(t){return new(t||e)(Ze(V_),Ze(Kc),Ze(T_),Ze(mm))},e.\u0275prov=ue({factory:function(){return new e(Ze(V_),Ze(Kc),Ze(T_),Ze(mm))},token:e,providedIn:"root"}),e})(),xm=0,Em=(()=>{class e{constructor(e,t,i,r,n,s,o,a,l,c,h){this.scrollStrategies=e,this._overlayContainer=t,this._componentFactoryResolver=i,this._positionBuilder=r,this._keyboardDispatcher=n,this._injector=s,this._ngZone=o,this._document=a,this._directionality=l,this._location=c,this._outsideClickDispatcher=h}create(e){const t=this._createHostElement(),i=this._createPaneElement(t),r=this._createPortalOutlet(i),n=new cm(e);return n.direction=n.direction||this._directionality.value,new gm(r,t,i,n,this._ngZone,this._keyboardDispatcher,this._document,this._location,this._outsideClickDispatcher)}position(){return this._positionBuilder}_createPaneElement(e){const t=this._document.createElement("div");return t.id="cdk-overlay-"+xm++,t.classList.add("cdk-overlay-pane"),e.appendChild(t),t}_createHostElement(){const e=this._document.createElement("div");return this._overlayContainer.getContainerElement().appendChild(e),e}_createPortalOutlet(e){return this._appRef||(this._appRef=this._injector.get(Mc)),new Y_(e,this._componentFactoryResolver,this._appRef,this._injector,this._document)}}return e.\u0275fac=function(t){return new(t||e)(Ze(lm),Ze(mm),Ze(La),Ze(km),Ze(fm),Ze(ao),Ze(mc),Ze(Kc),Ze(j_),Ze(lh),Ze(pm))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const Am=[{originX:"start",originY:"bottom",overlayX:"start",overlayY:"top"},{originX:"start",originY:"top",overlayX:"start",overlayY:"bottom"},{originX:"end",originY:"top",overlayX:"end",overlayY:"bottom"},{originX:"end",originY:"bottom",overlayX:"end",overlayY:"top"}],Om=new Be("cdk-connected-overlay-scroll-strategy");let Tm=(()=>{class e{constructor(e){this.elementRef=e}}return e.\u0275fac=function(t){return new(t||e)(Co(Pa))},e.\u0275dir=bt({type:e,selectors:[["","cdk-overlay-origin",""],["","overlay-origin",""],["","cdkOverlayOrigin",""]],exportAs:["cdkOverlayOrigin"]}),e})(),Rm=(()=>{class e{constructor(e,t,i,r,n){this._overlay=e,this._dir=n,this._hasBackdrop=!1,this._lockPosition=!1,this._growAfterOpen=!1,this._flexibleDimensions=!1,this._push=!1,this._backdropSubscription=u.EMPTY,this._attachSubscription=u.EMPTY,this._detachSubscription=u.EMPTY,this._positionSubscription=u.EMPTY,this.viewportMargin=0,this.open=!1,this.backdropClick=new El,this.positionChange=new El,this.attach=new El,this.detach=new El,this.overlayKeydown=new El,this.overlayOutsideClick=new El,this._templatePortal=new K_(t,i),this._scrollStrategyFactory=r,this.scrollStrategy=this._scrollStrategyFactory()}get offsetX(){return this._offsetX}set offsetX(e){this._offsetX=e,this._position&&this._updatePositionStrategy(this._position)}get offsetY(){return this._offsetY}set offsetY(e){this._offsetY=e,this._position&&this._updatePositionStrategy(this._position)}get hasBackdrop(){return this._hasBackdrop}set hasBackdrop(e){this._hasBackdrop=r_(e)}get lockPosition(){return this._lockPosition}set lockPosition(e){this._lockPosition=r_(e)}get flexibleDimensions(){return this._flexibleDimensions}set flexibleDimensions(e){this._flexibleDimensions=r_(e)}get growAfterOpen(){return this._growAfterOpen}set growAfterOpen(e){this._growAfterOpen=r_(e)}get push(){return this._push}set push(e){this._push=r_(e)}get overlayRef(){return this._overlayRef}get dir(){return this._dir?this._dir.value:"ltr"}ngOnDestroy(){this._attachSubscription.unsubscribe(),this._detachSubscription.unsubscribe(),this._backdropSubscription.unsubscribe(),this._positionSubscription.unsubscribe(),this._overlayRef&&this._overlayRef.dispose()}ngOnChanges(e){this._position&&(this._updatePositionStrategy(this._position),this._overlayRef.updateSize({width:this.width,minWidth:this.minWidth,height:this.height,minHeight:this.minHeight}),e.origin&&this.open&&this._position.apply()),e.open&&(this.open?this._attachOverlay():this._detachOverlay())}_createOverlay(){this.positions&&this.positions.length||(this.positions=Am);const e=this._overlayRef=this._overlay.create(this._buildConfig());this._attachSubscription=e.attachments().subscribe(()=>this.attach.emit()),this._detachSubscription=e.detachments().subscribe(()=>this.detach.emit()),e.keydownEvents().subscribe(e=>{this.overlayKeydown.next(e),27!==e.keyCode||tm(e)||(e.preventDefault(),this._detachOverlay())}),this._overlayRef.outsidePointerEvents().subscribe(e=>{this.overlayOutsideClick.next(e)})}_buildConfig(){const e=this._position=this.positionStrategy||this._createPositionStrategy(),t=new cm({direction:this._dir,positionStrategy:e,scrollStrategy:this.scrollStrategy,hasBackdrop:this.hasBackdrop});return(this.width||0===this.width)&&(t.width=this.width),(this.height||0===this.height)&&(t.height=this.height),(this.minWidth||0===this.minWidth)&&(t.minWidth=this.minWidth),(this.minHeight||0===this.minHeight)&&(t.minHeight=this.minHeight),this.backdropClass&&(t.backdropClass=this.backdropClass),this.panelClass&&(t.panelClass=this.panelClass),t}_updatePositionStrategy(e){const t=this.positions.map(e=>({originX:e.originX,originY:e.originY,overlayX:e.overlayX,overlayY:e.overlayY,offsetX:e.offsetX||this.offsetX,offsetY:e.offsetY||this.offsetY,panelClass:e.panelClass||void 0}));return e.setOrigin(this.origin.elementRef).withPositions(t).withFlexibleDimensions(this.flexibleDimensions).withPush(this.push).withGrowAfterOpen(this.growAfterOpen).withViewportMargin(this.viewportMargin).withLockedPosition(this.lockPosition).withTransformOriginOn(this.transformOriginSelector)}_createPositionStrategy(){const e=this._overlay.position().flexibleConnectedTo(this.origin.elementRef);return this._updatePositionStrategy(e),e}_attachOverlay(){this._overlayRef?this._overlayRef.getConfig().hasBackdrop=this.hasBackdrop:this._createOverlay(),this._overlayRef.hasAttached()||this._overlayRef.attach(this._templatePortal),this.hasBackdrop?this._backdropSubscription=this._overlayRef.backdropClick().subscribe(e=>{this.backdropClick.emit(e)}):this._backdropSubscription.unsubscribe(),this._positionSubscription.unsubscribe(),this.positionChange.observers.length>0&&(this._positionSubscription=this._position.positionChanges.pipe(function(e,t=!1){return i=>i.lift(new J_(e,t))}(()=>this.positionChange.observers.length>0)).subscribe(e=>{this.positionChange.emit(e),0===this.positionChange.observers.length&&this._positionSubscription.unsubscribe()}))}_detachOverlay(){this._overlayRef&&this._overlayRef.detach(),this._backdropSubscription.unsubscribe(),this._positionSubscription.unsubscribe()}}return e.\u0275fac=function(t){return new(t||e)(Co(Em),Co(il),Co(nl),Co(Om),Co(j_,8))},e.\u0275dir=bt({type:e,selectors:[["","cdk-connected-overlay",""],["","connected-overlay",""],["","cdkConnectedOverlay",""]],inputs:{viewportMargin:["cdkConnectedOverlayViewportMargin","viewportMargin"],open:["cdkConnectedOverlayOpen","open"],scrollStrategy:["cdkConnectedOverlayScrollStrategy","scrollStrategy"],offsetX:["cdkConnectedOverlayOffsetX","offsetX"],offsetY:["cdkConnectedOverlayOffsetY","offsetY"],hasBackdrop:["cdkConnectedOverlayHasBackdrop","hasBackdrop"],lockPosition:["cdkConnectedOverlayLockPosition","lockPosition"],flexibleDimensions:["cdkConnectedOverlayFlexibleDimensions","flexibleDimensions"],growAfterOpen:["cdkConnectedOverlayGrowAfterOpen","growAfterOpen"],push:["cdkConnectedOverlayPush","push"],positions:["cdkConnectedOverlayPositions","positions"],origin:["cdkConnectedOverlayOrigin","origin"],positionStrategy:["cdkConnectedOverlayPositionStrategy","positionStrategy"],width:["cdkConnectedOverlayWidth","width"],height:["cdkConnectedOverlayHeight","height"],minWidth:["cdkConnectedOverlayMinWidth","minWidth"],minHeight:["cdkConnectedOverlayMinHeight","minHeight"],backdropClass:["cdkConnectedOverlayBackdropClass","backdropClass"],panelClass:["cdkConnectedOverlayPanelClass","panelClass"],transformOriginSelector:["cdkConnectedOverlayTransformOriginOn","transformOriginSelector"]},outputs:{backdropClick:"backdropClick",positionChange:"positionChange",attach:"attach",detach:"detach",overlayKeydown:"overlayKeydown",overlayOutsideClick:"overlayOutsideClick"},exportAs:["cdkConnectedOverlay"],features:[Dt]}),e})();const Lm={provide:Om,deps:[Em],useFactory:function(e){return()=>e.scrollStrategies.reposition()}};let Pm=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[Em,Lm],imports:[[B_,Q_,z_],z_]}),e})();function Dm(e,t=lp){return i=>i.lift(new Im(e,t))}class Im{constructor(e,t){this.dueTime=e,this.scheduler=t}call(e,t){return t.subscribe(new Mm(e,this.dueTime,this.scheduler))}}class Mm extends p{constructor(e,t,i){super(e),this.dueTime=t,this.scheduler=i,this.debouncedSubscription=null,this.lastValue=null,this.hasValue=!1}_next(e){this.clearDebounce(),this.lastValue=e,this.hasValue=!0,this.add(this.debouncedSubscription=this.scheduler.schedule(Fm,this.dueTime,this))}_complete(){this.debouncedNext(),this.destination.complete()}debouncedNext(){if(this.clearDebounce(),this.hasValue){const{lastValue:e}=this;this.lastValue=null,this.hasValue=!1,this.destination.next(e)}}clearDebounce(){const e=this.debouncedSubscription;null!==e&&(this.remove(e),e.unsubscribe(),this.debouncedSubscription=null)}}function Fm(e){e.debouncedNext()}let Nm=(()=>{class e{create(e){return"undefined"==typeof MutationObserver?null:new MutationObserver(e)}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({factory:function(){return new e},token:e,providedIn:"root"}),e})(),jm=(()=>{class e{constructor(e){this._mutationObserverFactory=e,this._observedElements=new Map}ngOnDestroy(){this._observedElements.forEach((e,t)=>this._cleanupObserver(t))}observe(e){const t=a_(e);return new v(e=>{const i=this._observeElement(t).subscribe(e);return()=>{i.unsubscribe(),this._unobserveElement(t)}})}_observeElement(e){if(this._observedElements.has(e))this._observedElements.get(e).count++;else{const t=new S,i=this._mutationObserverFactory.create(e=>t.next(e));i&&i.observe(e,{characterData:!0,childList:!0,subtree:!0}),this._observedElements.set(e,{observer:i,stream:t,count:1})}return this._observedElements.get(e).stream}_unobserveElement(e){this._observedElements.has(e)&&(this._observedElements.get(e).count--,this._observedElements.get(e).count||this._cleanupObserver(e))}_cleanupObserver(e){if(this._observedElements.has(e)){const{observer:t,stream:i}=this._observedElements.get(e);t&&t.disconnect(),i.complete(),this._observedElements.delete(e)}}}return e.\u0275fac=function(t){return new(t||e)(Ze(Nm))},e.\u0275prov=ue({factory:function(){return new e(Ze(Nm))},token:e,providedIn:"root"}),e})(),Bm=(()=>{class e{constructor(e,t,i){this._contentObserver=e,this._elementRef=t,this._ngZone=i,this.event=new El,this._disabled=!1,this._currentSubscription=null}get disabled(){return this._disabled}set disabled(e){this._disabled=r_(e),this._disabled?this._unsubscribe():this._subscribe()}get debounce(){return this._debounce}set debounce(e){this._debounce=n_(e),this._subscribe()}ngAfterContentInit(){this._currentSubscription||this.disabled||this._subscribe()}ngOnDestroy(){this._unsubscribe()}_subscribe(){this._unsubscribe();const e=this._contentObserver.observe(this._elementRef);this._ngZone.runOutsideAngular(()=>{this._currentSubscription=(this.debounce?e.pipe(Dm(this.debounce)):e).subscribe(this.event)})}_unsubscribe(){this._currentSubscription&&this._currentSubscription.unsubscribe()}}return e.\u0275fac=function(t){return new(t||e)(Co(jm),Co(Pa),Co(mc))},e.\u0275dir=bt({type:e,selectors:[["","cdkObserveContent",""]],inputs:{disabled:["cdkObserveContentDisabled","disabled"],debounce:"debounce"},outputs:{event:"cdkObserveContent"},exportAs:["cdkObserveContent"]}),e})(),Hm=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[Nm]}),e})();class Um extends class{constructor(e){this._items=e,this._activeItemIndex=-1,this._activeItem=null,this._wrap=!1,this._letterKeyStream=new S,this._typeaheadSubscription=u.EMPTY,this._vertical=!0,this._allowedModifierKeys=[],this._homeAndEnd=!1,this._skipPredicateFn=e=>e.disabled,this._pressedLetters=[],this.tabOut=new S,this.change=new S,e instanceof Ol&&e.changes.subscribe(e=>{if(this._activeItem){const t=e.toArray().indexOf(this._activeItem);t>-1&&t!==this._activeItemIndex&&(this._activeItemIndex=t)}})}skipPredicate(e){return this._skipPredicateFn=e,this}withWrap(e=!0){return this._wrap=e,this}withVerticalOrientation(e=!0){return this._vertical=e,this}withHorizontalOrientation(e){return this._horizontal=e,this}withAllowedModifierKeys(e){return this._allowedModifierKeys=e,this}withTypeAhead(e=200){return this._typeaheadSubscription.unsubscribe(),this._typeaheadSubscription=this._letterKeyStream.pipe(tp(e=>this._pressedLetters.push(e)),Dm(e),Wf(()=>this._pressedLetters.length>0),M(()=>this._pressedLetters.join(""))).subscribe(e=>{const t=this._getItemsArray();for(let i=1;i!e[t]||this._allowedModifierKeys.indexOf(t)>-1);switch(t){case 9:return void this.tabOut.next();case 40:if(this._vertical&&i){this.setNextItemActive();break}return;case 38:if(this._vertical&&i){this.setPreviousItemActive();break}return;case 39:if(this._horizontal&&i){"rtl"===this._horizontal?this.setPreviousItemActive():this.setNextItemActive();break}return;case 37:if(this._horizontal&&i){"rtl"===this._horizontal?this.setNextItemActive():this.setPreviousItemActive();break}return;case 36:if(this._homeAndEnd&&i){this.setFirstItemActive();break}return;case 35:if(this._homeAndEnd&&i){this.setLastItemActive();break}return;default:return void((i||tm(e,"shiftKey"))&&(e.key&&1===e.key.length?this._letterKeyStream.next(e.key.toLocaleUpperCase()):(t>=65&&t<=90||t>=48&&t<=57)&&this._letterKeyStream.next(String.fromCharCode(t))))}this._pressedLetters=[],e.preventDefault()}get activeItemIndex(){return this._activeItemIndex}get activeItem(){return this._activeItem}isTyping(){return this._pressedLetters.length>0}setFirstItemActive(){this._setActiveItemByIndex(0,1)}setLastItemActive(){this._setActiveItemByIndex(this._items.length-1,-1)}setNextItemActive(){this._activeItemIndex<0?this.setFirstItemActive():this._setActiveItemByDelta(1)}setPreviousItemActive(){this._activeItemIndex<0&&this._wrap?this.setLastItemActive():this._setActiveItemByDelta(-1)}updateActiveItem(e){const t=this._getItemsArray(),i="number"==typeof e?e:t.indexOf(e),r=t[i];this._activeItem=null==r?null:r,this._activeItemIndex=i}_setActiveItemByDelta(e){this._wrap?this._setActiveInWrapMode(e):this._setActiveInDefaultMode(e)}_setActiveInWrapMode(e){const t=this._getItemsArray();for(let i=1;i<=t.length;i++){const r=(this._activeItemIndex+e*i+t.length)%t.length;if(!this._skipPredicateFn(t[r]))return void this.setActiveItem(r)}}_setActiveInDefaultMode(e){this._setActiveItemByIndex(this._activeItemIndex+e,e)}_setActiveItemByIndex(e,t){const i=this._getItemsArray();if(i[e]){for(;this._skipPredicateFn(i[e]);)if(!i[e+=t])return;this.setActiveItem(e)}}_getItemsArray(){return this._items instanceof Ol?this._items.toArray():this._items}}{setActiveItem(e){this.activeItem&&this.activeItem.setInactiveStyles(),super.setActiveItem(e),this.activeItem&&this.activeItem.setActiveStyles()}}"undefined"!=typeof Element&∈const Vm=new Be("liveAnnouncerElement",{providedIn:"root",factory:function(){return null}}),qm=new Be("LIVE_ANNOUNCER_DEFAULT_OPTIONS");let zm=(()=>{class e{constructor(e,t,i,r){this._ngZone=t,this._defaultOptions=r,this._document=i,this._liveElement=e||this._createLiveElement()}announce(e,...t){const i=this._defaultOptions;let r,n;return 1===t.length&&"number"==typeof t[0]?n=t[0]:[r,n]=t,this.clear(),clearTimeout(this._previousTimeout),r||(r=i&&i.politeness?i.politeness:"polite"),null==n&&i&&(n=i.duration),this._liveElement.setAttribute("aria-live",r),this._ngZone.runOutsideAngular(()=>new Promise(t=>{clearTimeout(this._previousTimeout),this._previousTimeout=setTimeout(()=>{this._liveElement.textContent=e,t(),"number"==typeof n&&(this._previousTimeout=setTimeout(()=>this.clear(),n))},100)}))}clear(){this._liveElement&&(this._liveElement.textContent="")}ngOnDestroy(){clearTimeout(this._previousTimeout),this._liveElement&&this._liveElement.parentNode&&(this._liveElement.parentNode.removeChild(this._liveElement),this._liveElement=null)}_createLiveElement(){const e=this._document.getElementsByClassName("cdk-live-announcer-element"),t=this._document.createElement("div");for(let i=0;i{class e{constructor(e,t,i,r){this._ngZone=e,this._platform=t,this._origin=null,this._windowFocused=!1,this._elementInfo=new Map,this._monitoredElementCount=0,this._rootNodeFocusListenerCount=new Map,this._documentKeydownListener=()=>{this._lastTouchTarget=null,this._setOriginForCurrentEventQueue("keyboard")},this._documentMousedownListener=e=>{if(!this._lastTouchTarget){const t=Wm(e)?"keyboard":"mouse";this._setOriginForCurrentEventQueue(t)}},this._documentTouchstartListener=e=>{null!=this._touchTimeoutId&&clearTimeout(this._touchTimeoutId),this._lastTouchTarget=Zm(e),this._touchTimeoutId=setTimeout(()=>this._lastTouchTarget=null,650)},this._windowFocusListener=()=>{this._windowFocused=!0,this._windowFocusTimeoutId=setTimeout(()=>this._windowFocused=!1)},this._rootNodeFocusAndBlurListener=e=>{const t=Zm(e),i="focus"===e.type?this._onFocus:this._onBlur;for(let r=t;r;r=r.parentElement)i.call(this,e,r)},this._document=i,this._detectionMode=(null==r?void 0:r.detectionMode)||0}monitor(e,t=!1){const i=a_(e);if(!this._platform.isBrowser||1!==i.nodeType)return Hf(null);const r=F_(i)||this._getDocument(),n=this._elementInfo.get(i);if(n)return t&&(n.checkChildren=!0),n.subject;const s={checkChildren:t,subject:new S,rootNode:r};return this._elementInfo.set(i,s),this._registerGlobalListeners(s),s.subject}stopMonitoring(e){const t=a_(e),i=this._elementInfo.get(t);i&&(i.subject.complete(),this._setClasses(t),this._elementInfo.delete(t),this._removeGlobalListeners(i))}focusVia(e,t,i){const r=a_(e);this._setOriginForCurrentEventQueue(t),"function"==typeof r.focus&&r.focus(i)}ngOnDestroy(){this._elementInfo.forEach((e,t)=>this.stopMonitoring(t))}_getDocument(){return this._document||document}_getWindow(){return this._getDocument().defaultView||window}_toggleClass(e,t,i){i?e.classList.add(t):e.classList.remove(t)}_getFocusOrigin(e){return this._origin?this._origin:this._windowFocused&&this._lastFocusOrigin?this._lastFocusOrigin:this._wasCausedByTouch(e)?"touch":"program"}_setClasses(e,t){this._toggleClass(e,"cdk-focused",!!t),this._toggleClass(e,"cdk-touch-focused","touch"===t),this._toggleClass(e,"cdk-keyboard-focused","keyboard"===t),this._toggleClass(e,"cdk-mouse-focused","mouse"===t),this._toggleClass(e,"cdk-program-focused","program"===t)}_setOriginForCurrentEventQueue(e){this._ngZone.runOutsideAngular(()=>{this._origin=e,0===this._detectionMode&&(this._originTimeoutId=setTimeout(()=>this._origin=null,1))})}_wasCausedByTouch(e){const t=Zm(e);return this._lastTouchTarget instanceof Node&&t instanceof Node&&(t===this._lastTouchTarget||t.contains(this._lastTouchTarget))}_onFocus(e,t){const i=this._elementInfo.get(t);if(!i||!i.checkChildren&&t!==Zm(e))return;const r=this._getFocusOrigin(e);this._setClasses(t,r),this._emitOrigin(i.subject,r),this._lastFocusOrigin=r}_onBlur(e,t){const i=this._elementInfo.get(t);!i||i.checkChildren&&e.relatedTarget instanceof Node&&t.contains(e.relatedTarget)||(this._setClasses(t),this._emitOrigin(i.subject,null))}_emitOrigin(e,t){this._ngZone.run(()=>e.next(t))}_registerGlobalListeners(e){if(!this._platform.isBrowser)return;const t=e.rootNode,i=this._rootNodeFocusListenerCount.get(t)||0;i||this._ngZone.runOutsideAngular(()=>{t.addEventListener("focus",this._rootNodeFocusAndBlurListener,Km),t.addEventListener("blur",this._rootNodeFocusAndBlurListener,Km)}),this._rootNodeFocusListenerCount.set(t,i+1),1==++this._monitoredElementCount&&this._ngZone.runOutsideAngular(()=>{const e=this._getDocument(),t=this._getWindow();e.addEventListener("keydown",this._documentKeydownListener,Km),e.addEventListener("mousedown",this._documentMousedownListener,Km),e.addEventListener("touchstart",this._documentTouchstartListener,Km),t.addEventListener("focus",this._windowFocusListener)})}_removeGlobalListeners(e){const t=e.rootNode;if(this._rootNodeFocusListenerCount.has(t)){const e=this._rootNodeFocusListenerCount.get(t);e>1?this._rootNodeFocusListenerCount.set(t,e-1):(t.removeEventListener("focus",this._rootNodeFocusAndBlurListener,Km),t.removeEventListener("blur",this._rootNodeFocusAndBlurListener,Km),this._rootNodeFocusListenerCount.delete(t))}if(!--this._monitoredElementCount){const e=this._getDocument(),t=this._getWindow();e.removeEventListener("keydown",this._documentKeydownListener,Km),e.removeEventListener("mousedown",this._documentMousedownListener,Km),e.removeEventListener("touchstart",this._documentTouchstartListener,Km),t.removeEventListener("focus",this._windowFocusListener),clearTimeout(this._windowFocusTimeoutId),clearTimeout(this._touchTimeoutId),clearTimeout(this._originTimeoutId)}}}return e.\u0275fac=function(t){return new(t||e)(Ze(mc),Ze(T_),Ze(Kc,8),Ze($m,8))},e.\u0275prov=ue({factory:function(){return new e(Ze(mc),Ze(T_),Ze(Kc,8),Ze($m,8))},token:e,providedIn:"root"}),e})();function Zm(e){return e.composedPath?e.composedPath()[0]:e.target}let Ym=(()=>{class e{constructor(e,t){this._platform=e,this._document=t}getHighContrastMode(){if(!this._platform.isBrowser)return 0;const e=this._document.createElement("div");e.style.backgroundColor="rgb(1,2,3)",e.style.position="absolute",this._document.body.appendChild(e);const t=this._document.defaultView||window,i=t&&t.getComputedStyle?t.getComputedStyle(e):null,r=(i&&i.backgroundColor||"").replace(/ /g,"");switch(this._document.body.removeChild(e),r){case"rgb(0,0,0)":return 2;case"rgb(255,255,255)":return 1}return 0}_applyBodyHighContrastModeCssClasses(){if(this._platform.isBrowser&&this._document.body){const e=this._document.body.classList;e.remove("cdk-high-contrast-active"),e.remove("cdk-high-contrast-black-on-white"),e.remove("cdk-high-contrast-white-on-black");const t=this.getHighContrastMode();1===t?(e.add("cdk-high-contrast-active"),e.add("cdk-high-contrast-black-on-white")):2===t&&(e.add("cdk-high-contrast-active"),e.add("cdk-high-contrast-white-on-black"))}}}return e.\u0275fac=function(t){return new(t||e)(Ze(T_),Ze(Kc))},e.\u0275prov=ue({factory:function(){return new e(Ze(T_),Ze(Kc))},token:e,providedIn:"root"}),e})();const Xm=new Ba("10.2.7");function Qm(e,t){if(1&e&&Oo(0,"mat-pseudo-checkbox",3),2&e){const e=Bo();ko("state",e.selected?"checked":"unchecked")("disabled",e.disabled)}}const Jm=["*"],eg=new Ba("10.2.7"),tg=new Be("mat-sanity-checks",{providedIn:"root",factory:function(){return!0}});let ig,rg=(()=>{class e{constructor(e,t,i){this._hasDoneGlobalChecks=!1,this._document=i,e._applyBodyHighContrastModeCssClasses(),this._sanityChecks=t,this._hasDoneGlobalChecks||(this._checkDoctypeIsDefined(),this._checkThemeIsPresent(),this._checkCdkVersionMatch(),this._hasDoneGlobalChecks=!0)}_getDocument(){const e=this._document||document;return"object"==typeof e&&e?e:null}_getWindow(){const e=this._getDocument(),t=(null==e?void 0:e.defaultView)||window;return"object"==typeof t&&t?t:null}_checksAreEnabled(){return kr()&&!this._isTestEnv()}_isTestEnv(){const e=this._getWindow();return e&&(e.__karma__||e.jasmine)}_checkDoctypeIsDefined(){const e=this._checksAreEnabled()&&(!0===this._sanityChecks||this._sanityChecks.doctype),t=this._getDocument();e&&t&&!t.doctype&&console.warn("Current document does not have a doctype. This may cause some Angular Material components not to behave as expected.")}_checkThemeIsPresent(){const e=!this._checksAreEnabled()||!1===this._sanityChecks||!this._sanityChecks.theme,t=this._getDocument();if(e||!t||!t.body||"function"!=typeof getComputedStyle)return;const i=t.createElement("div");i.classList.add("mat-theme-loaded-marker"),t.body.appendChild(i);const r=getComputedStyle(i);r&&"none"!==r.display&&console.warn("Could not find Angular Material core theme. Most Material components may not work as expected. For more info refer to the theming guide: https://material.angular.io/guide/theming"),t.body.removeChild(i)}_checkCdkVersionMatch(){this._checksAreEnabled()&&(!0===this._sanityChecks||this._sanityChecks.version)&&eg.full!==Xm.full&&console.warn("The Angular Material version ("+eg.full+") does not match the Angular CDK version ("+Xm.full+").\nPlease ensure the versions of these two packages exactly match.")}}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)(Ze(Ym),Ze(tg,8),Ze(Kc,8))},imports:[[B_],B_]}),e})();function ng(e){return class extends e{constructor(...e){super(...e),this._disabled=!1}get disabled(){return this._disabled}set disabled(e){this._disabled=r_(e)}}}function sg(e,t){return class extends e{constructor(...e){super(...e),this.defaultColor=t,this.color=t}get color(){return this._color}set color(e){const t=e||this.defaultColor;t!==this._color&&(this._color&&this._elementRef.nativeElement.classList.remove("mat-"+this._color),t&&this._elementRef.nativeElement.classList.add("mat-"+t),this._color=t)}}}function og(e){return class extends e{constructor(...e){super(...e),this._disableRipple=!1}get disableRipple(){return this._disableRipple}set disableRipple(e){this._disableRipple=r_(e)}}}function ag(e,t=0){return class extends e{constructor(...e){super(...e),this._tabIndex=t,this.defaultTabIndex=t}get tabIndex(){return this.disabled?-1:this._tabIndex}set tabIndex(e){this._tabIndex=null!=e?n_(e):this.defaultTabIndex}}}function lg(e){return class extends e{constructor(...e){super(...e),this.errorState=!1,this.stateChanges=new S}updateErrorState(){const e=this.errorState,t=(this.errorStateMatcher||this._defaultErrorStateMatcher).isErrorState(this.ngControl?this.ngControl.control:null,this._parentFormGroup||this._parentForm);t!==e&&(this.errorState=t,this.stateChanges.next())}}}try{ig="undefined"!=typeof Intl}catch(px){ig=!1}let cg=(()=>{class e{isErrorState(e,t){return!!(e&&e.invalid&&(e.touched||t&&t.submitted))}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({factory:function(){return new e},token:e,providedIn:"root"}),e})();class hg{constructor(e,t,i){this._renderer=e,this.element=t,this.config=i,this.state=3}fadeOut(){this._renderer.fadeOutRipple(this)}}const ug={enterDuration:450,exitDuration:400},dg=M_({passive:!0}),fg=["mousedown","touchstart"],pg=["mouseup","mouseleave","touchend","touchcancel"];class _g{constructor(e,t,i,r){this._target=e,this._ngZone=t,this._isPointerDown=!1,this._activeRipples=new Set,this._pointerUpEventsRegistered=!1,r.isBrowser&&(this._containerElement=a_(i))}fadeInRipple(e,t,i={}){const r=this._containerRect=this._containerRect||this._containerElement.getBoundingClientRect(),n=Object.assign(Object.assign({},ug),i.animation);i.centered&&(e=r.left+r.width/2,t=r.top+r.height/2);const s=i.radius||function(e,t,i){const r=Math.max(Math.abs(e-i.left),Math.abs(e-i.right)),n=Math.max(Math.abs(t-i.top),Math.abs(t-i.bottom));return Math.sqrt(r*r+n*n)}(e,t,r),o=e-r.left,a=t-r.top,l=n.enterDuration,c=document.createElement("div");c.classList.add("mat-ripple-element"),c.style.left=o-s+"px",c.style.top=a-s+"px",c.style.height=2*s+"px",c.style.width=2*s+"px",null!=i.color&&(c.style.backgroundColor=i.color),c.style.transitionDuration=l+"ms",this._containerElement.appendChild(c),window.getComputedStyle(c).getPropertyValue("opacity"),c.style.transform="scale(1)";const h=new hg(this,c,i);return h.state=0,this._activeRipples.add(h),i.persistent||(this._mostRecentTransientRipple=h),this._runTimeoutOutsideZone(()=>{const e=h===this._mostRecentTransientRipple;h.state=1,i.persistent||e&&this._isPointerDown||h.fadeOut()},l),h}fadeOutRipple(e){const t=this._activeRipples.delete(e);if(e===this._mostRecentTransientRipple&&(this._mostRecentTransientRipple=null),this._activeRipples.size||(this._containerRect=null),!t)return;const i=e.element,r=Object.assign(Object.assign({},ug),e.config.animation);i.style.transitionDuration=r.exitDuration+"ms",i.style.opacity="0",e.state=2,this._runTimeoutOutsideZone(()=>{e.state=3,i.parentNode.removeChild(i)},r.exitDuration)}fadeOutAll(){this._activeRipples.forEach(e=>e.fadeOut())}setupTriggerEvents(e){const t=a_(e);t&&t!==this._triggerElement&&(this._removeTriggerEvents(),this._triggerElement=t,this._registerEvents(fg))}handleEvent(e){"mousedown"===e.type?this._onMousedown(e):"touchstart"===e.type?this._onTouchStart(e):this._onPointerUp(),this._pointerUpEventsRegistered||(this._registerEvents(pg),this._pointerUpEventsRegistered=!0)}_onMousedown(e){const t=Wm(e),i=this._lastTouchStartEvent&&Date.now(){!e.config.persistent&&(1===e.state||e.config.terminateOnPointerUp&&0===e.state)&&e.fadeOut()}))}_runTimeoutOutsideZone(e,t=0){this._ngZone.runOutsideAngular(()=>setTimeout(e,t))}_registerEvents(e){this._ngZone.runOutsideAngular(()=>{e.forEach(e=>{this._triggerElement.addEventListener(e,this,dg)})})}_removeTriggerEvents(){this._triggerElement&&(fg.forEach(e=>{this._triggerElement.removeEventListener(e,this,dg)}),this._pointerUpEventsRegistered&&pg.forEach(e=>{this._triggerElement.removeEventListener(e,this,dg)}))}}const mg=new Be("mat-ripple-global-options");let gg=(()=>{class e{constructor(e,t,i,r,n){this._elementRef=e,this._animationMode=n,this.radius=0,this._disabled=!1,this._isInitialized=!1,this._globalOptions=r||{},this._rippleRenderer=new _g(this,t,e,i)}get disabled(){return this._disabled}set disabled(e){this._disabled=e,this._setupTriggerEventsIfEnabled()}get trigger(){return this._trigger||this._elementRef.nativeElement}set trigger(e){this._trigger=e,this._setupTriggerEventsIfEnabled()}ngOnInit(){this._isInitialized=!0,this._setupTriggerEventsIfEnabled()}ngOnDestroy(){this._rippleRenderer._removeTriggerEvents()}fadeOutAll(){this._rippleRenderer.fadeOutAll()}get rippleConfig(){return{centered:this.centered,radius:this.radius,color:this.color,animation:Object.assign(Object.assign(Object.assign({},this._globalOptions.animation),"NoopAnimations"===this._animationMode?{enterDuration:0,exitDuration:0}:{}),this.animation),terminateOnPointerUp:this._globalOptions.terminateOnPointerUp}}get rippleDisabled(){return this.disabled||!!this._globalOptions.disabled}_setupTriggerEventsIfEnabled(){!this.disabled&&this._isInitialized&&this._rippleRenderer.setupTriggerEvents(this.trigger)}launch(e,t=0,i){return"number"==typeof e?this._rippleRenderer.fadeInRipple(e,t,Object.assign(Object.assign({},this.rippleConfig),i)):this._rippleRenderer.fadeInRipple(0,0,Object.assign(Object.assign({},this.rippleConfig),e))}}return e.\u0275fac=function(t){return new(t||e)(Co(Pa),Co(mc),Co(T_),Co(mg,8),Co(Lf,8))},e.\u0275dir=bt({type:e,selectors:[["","mat-ripple",""],["","matRipple",""]],hostAttrs:[1,"mat-ripple"],hostVars:2,hostBindings:function(e,t){2&e&&Xo("mat-ripple-unbounded",t.unbounded)},inputs:{radius:["matRippleRadius","radius"],disabled:["matRippleDisabled","disabled"],trigger:["matRippleTrigger","trigger"],color:["matRippleColor","color"],unbounded:["matRippleUnbounded","unbounded"],centered:["matRippleCentered","centered"],animation:["matRippleAnimation","animation"]},exportAs:["matRipple"]}),e})(),vg=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[rg,R_],rg]}),e})(),yg=(()=>{class e{constructor(e){this._animationMode=e,this.state="unchecked",this.disabled=!1}}return e.\u0275fac=function(t){return new(t||e)(Co(Lf,8))},e.\u0275cmp=pt({type:e,selectors:[["mat-pseudo-checkbox"]],hostAttrs:[1,"mat-pseudo-checkbox"],hostVars:8,hostBindings:function(e,t){2&e&&Xo("mat-pseudo-checkbox-indeterminate","indeterminate"===t.state)("mat-pseudo-checkbox-checked","checked"===t.state)("mat-pseudo-checkbox-disabled",t.disabled)("_mat-animation-noopable","NoopAnimations"===t._animationMode)},inputs:{state:"state",disabled:"disabled"},decls:0,vars:0,template:function(e,t){},styles:['.mat-pseudo-checkbox{width:16px;height:16px;border:2px solid;border-radius:2px;cursor:pointer;display:inline-block;vertical-align:middle;box-sizing:border-box;position:relative;flex-shrink:0;transition:border-color 90ms cubic-bezier(0, 0, 0.2, 0.1),background-color 90ms cubic-bezier(0, 0, 0.2, 0.1)}.mat-pseudo-checkbox::after{position:absolute;opacity:0;content:"";border-bottom:2px solid currentColor;transition:opacity 90ms cubic-bezier(0, 0, 0.2, 0.1)}.mat-pseudo-checkbox.mat-pseudo-checkbox-checked,.mat-pseudo-checkbox.mat-pseudo-checkbox-indeterminate{border-color:transparent}._mat-animation-noopable.mat-pseudo-checkbox{transition:none;animation:none}._mat-animation-noopable.mat-pseudo-checkbox::after{transition:none}.mat-pseudo-checkbox-disabled{cursor:default}.mat-pseudo-checkbox-indeterminate::after{top:5px;left:1px;width:10px;opacity:1;border-radius:2px}.mat-pseudo-checkbox-checked::after{top:2.4px;left:1px;width:8px;height:3px;border-left:2px solid currentColor;transform:rotate(-45deg);opacity:1;box-sizing:content-box}\n'],encapsulation:2,changeDetection:0}),e})(),bg=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)}}),e})();class wg{}const Cg=ng(wg);let Sg=0,kg=(()=>{class e extends Cg{constructor(){super(...arguments),this._labelId="mat-optgroup-label-"+Sg++}}return e.\u0275fac=function(t){return xg(t||e)},e.\u0275dir=bt({type:e,inputs:{label:"label"},features:[ma]}),e})();const xg=pr(kg),Eg=new Be("MatOptgroup");let Ag=0;class Og{constructor(e,t=!1){this.source=e,this.isUserInput=t}}const Tg=new Be("MAT_OPTION_PARENT_COMPONENT");let Rg=(()=>{class e{constructor(e,t,i,r){this._element=e,this._changeDetectorRef=t,this._parent=i,this.group=r,this._selected=!1,this._active=!1,this._disabled=!1,this._mostRecentViewValue="",this.id="mat-option-"+Ag++,this.onSelectionChange=new El,this._stateChanges=new S}get multiple(){return this._parent&&this._parent.multiple}get selected(){return this._selected}get disabled(){return this.group&&this.group.disabled||this._disabled}set disabled(e){this._disabled=r_(e)}get disableRipple(){return this._parent&&this._parent.disableRipple}get active(){return this._active}get viewValue(){return(this._getHostElement().textContent||"").trim()}select(){this._selected||(this._selected=!0,this._changeDetectorRef.markForCheck(),this._emitSelectionChangeEvent())}deselect(){this._selected&&(this._selected=!1,this._changeDetectorRef.markForCheck(),this._emitSelectionChangeEvent())}focus(e,t){const i=this._getHostElement();"function"==typeof i.focus&&i.focus(t)}setActiveStyles(){this._active||(this._active=!0,this._changeDetectorRef.markForCheck())}setInactiveStyles(){this._active&&(this._active=!1,this._changeDetectorRef.markForCheck())}getLabel(){return this.viewValue}_handleKeydown(e){13!==e.keyCode&&32!==e.keyCode||tm(e)||(this._selectViaInteraction(),e.preventDefault())}_selectViaInteraction(){this.disabled||(this._selected=!this.multiple||!this._selected,this._changeDetectorRef.markForCheck(),this._emitSelectionChangeEvent(!0))}_getAriaSelected(){return this.selected||!this.multiple&&null}_getTabIndex(){return this.disabled?"-1":"0"}_getHostElement(){return this._element.nativeElement}ngAfterViewChecked(){if(this._selected){const e=this.viewValue;e!==this._mostRecentViewValue&&(this._mostRecentViewValue=e,this._stateChanges.next())}}ngOnDestroy(){this._stateChanges.complete()}_emitSelectionChangeEvent(e=!1){this.onSelectionChange.emit(new Og(this,e))}}return e.\u0275fac=function(t){return new(t||e)(Co(Pa),Co(zs),Co(void 0),Co(kg))},e.\u0275dir=bt({type:e,inputs:{id:"id",disabled:"disabled",value:"value"},outputs:{onSelectionChange:"onSelectionChange"}}),e})(),Lg=(()=>{class e extends Rg{constructor(e,t,i,r){super(e,t,i,r)}}return e.\u0275fac=function(t){return new(t||e)(Co(Pa),Co(zs),Co(Tg,8),Co(Eg,8))},e.\u0275cmp=pt({type:e,selectors:[["mat-option"]],hostAttrs:["role","option",1,"mat-option","mat-focus-indicator"],hostVars:12,hostBindings:function(e,t){1&e&&Io("click",(function(){return t._selectViaInteraction()}))("keydown",(function(e){return t._handleKeydown(e)})),2&e&&(fa("id",t.id),vo("tabindex",t._getTabIndex())("aria-selected",t._getAriaSelected())("aria-disabled",t.disabled.toString()),Xo("mat-selected",t.selected)("mat-option-multiple",t.multiple)("mat-active",t.active)("mat-option-disabled",t.disabled))},exportAs:["matOption"],features:[ma],ngContentSelectors:Jm,decls:4,vars:3,consts:[["class","mat-option-pseudo-checkbox",3,"state","disabled",4,"ngIf"],[1,"mat-option-text"],["mat-ripple","",1,"mat-option-ripple",3,"matRippleTrigger","matRippleDisabled"],[1,"mat-option-pseudo-checkbox",3,"state","disabled"]],template:function(e,t){1&e&&(Uo(),bo(0,Qm,1,2,"mat-pseudo-checkbox",0),Eo(1,"span",1),Vo(2),Ao(),Oo(3,"div",2)),2&e&&(ko("ngIf",t.multiple),gn(3),ko("matRippleTrigger",t._getHostElement())("matRippleDisabled",t.disabled||t.disableRipple))},directives:[yh,gg,yg],styles:[".mat-option{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;line-height:48px;height:48px;padding:0 16px;text-align:left;text-decoration:none;max-width:100%;position:relative;cursor:pointer;outline:none;display:flex;flex-direction:row;max-width:100%;box-sizing:border-box;align-items:center;-webkit-tap-highlight-color:transparent}.mat-option[disabled]{cursor:default}[dir=rtl] .mat-option{text-align:right}.mat-option .mat-icon{margin-right:16px;vertical-align:middle}.mat-option .mat-icon svg{vertical-align:top}[dir=rtl] .mat-option .mat-icon{margin-left:16px;margin-right:0}.mat-option[aria-disabled=true]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.mat-optgroup .mat-option:not(.mat-option-multiple){padding-left:32px}[dir=rtl] .mat-optgroup .mat-option:not(.mat-option-multiple){padding-left:16px;padding-right:32px}.cdk-high-contrast-active .mat-option{margin:0 1px}.cdk-high-contrast-active .mat-option.mat-active{border:solid 1px currentColor;margin:0}.mat-option-text{display:inline-block;flex-grow:1;overflow:hidden;text-overflow:ellipsis}.mat-option .mat-option-ripple{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none}.cdk-high-contrast-active .mat-option .mat-option-ripple{opacity:.5}.mat-option-pseudo-checkbox{margin-right:8px}[dir=rtl] .mat-option-pseudo-checkbox{margin-left:8px;margin-right:0}\n"],encapsulation:2,changeDetection:0}),e})();function Pg(e,t,i){if(i.length){let r=t.toArray(),n=i.toArray(),s=0;for(let t=0;t{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[vg,Lh,bg]]}),e})();const Ig=new Be("mat-label-global-options"),Mg=["mat-button",""],Fg=["*"],Ng=".mat-button .mat-button-focus-overlay,.mat-icon-button .mat-button-focus-overlay{opacity:0}.mat-button:hover:not(.mat-button-disabled) .mat-button-focus-overlay,.mat-stroked-button:hover:not(.mat-button-disabled) .mat-button-focus-overlay{opacity:.04}@media(hover: none){.mat-button:hover:not(.mat-button-disabled) .mat-button-focus-overlay,.mat-stroked-button:hover:not(.mat-button-disabled) .mat-button-focus-overlay{opacity:0}}.mat-button,.mat-icon-button,.mat-stroked-button,.mat-flat-button{box-sizing:border-box;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;outline:none;border:none;-webkit-tap-highlight-color:transparent;display:inline-block;white-space:nowrap;text-decoration:none;vertical-align:baseline;text-align:center;margin:0;min-width:64px;line-height:36px;padding:0 16px;border-radius:4px;overflow:visible}.mat-button::-moz-focus-inner,.mat-icon-button::-moz-focus-inner,.mat-stroked-button::-moz-focus-inner,.mat-flat-button::-moz-focus-inner{border:0}.mat-button.mat-button-disabled,.mat-icon-button.mat-button-disabled,.mat-stroked-button.mat-button-disabled,.mat-flat-button.mat-button-disabled{cursor:default}.mat-button.cdk-keyboard-focused .mat-button-focus-overlay,.mat-button.cdk-program-focused .mat-button-focus-overlay,.mat-icon-button.cdk-keyboard-focused .mat-button-focus-overlay,.mat-icon-button.cdk-program-focused .mat-button-focus-overlay,.mat-stroked-button.cdk-keyboard-focused .mat-button-focus-overlay,.mat-stroked-button.cdk-program-focused .mat-button-focus-overlay,.mat-flat-button.cdk-keyboard-focused .mat-button-focus-overlay,.mat-flat-button.cdk-program-focused .mat-button-focus-overlay{opacity:.12}.mat-button::-moz-focus-inner,.mat-icon-button::-moz-focus-inner,.mat-stroked-button::-moz-focus-inner,.mat-flat-button::-moz-focus-inner{border:0}.mat-raised-button{box-sizing:border-box;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;outline:none;border:none;-webkit-tap-highlight-color:transparent;display:inline-block;white-space:nowrap;text-decoration:none;vertical-align:baseline;text-align:center;margin:0;min-width:64px;line-height:36px;padding:0 16px;border-radius:4px;overflow:visible;transform:translate3d(0, 0, 0);transition:background 400ms cubic-bezier(0.25, 0.8, 0.25, 1),box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1)}.mat-raised-button::-moz-focus-inner{border:0}.mat-raised-button.mat-button-disabled{cursor:default}.mat-raised-button.cdk-keyboard-focused .mat-button-focus-overlay,.mat-raised-button.cdk-program-focused .mat-button-focus-overlay{opacity:.12}.mat-raised-button::-moz-focus-inner{border:0}._mat-animation-noopable.mat-raised-button{transition:none;animation:none}.mat-stroked-button{border:1px solid currentColor;padding:0 15px;line-height:34px}.mat-stroked-button .mat-button-ripple.mat-ripple,.mat-stroked-button .mat-button-focus-overlay{top:-1px;left:-1px;right:-1px;bottom:-1px}.mat-fab{box-sizing:border-box;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;outline:none;border:none;-webkit-tap-highlight-color:transparent;display:inline-block;white-space:nowrap;text-decoration:none;vertical-align:baseline;text-align:center;margin:0;min-width:64px;line-height:36px;padding:0 16px;border-radius:4px;overflow:visible;transform:translate3d(0, 0, 0);transition:background 400ms cubic-bezier(0.25, 0.8, 0.25, 1),box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);min-width:0;border-radius:50%;width:56px;height:56px;padding:0;flex-shrink:0}.mat-fab::-moz-focus-inner{border:0}.mat-fab.mat-button-disabled{cursor:default}.mat-fab.cdk-keyboard-focused .mat-button-focus-overlay,.mat-fab.cdk-program-focused .mat-button-focus-overlay{opacity:.12}.mat-fab::-moz-focus-inner{border:0}._mat-animation-noopable.mat-fab{transition:none;animation:none}.mat-fab .mat-button-wrapper{padding:16px 0;display:inline-block;line-height:24px}.mat-mini-fab{box-sizing:border-box;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;outline:none;border:none;-webkit-tap-highlight-color:transparent;display:inline-block;white-space:nowrap;text-decoration:none;vertical-align:baseline;text-align:center;margin:0;min-width:64px;line-height:36px;padding:0 16px;border-radius:4px;overflow:visible;transform:translate3d(0, 0, 0);transition:background 400ms cubic-bezier(0.25, 0.8, 0.25, 1),box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);min-width:0;border-radius:50%;width:40px;height:40px;padding:0;flex-shrink:0}.mat-mini-fab::-moz-focus-inner{border:0}.mat-mini-fab.mat-button-disabled{cursor:default}.mat-mini-fab.cdk-keyboard-focused .mat-button-focus-overlay,.mat-mini-fab.cdk-program-focused .mat-button-focus-overlay{opacity:.12}.mat-mini-fab::-moz-focus-inner{border:0}._mat-animation-noopable.mat-mini-fab{transition:none;animation:none}.mat-mini-fab .mat-button-wrapper{padding:8px 0;display:inline-block;line-height:24px}.mat-icon-button{padding:0;min-width:0;width:40px;height:40px;flex-shrink:0;line-height:40px;border-radius:50%}.mat-icon-button i,.mat-icon-button .mat-icon{line-height:24px}.mat-button-ripple.mat-ripple,.mat-button-focus-overlay{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;border-radius:inherit}.mat-button-ripple.mat-ripple:not(:empty){transform:translateZ(0)}.mat-button-focus-overlay{opacity:0;transition:opacity 200ms cubic-bezier(0.35, 0, 0.25, 1),background-color 200ms cubic-bezier(0.35, 0, 0.25, 1)}._mat-animation-noopable .mat-button-focus-overlay{transition:none}.mat-button-ripple-round{border-radius:50%;z-index:1}.mat-button .mat-button-wrapper>*,.mat-flat-button .mat-button-wrapper>*,.mat-stroked-button .mat-button-wrapper>*,.mat-raised-button .mat-button-wrapper>*,.mat-icon-button .mat-button-wrapper>*,.mat-fab .mat-button-wrapper>*,.mat-mini-fab .mat-button-wrapper>*{vertical-align:middle}.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-prefix .mat-icon-button,.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-suffix .mat-icon-button{display:block;font-size:inherit;width:2.5em;height:2.5em}.cdk-high-contrast-active .mat-button,.cdk-high-contrast-active .mat-flat-button,.cdk-high-contrast-active .mat-raised-button,.cdk-high-contrast-active .mat-icon-button,.cdk-high-contrast-active .mat-fab,.cdk-high-contrast-active .mat-mini-fab{outline:solid 1px}.cdk-high-contrast-active .mat-button-base.cdk-keyboard-focused,.cdk-high-contrast-active .mat-button-base.cdk-program-focused{outline:solid 3px}\n",jg=["mat-button","mat-flat-button","mat-icon-button","mat-raised-button","mat-stroked-button","mat-mini-fab","mat-fab"];class Bg{constructor(e){this._elementRef=e}}const Hg=sg(ng(og(Bg)));let Ug=(()=>{class e extends Hg{constructor(e,t,i){super(e),this._focusMonitor=t,this._animationMode=i,this.isRoundButton=this._hasHostAttributes("mat-fab","mat-mini-fab"),this.isIconButton=this._hasHostAttributes("mat-icon-button");for(const r of jg)this._hasHostAttributes(r)&&this._getHostElement().classList.add(r);e.nativeElement.classList.add("mat-button-base"),this.isRoundButton&&(this.color="accent")}ngAfterViewInit(){this._focusMonitor.monitor(this._elementRef,!0)}ngOnDestroy(){this._focusMonitor.stopMonitoring(this._elementRef)}focus(e="program",t){this._focusMonitor.focusVia(this._getHostElement(),e,t)}_getHostElement(){return this._elementRef.nativeElement}_isRippleDisabled(){return this.disableRipple||this.disabled}_hasHostAttributes(...e){return e.some(e=>this._getHostElement().hasAttribute(e))}}return e.\u0275fac=function(t){return new(t||e)(Co(Pa),Co(Gm),Co(Lf,8))},e.\u0275cmp=pt({type:e,selectors:[["button","mat-button",""],["button","mat-raised-button",""],["button","mat-icon-button",""],["button","mat-fab",""],["button","mat-mini-fab",""],["button","mat-stroked-button",""],["button","mat-flat-button",""]],viewQuery:function(e,t){var i;1&e&&Bl(gg,!0),2&e&&Nl(i=zl())&&(t.ripple=i.first)},hostAttrs:[1,"mat-focus-indicator"],hostVars:5,hostBindings:function(e,t){2&e&&(vo("disabled",t.disabled||null),Xo("_mat-animation-noopable","NoopAnimations"===t._animationMode)("mat-button-disabled",t.disabled))},inputs:{disabled:"disabled",disableRipple:"disableRipple",color:"color"},exportAs:["matButton"],features:[ma],attrs:Mg,ngContentSelectors:Fg,decls:4,vars:5,consts:[[1,"mat-button-wrapper"],["matRipple","",1,"mat-button-ripple",3,"matRippleDisabled","matRippleCentered","matRippleTrigger"],[1,"mat-button-focus-overlay"]],template:function(e,t){1&e&&(Uo(),Eo(0,"span",0),Vo(1),Ao(),Oo(2,"span",1),Oo(3,"span",2)),2&e&&(gn(2),Xo("mat-button-ripple-round",t.isRoundButton||t.isIconButton),ko("matRippleDisabled",t._isRippleDisabled())("matRippleCentered",t.isIconButton)("matRippleTrigger",t._getHostElement()))},directives:[gg],styles:[Ng],encapsulation:2,changeDetection:0}),e})(),Vg=(()=>{class e extends Ug{constructor(e,t,i){super(t,e,i)}_haltDisabledEvents(e){this.disabled&&(e.preventDefault(),e.stopImmediatePropagation())}}return e.\u0275fac=function(t){return new(t||e)(Co(Gm),Co(Pa),Co(Lf,8))},e.\u0275cmp=pt({type:e,selectors:[["a","mat-button",""],["a","mat-raised-button",""],["a","mat-icon-button",""],["a","mat-fab",""],["a","mat-mini-fab",""],["a","mat-stroked-button",""],["a","mat-flat-button",""]],hostAttrs:[1,"mat-focus-indicator"],hostVars:7,hostBindings:function(e,t){1&e&&Io("click",(function(e){return t._haltDisabledEvents(e)})),2&e&&(vo("tabindex",t.disabled?-1:t.tabIndex||0)("disabled",t.disabled||null)("aria-disabled",t.disabled.toString()),Xo("_mat-animation-noopable","NoopAnimations"===t._animationMode)("mat-button-disabled",t.disabled))},inputs:{disabled:"disabled",disableRipple:"disableRipple",color:"color",tabIndex:"tabIndex"},exportAs:["matButton","matAnchor"],features:[ma],attrs:Mg,ngContentSelectors:Fg,decls:4,vars:5,consts:[[1,"mat-button-wrapper"],["matRipple","",1,"mat-button-ripple",3,"matRippleDisabled","matRippleCentered","matRippleTrigger"],[1,"mat-button-focus-overlay"]],template:function(e,t){1&e&&(Uo(),Eo(0,"span",0),Vo(1),Ao(),Oo(2,"span",1),Oo(3,"span",2)),2&e&&(gn(2),Xo("mat-button-ripple-round",t.isRoundButton||t.isIconButton),ko("matRippleDisabled",t._isRippleDisabled())("matRippleCentered",t.isIconButton)("matRippleTrigger",t._getHostElement()))},directives:[gg],styles:[Ng],encapsulation:2,changeDetection:0}),e})(),qg=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[vg,rg],rg]}),e})();const zg={};function Wg(...e){let t=null,i=null;return x(e[e.length-1])&&(i=e.pop()),"function"==typeof e[e.length-1]&&(t=e.pop()),1===e.length&&l(e[0])&&(e=e[0]),z(e,i).lift(new $g(t))}class $g{constructor(e){this.resultSelector=e}call(e,t){return t.subscribe(new Kg(e,this.resultSelector))}}class Kg extends I{constructor(e,t){super(e),this.resultSelector=t,this.active=0,this.values=[],this.observables=[]}_next(e){this.values.push(zg),this.observables.push(e)}_complete(){const e=this.observables,t=e.length;if(0===t)this.destination.complete();else{this.active=t,this.toRespond=t;for(let i=0;ithis.total&&this.destination.next(e)}}const Yg=new Set;let Xg,Qg=(()=>{class e{constructor(e){this._platform=e,this._matchMedia=this._platform.isBrowser&&window.matchMedia?window.matchMedia.bind(window):Jg}matchMedia(e){return this._platform.WEBKIT&&function(e){if(!Yg.has(e))try{Xg||(Xg=document.createElement("style"),Xg.setAttribute("type","text/css"),document.head.appendChild(Xg)),Xg.sheet&&(Xg.sheet.insertRule(`@media ${e} {.fx-query-test{ }}`,0),Yg.add(e))}catch(t){console.error(t)}}(e),this._matchMedia(e)}}return e.\u0275fac=function(t){return new(t||e)(Ze(T_))},e.\u0275prov=ue({factory:function(){return new e(Ze(T_))},token:e,providedIn:"root"}),e})();function Jg(e){return{matches:"all"===e||""===e,media:e,addListener:()=>{},removeListener:()=>{}}}let ev=(()=>{class e{constructor(e,t){this._mediaMatcher=e,this._zone=t,this._queries=new Map,this._destroySubject=new S}ngOnDestroy(){this._destroySubject.next(),this._destroySubject.complete()}isMatched(e){return tv(s_(e)).some(e=>this._registerQuery(e).mql.matches)}observe(e){let t=Wg(tv(s_(e)).map(e=>this._registerQuery(e).observable));return t=Vf(t.pipe(Qf(1)),t.pipe(e=>e.lift(new Gg(1)),Dm(0))),t.pipe(M(e=>{const t={matches:!1,breakpoints:{}};return e.forEach(({matches:e,query:i})=>{t.matches=t.matches||e,t.breakpoints[i]=e}),t}))}_registerQuery(e){if(this._queries.has(e))return this._queries.get(e);const t=this._mediaMatcher.matchMedia(e),i={observable:new v(e=>{const i=t=>this._zone.run(()=>e.next(t));return t.addListener(i),()=>{t.removeListener(i)}}).pipe(v_(t),M(({matches:t})=>({query:e,matches:t})),__(this._destroySubject)),mql:t};return this._queries.set(e,i),i}}return e.\u0275fac=function(t){return new(t||e)(Ze(Qg),Ze(mc))},e.\u0275prov=ue({factory:function(){return new e(Ze(Qg),Ze(mc))},token:e,providedIn:"root"}),e})();function tv(e){return e.map(e=>e.split(",")).reduce((e,t)=>e.concat(t)).map(e=>e.trim())}function iv(e,t){if(1&e){const e=Lo();Eo(0,"div",1),Eo(1,"button",2),Io("click",(function(){return ni(e),Bo().action()})),ha(2),Ao(),Ao()}if(2&e){const e=Bo();gn(2),ua(e.data.action)}}function rv(e,t){}const nv=new Be("MatSnackBarData");class sv{constructor(){this.politeness="assertive",this.announcementMessage="",this.duration=0,this.data=null,this.horizontalPosition="center",this.verticalPosition="bottom"}}const ov=Math.pow(2,31)-1;class av{constructor(e,t){this._overlayRef=t,this._afterDismissed=new S,this._afterOpened=new S,this._onAction=new S,this._dismissedByAction=!1,this.containerInstance=e,this.onAction().subscribe(()=>this.dismiss()),e._onExit.subscribe(()=>this._finishDismiss())}dismiss(){this._afterDismissed.closed||this.containerInstance.exit(),clearTimeout(this._durationTimeoutId)}dismissWithAction(){this._onAction.closed||(this._dismissedByAction=!0,this._onAction.next(),this._onAction.complete())}closeWithAction(){this.dismissWithAction()}_dismissAfter(e){this._durationTimeoutId=setTimeout(()=>this.dismiss(),Math.min(e,ov))}_open(){this._afterOpened.closed||(this._afterOpened.next(),this._afterOpened.complete())}_finishDismiss(){this._overlayRef.dispose(),this._onAction.closed||this._onAction.complete(),this._afterDismissed.next({dismissedByAction:this._dismissedByAction}),this._afterDismissed.complete(),this._dismissedByAction=!1}afterDismissed(){return this._afterDismissed}afterOpened(){return this.containerInstance._onEnter}onAction(){return this._onAction}}let lv=(()=>{class e{constructor(e,t){this.snackBarRef=e,this.data=t}action(){this.snackBarRef.dismissWithAction()}get hasAction(){return!!this.data.action}}return e.\u0275fac=function(t){return new(t||e)(Co(av),Co(nv))},e.\u0275cmp=pt({type:e,selectors:[["simple-snack-bar"]],hostAttrs:[1,"mat-simple-snackbar"],decls:3,vars:2,consts:[["class","mat-simple-snackbar-action",4,"ngIf"],[1,"mat-simple-snackbar-action"],["mat-button","",3,"click"]],template:function(e,t){1&e&&(Eo(0,"span"),ha(1),Ao(),bo(2,iv,3,1,"div",0)),2&e&&(gn(1),ua(t.data.message),gn(1),ko("ngIf",t.hasAction))},directives:[yh,Ug],styles:[".mat-simple-snackbar{display:flex;justify-content:space-between;align-items:center;line-height:20px;opacity:1}.mat-simple-snackbar-action{flex-shrink:0;margin:-8px -8px -8px 8px}.mat-simple-snackbar-action button{max-height:36px;min-width:0}[dir=rtl] .mat-simple-snackbar-action{margin-left:-8px;margin-right:8px}\n"],encapsulation:2,changeDetection:0}),e})();const cv={snackBarState:uu("state",[_u("void, hidden",pu({transform:"scale(0.8)",opacity:0})),_u("visible",pu({transform:"scale(1)",opacity:1})),mu("* => visible",du("150ms cubic-bezier(0, 0, 0.2, 1)")),mu("* => void, * => hidden",du("75ms cubic-bezier(0.4, 0.0, 1, 1)",pu({opacity:0})))])};let hv=(()=>{class e extends Z_{constructor(e,t,i,r){super(),this._ngZone=e,this._elementRef=t,this._changeDetectorRef=i,this.snackBarConfig=r,this._destroyed=!1,this._onExit=new S,this._onEnter=new S,this._animationState="void",this.attachDomPortal=e=>(this._assertNotAttached(),this._applySnackBarClasses(),this._portalOutlet.attachDomPortal(e)),this._role="assertive"!==r.politeness||r.announcementMessage?"off"===r.politeness?null:"status":"alert"}attachComponentPortal(e){return this._assertNotAttached(),this._applySnackBarClasses(),this._portalOutlet.attachComponentPortal(e)}attachTemplatePortal(e){return this._assertNotAttached(),this._applySnackBarClasses(),this._portalOutlet.attachTemplatePortal(e)}onAnimationEnd(e){const{fromState:t,toState:i}=e;if(("void"===i&&"void"!==t||"hidden"===i)&&this._completeExit(),"visible"===i){const e=this._onEnter;this._ngZone.run(()=>{e.next(),e.complete()})}}enter(){this._destroyed||(this._animationState="visible",this._changeDetectorRef.detectChanges())}exit(){return this._animationState="hidden",this._elementRef.nativeElement.setAttribute("mat-exit",""),this._onExit}ngOnDestroy(){this._destroyed=!0,this._completeExit()}_completeExit(){this._ngZone.onMicrotaskEmpty.pipe(Qf(1)).subscribe(()=>{this._onExit.next(),this._onExit.complete()})}_applySnackBarClasses(){const e=this._elementRef.nativeElement,t=this.snackBarConfig.panelClass;t&&(Array.isArray(t)?t.forEach(t=>e.classList.add(t)):e.classList.add(t)),"center"===this.snackBarConfig.horizontalPosition&&e.classList.add("mat-snack-bar-center"),"top"===this.snackBarConfig.verticalPosition&&e.classList.add("mat-snack-bar-top")}_assertNotAttached(){this._portalOutlet.hasAttached()}}return e.\u0275fac=function(t){return new(t||e)(Co(mc),Co(Pa),Co(zs),Co(sv))},e.\u0275cmp=pt({type:e,selectors:[["snack-bar-container"]],viewQuery:function(e,t){var i;1&e&&jl(X_,!0),2&e&&Nl(i=zl())&&(t._portalOutlet=i.first)},hostAttrs:[1,"mat-snack-bar-container"],hostVars:2,hostBindings:function(e,t){1&e&&Mo("@state.done",(function(e){return t.onAnimationEnd(e)})),2&e&&(vo("role",t._role),pa("@state",t._animationState))},features:[ma],decls:1,vars:0,consts:[["cdkPortalOutlet",""]],template:function(e,t){1&e&&bo(0,rv,0,0,"ng-template",0)},directives:[X_],styles:[".mat-snack-bar-container{border-radius:4px;box-sizing:border-box;display:block;margin:24px;max-width:33vw;min-width:344px;padding:14px 16px;min-height:48px;transform-origin:center}.cdk-high-contrast-active .mat-snack-bar-container{border:solid 1px}.mat-snack-bar-handset{width:100%}.mat-snack-bar-handset .mat-snack-bar-container{margin:8px;max-width:100%;min-width:0;width:100%}\n"],encapsulation:2,data:{animation:[cv.snackBarState]}}),e})(),uv=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[Pm,Q_,Lh,qg,rg],rg]}),e})();const dv=new Be("mat-snack-bar-default-options",{providedIn:"root",factory:function(){return new sv}});let fv=(()=>{class e{constructor(e,t,i,r,n,s){this._overlay=e,this._live=t,this._injector=i,this._breakpointObserver=r,this._parentSnackBar=n,this._defaultConfig=s,this._snackBarRefAtThisLevel=null,this.simpleSnackBarComponent=lv,this.snackBarContainerComponent=hv,this.handsetCssClass="mat-snack-bar-handset"}get _openedSnackBarRef(){const e=this._parentSnackBar;return e?e._openedSnackBarRef:this._snackBarRefAtThisLevel}set _openedSnackBarRef(e){this._parentSnackBar?this._parentSnackBar._openedSnackBarRef=e:this._snackBarRefAtThisLevel=e}openFromComponent(e,t){return this._attach(e,t)}openFromTemplate(e,t){return this._attach(e,t)}open(e,t="",i){const r=Object.assign(Object.assign({},this._defaultConfig),i);return r.data={message:e,action:t},r.announcementMessage===e&&(r.announcementMessage=void 0),this.openFromComponent(this.simpleSnackBarComponent,r)}dismiss(){this._openedSnackBarRef&&this._openedSnackBarRef.dismiss()}ngOnDestroy(){this._snackBarRefAtThisLevel&&this._snackBarRefAtThisLevel.dismiss()}_attachSnackBarContainer(e,t){const i=ao.create({parent:t&&t.viewContainerRef&&t.viewContainerRef.injector||this._injector,providers:[{provide:sv,useValue:t}]}),r=new $_(this.snackBarContainerComponent,t.viewContainerRef,i),n=e.attach(r);return n.instance.snackBarConfig=t,n.instance}_attach(e,t){const i=Object.assign(Object.assign(Object.assign({},new sv),this._defaultConfig),t),r=this._createOverlay(i),n=this._attachSnackBarContainer(r,i),s=new av(n,r);if(e instanceof il){const t=new K_(e,null,{$implicit:i.data,snackBarRef:s});s.instance=n.attachTemplatePortal(t)}else{const t=this._createInjector(i,s),r=new $_(e,void 0,t),o=n.attachComponentPortal(r);s.instance=o.instance}return this._breakpointObserver.observe("(max-width: 599.99px) and (orientation: portrait)").pipe(__(r.detachments())).subscribe(e=>{const t=r.overlayElement.classList;e.matches?t.add(this.handsetCssClass):t.remove(this.handsetCssClass)}),this._animateSnackBar(s,i),this._openedSnackBarRef=s,this._openedSnackBarRef}_animateSnackBar(e,t){e.afterDismissed().subscribe(()=>{this._openedSnackBarRef==e&&(this._openedSnackBarRef=null),t.announcementMessage&&this._live.clear()}),this._openedSnackBarRef?(this._openedSnackBarRef.afterDismissed().subscribe(()=>{e.containerInstance.enter()}),this._openedSnackBarRef.dismiss()):e.containerInstance.enter(),t.duration&&t.duration>0&&e.afterOpened().subscribe(()=>e._dismissAfter(t.duration)),t.announcementMessage&&this._live.announce(t.announcementMessage,t.politeness)}_createOverlay(e){const t=new cm;t.direction=e.direction;let i=this._overlay.position().global();const r="rtl"===e.direction,n="left"===e.horizontalPosition||"start"===e.horizontalPosition&&!r||"end"===e.horizontalPosition&&r,s=!n&&"center"!==e.horizontalPosition;return n?i.left("0"):s?i.right("0"):i.centerHorizontally(),"top"===e.verticalPosition?i.top("0"):i.bottom("0"),t.positionStrategy=i,this._overlay.create(t)}_createInjector(e,t){return ao.create({parent:e&&e.viewContainerRef&&e.viewContainerRef.injector||this._injector,providers:[{provide:av,useValue:t},{provide:nv,useValue:e.data}]})}}return e.\u0275fac=function(t){return new(t||e)(Ze(Em),Ze(zm),Ze(ao),Ze(ev),Ze(e,12),Ze(dv))},e.\u0275prov=ue({factory:function(){return new e(Ze(Em),Ze(zm),Ze(He),Ze(ev),Ze(e,12),Ze(dv))},token:e,providedIn:uv}),e})();class pv extends S{constructor(e){super(),this._value=e}get value(){return this.getValue()}_subscribe(e){const t=super._subscribe(e);return t&&!t.closed&&e.next(this._value),t}getValue(){if(this.hasError)throw this.thrownError;if(this.closed)throw new b;return this._value}next(e){super.next(this._value=e)}}const _v=(()=>{function e(){return Error.call(this),this.message="no elements in sequence",this.name="EmptyError",this}return e.prototype=Object.create(Error.prototype),e})();function mv(e){return function(t){return 0===e?Mf():t.lift(new gv(e))}}class gv{constructor(e){if(this.total=e,this.total<0)throw new Xf}call(e,t){return t.subscribe(new vv(e,this.total))}}class vv extends p{constructor(e,t){super(e),this.total=t,this.ring=new Array,this.count=0}_next(e){const t=this.ring,i=this.total,r=this.count++;t.length0){const i=this.count>=this.total?this.total:this.count,r=this.ring;for(let n=0;nt.lift(new bv(e))}class bv{constructor(e){this.errorFactory=e}call(e,t){return t.subscribe(new wv(e,this.errorFactory))}}class wv extends p{constructor(e,t){super(e),this.errorFactory=t,this.hasValue=!1}_next(e){this.hasValue=!0,this.destination.next(e)}_complete(){if(this.hasValue)return this.destination.complete();{let t;try{t=this.errorFactory()}catch(e){t=e}this.destination.error(t)}}}function Cv(){return new _v}function Sv(e=null){return t=>t.lift(new kv(e))}class kv{constructor(e){this.defaultValue=e}call(e,t){return t.subscribe(new xv(e,this.defaultValue))}}class xv extends p{constructor(e,t){super(e),this.defaultValue=t,this.isEmpty=!0}_next(e){this.isEmpty=!1,this.destination.next(e)}_complete(){this.isEmpty&&this.destination.next(this.defaultValue),this.destination.complete()}}function Ev(e){return function(t){const i=new Av(e),r=t.lift(i);return i.caught=r}}class Av{constructor(e){this.selector=e}call(e,t){return t.subscribe(new Ov(e,this.selector,this.caught))}}class Ov extends I{constructor(e,t,i){super(e),this.selector=t,this.caught=i}error(e){if(!this.isStopped){let i;try{i=this.selector(e,this.caught)}catch(t){return void super.error(t)}this._unsubscribeAndRecycle();const r=new E(this,void 0,void 0);this.add(r);const n=D(this,i,void 0,void 0,r);n!==r&&this.add(n)}}}function Tv(e,t){const i=arguments.length>=2;return r=>r.pipe(e?Wf((t,i)=>e(t,i,r)):g,Qf(1),i?Sv(t):yv(()=>new _v))}class Rv{constructor(e,t,i){this.predicate=e,this.thisArg=t,this.source=i}call(e,t){return t.subscribe(new Lv(e,this.predicate,this.thisArg,this.source))}}class Lv extends p{constructor(e,t,i,r){super(e),this.predicate=t,this.thisArg=i,this.source=r,this.index=0,this.thisArg=i||this}notifyComplete(e){this.destination.next(e),this.destination.complete()}_next(e){let t=!1;try{t=this.predicate.call(this.thisArg,e,this.index++,this.source)}catch(i){return void this.destination.error(i)}t||this.notifyComplete(!1)}_complete(){this.notifyComplete(!0)}}class Pv{constructor(e,t,i=!1){this.accumulator=e,this.seed=t,this.hasSeed=i}call(e,t){return t.subscribe(new Dv(e,this.accumulator,this.seed,this.hasSeed))}}class Dv extends p{constructor(e,t,i,r){super(e),this.accumulator=t,this._seed=i,this.hasSeed=r,this.index=0}get seed(){return this._seed}set seed(e){this.hasSeed=!0,this._seed=e}_next(e){if(this.hasSeed)return this._tryNext(e);this.seed=e,this.destination.next(e)}_tryNext(e){const t=this.index++;let i;try{i=this.accumulator(this.seed,e,t)}catch(r){this.destination.error(r)}this.seed=i,this.destination.next(i)}}class Iv{constructor(e){this.callback=e}call(e,t){return t.subscribe(new Mv(e,this.callback))}}class Mv extends p{constructor(e,t){super(e),this.add(new u(t))}}class Fv{constructor(e,t){this.id=e,this.url=t}}class Nv extends Fv{constructor(e,t,i="imperative",r=null){super(e,t),this.navigationTrigger=i,this.restoredState=r}toString(){return`NavigationStart(id: ${this.id}, url: '${this.url}')`}}class jv extends Fv{constructor(e,t,i){super(e,t),this.urlAfterRedirects=i}toString(){return`NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`}}class Bv extends Fv{constructor(e,t,i){super(e,t),this.reason=i}toString(){return`NavigationCancel(id: ${this.id}, url: '${this.url}')`}}class Hv extends Fv{constructor(e,t,i){super(e,t),this.error=i}toString(){return`NavigationError(id: ${this.id}, url: '${this.url}', error: ${this.error})`}}class Uv extends Fv{constructor(e,t,i,r){super(e,t),this.urlAfterRedirects=i,this.state=r}toString(){return`RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}}class Vv extends Fv{constructor(e,t,i,r){super(e,t),this.urlAfterRedirects=i,this.state=r}toString(){return`GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}}class qv extends Fv{constructor(e,t,i,r,n){super(e,t),this.urlAfterRedirects=i,this.state=r,this.shouldActivate=n}toString(){return`GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`}}class zv extends Fv{constructor(e,t,i,r){super(e,t),this.urlAfterRedirects=i,this.state=r}toString(){return`ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}}class Wv extends Fv{constructor(e,t,i,r){super(e,t),this.urlAfterRedirects=i,this.state=r}toString(){return`ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}}class $v{constructor(e){this.route=e}toString(){return`RouteConfigLoadStart(path: ${this.route.path})`}}class Kv{constructor(e){this.route=e}toString(){return`RouteConfigLoadEnd(path: ${this.route.path})`}}class Gv{constructor(e){this.snapshot=e}toString(){return`ChildActivationStart(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}}class Zv{constructor(e){this.snapshot=e}toString(){return`ChildActivationEnd(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}}class Yv{constructor(e){this.snapshot=e}toString(){return`ActivationStart(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}}class Xv{constructor(e){this.snapshot=e}toString(){return`ActivationEnd(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}}class Qv{constructor(e,t,i){this.routerEvent=e,this.position=t,this.anchor=i}toString(){return`Scroll(anchor: '${this.anchor}', position: '${this.position?`${this.position[0]}, ${this.position[1]}`:null}')`}}class Jv{constructor(e){this.params=e||{}}has(e){return Object.prototype.hasOwnProperty.call(this.params,e)}get(e){if(this.has(e)){const t=this.params[e];return Array.isArray(t)?t[0]:t}return null}getAll(e){if(this.has(e)){const t=this.params[e];return Array.isArray(t)?t:[t]}return[]}get keys(){return Object.keys(this.params)}}function ey(e){return new Jv(e)}function ty(e){const t=Error("NavigationCancelingError: "+e);return t.ngNavigationCancelingError=!0,t}function iy(e,t,i){const r=i.path.split("/");if(r.length>e.length)return null;if("full"===i.pathMatch&&(t.hasChildren()||r.lengtht.indexOf(e)>-1):e===t}function sy(e){return Array.prototype.concat.apply([],e)}function oy(e){return e.length>0?e[e.length-1]:null}function ay(e,t){for(const i in e)e.hasOwnProperty(i)&&t(e[i],i)}function ly(e){return Do(e)?e:Po(e)?B(Promise.resolve(e)):Hf(e)}function cy(e,t,i){return i?function(e,t){return ry(e,t)}(e.queryParams,t.queryParams)&&function e(t,i){if(!fy(t.segments,i.segments))return!1;if(t.numberOfChildren!==i.numberOfChildren)return!1;for(const r in i.children){if(!t.children[r])return!1;if(!e(t.children[r],i.children[r]))return!1}return!0}(e.root,t.root):function(e,t){return Object.keys(t).length<=Object.keys(e).length&&Object.keys(t).every(i=>ny(e[i],t[i]))}(e.queryParams,t.queryParams)&&function e(t,i){return function t(i,r,n){if(i.segments.length>n.length)return!!fy(i.segments.slice(0,n.length),n)&&!r.hasChildren();if(i.segments.length===n.length){if(!fy(i.segments,n))return!1;for(const t in r.children){if(!i.children[t])return!1;if(!e(i.children[t],r.children[t]))return!1}return!0}{const e=n.slice(0,i.segments.length),s=n.slice(i.segments.length);return!!fy(i.segments,e)&&!!i.children.primary&&t(i.children.primary,r,s)}}(t,i,i.segments)}(e.root,t.root)}class hy{constructor(e,t,i){this.root=e,this.queryParams=t,this.fragment=i}get queryParamMap(){return this._queryParamMap||(this._queryParamMap=ey(this.queryParams)),this._queryParamMap}toString(){return gy.serialize(this)}}class uy{constructor(e,t){this.segments=e,this.children=t,this.parent=null,ay(t,(e,t)=>e.parent=this)}hasChildren(){return this.numberOfChildren>0}get numberOfChildren(){return Object.keys(this.children).length}toString(){return vy(this)}}class dy{constructor(e,t){this.path=e,this.parameters=t}get parameterMap(){return this._parameterMap||(this._parameterMap=ey(this.parameters)),this._parameterMap}toString(){return ky(this)}}function fy(e,t){return e.length===t.length&&e.every((e,i)=>e.path===t[i].path)}function py(e,t){let i=[];return ay(e.children,(e,r)=>{"primary"===r&&(i=i.concat(t(e,r)))}),ay(e.children,(e,r)=>{"primary"!==r&&(i=i.concat(t(e,r)))}),i}class _y{}class my{parse(e){const t=new Ty(e);return new hy(t.parseRootSegment(),t.parseQueryParams(),t.parseFragment())}serialize(e){return`${"/"+function e(t,i){if(!t.hasChildren())return vy(t);if(i){const i=t.children.primary?e(t.children.primary,!1):"",r=[];return ay(t.children,(t,i)=>{"primary"!==i&&r.push(`${i}:${e(t,!1)}`)}),r.length>0?`${i}(${r.join("//")})`:i}{const i=py(t,(i,r)=>"primary"===r?[e(t.children.primary,!1)]:[`${r}:${e(i,!1)}`]);return`${vy(t)}/(${i.join("//")})`}}(e.root,!0)}${function(e){const t=Object.keys(e).map(t=>{const i=e[t];return Array.isArray(i)?i.map(e=>`${by(t)}=${by(e)}`).join("&"):`${by(t)}=${by(i)}`});return t.length?"?"+t.join("&"):""}(e.queryParams)}${"string"==typeof e.fragment?"#"+encodeURI(e.fragment):""}`}}const gy=new my;function vy(e){return e.segments.map(e=>ky(e)).join("/")}function yy(e){return encodeURIComponent(e).replace(/%40/g,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",")}function by(e){return yy(e).replace(/%3B/gi,";")}function wy(e){return yy(e).replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/%26/gi,"&")}function Cy(e){return decodeURIComponent(e)}function Sy(e){return Cy(e.replace(/\+/g,"%20"))}function ky(e){return`${wy(e.path)}${t=e.parameters,Object.keys(t).map(e=>`;${wy(e)}=${wy(t[e])}`).join("")}`;var t}const xy=/^[^\/()?;=#]+/;function Ey(e){const t=e.match(xy);return t?t[0]:""}const Ay=/^[^=?&#]+/,Oy=/^[^?&#]+/;class Ty{constructor(e){this.url=e,this.remaining=e}parseRootSegment(){return this.consumeOptional("/"),""===this.remaining||this.peekStartsWith("?")||this.peekStartsWith("#")?new uy([],{}):new uy([],this.parseChildren())}parseQueryParams(){const e={};if(this.consumeOptional("?"))do{this.parseQueryParam(e)}while(this.consumeOptional("&"));return e}parseFragment(){return this.consumeOptional("#")?decodeURIComponent(this.remaining):null}parseChildren(){if(""===this.remaining)return{};this.consumeOptional("/");const e=[];for(this.peekStartsWith("(")||e.push(this.parseSegment());this.peekStartsWith("/")&&!this.peekStartsWith("//")&&!this.peekStartsWith("/(");)this.capture("/"),e.push(this.parseSegment());let t={};this.peekStartsWith("/(")&&(this.capture("/"),t=this.parseParens(!0));let i={};return this.peekStartsWith("(")&&(i=this.parseParens(!1)),(e.length>0||Object.keys(t).length>0)&&(i.primary=new uy(e,t)),i}parseSegment(){const e=Ey(this.remaining);if(""===e&&this.peekStartsWith(";"))throw new Error(`Empty path url segment cannot have parameters: '${this.remaining}'.`);return this.capture(e),new dy(Cy(e),this.parseMatrixParams())}parseMatrixParams(){const e={};for(;this.consumeOptional(";");)this.parseParam(e);return e}parseParam(e){const t=Ey(this.remaining);if(!t)return;this.capture(t);let i="";if(this.consumeOptional("=")){const e=Ey(this.remaining);e&&(i=e,this.capture(i))}e[Cy(t)]=Cy(i)}parseQueryParam(e){const t=function(e){const t=e.match(Ay);return t?t[0]:""}(this.remaining);if(!t)return;this.capture(t);let i="";if(this.consumeOptional("=")){const e=function(e){const t=e.match(Oy);return t?t[0]:""}(this.remaining);e&&(i=e,this.capture(i))}const r=Sy(t),n=Sy(i);if(e.hasOwnProperty(r)){let t=e[r];Array.isArray(t)||(t=[t],e[r]=t),t.push(n)}else e[r]=n}parseParens(e){const t={};for(this.capture("(");!this.consumeOptional(")")&&this.remaining.length>0;){const i=Ey(this.remaining),r=this.remaining[i.length];if("/"!==r&&")"!==r&&";"!==r)throw new Error(`Cannot parse url '${this.url}'`);let n=void 0;i.indexOf(":")>-1?(n=i.substr(0,i.indexOf(":")),this.capture(n),this.capture(":")):e&&(n="primary");const s=this.parseChildren();t[n]=1===Object.keys(s).length?s.primary:new uy([],s),this.consumeOptional("//")}return t}peekStartsWith(e){return this.remaining.startsWith(e)}consumeOptional(e){return!!this.peekStartsWith(e)&&(this.remaining=this.remaining.substring(e.length),!0)}capture(e){if(!this.consumeOptional(e))throw new Error(`Expected "${e}".`)}}class Ry{constructor(e){this._root=e}get root(){return this._root.value}parent(e){const t=this.pathFromRoot(e);return t.length>1?t[t.length-2]:null}children(e){const t=Ly(e,this._root);return t?t.children.map(e=>e.value):[]}firstChild(e){const t=Ly(e,this._root);return t&&t.children.length>0?t.children[0].value:null}siblings(e){const t=Py(e,this._root);return t.length<2?[]:t[t.length-2].children.map(e=>e.value).filter(t=>t!==e)}pathFromRoot(e){return Py(e,this._root).map(e=>e.value)}}function Ly(e,t){if(e===t.value)return t;for(const i of t.children){const t=Ly(e,i);if(t)return t}return null}function Py(e,t){if(e===t.value)return[t];for(const i of t.children){const r=Py(e,i);if(r.length)return r.unshift(t),r}return[]}class Dy{constructor(e,t){this.value=e,this.children=t}toString(){return`TreeNode(${this.value})`}}function Iy(e){const t={};return e&&e.children.forEach(e=>t[e.value.outlet]=e),t}class My extends Ry{constructor(e,t){super(e),this.snapshot=t,Uy(this,e)}toString(){return this.snapshot.toString()}}function Fy(e,t){const i=function(e,t){const i=new By([],{},{},"",{},"primary",t,null,e.root,-1,{});return new Hy("",new Dy(i,[]))}(e,t),r=new pv([new dy("",{})]),n=new pv({}),s=new pv({}),o=new pv({}),a=new pv(""),l=new Ny(r,n,o,a,s,"primary",t,i.root);return l.snapshot=i.root,new My(new Dy(l,[]),i)}class Ny{constructor(e,t,i,r,n,s,o,a){this.url=e,this.params=t,this.queryParams=i,this.fragment=r,this.data=n,this.outlet=s,this.component=o,this._futureSnapshot=a}get routeConfig(){return this._futureSnapshot.routeConfig}get root(){return this._routerState.root}get parent(){return this._routerState.parent(this)}get firstChild(){return this._routerState.firstChild(this)}get children(){return this._routerState.children(this)}get pathFromRoot(){return this._routerState.pathFromRoot(this)}get paramMap(){return this._paramMap||(this._paramMap=this.params.pipe(M(e=>ey(e)))),this._paramMap}get queryParamMap(){return this._queryParamMap||(this._queryParamMap=this.queryParams.pipe(M(e=>ey(e)))),this._queryParamMap}toString(){return this.snapshot?this.snapshot.toString():`Future(${this._futureSnapshot})`}}function jy(e,t="emptyOnly"){const i=e.pathFromRoot;let r=0;if("always"!==t)for(r=i.length-1;r>=1;){const e=i[r],t=i[r-1];if(e.routeConfig&&""===e.routeConfig.path)r--;else{if(t.component)break;r--}}return function(e){return e.reduce((e,t)=>({params:Object.assign(Object.assign({},e.params),t.params),data:Object.assign(Object.assign({},e.data),t.data),resolve:Object.assign(Object.assign({},e.resolve),t._resolvedData)}),{params:{},data:{},resolve:{}})}(i.slice(r))}class By{constructor(e,t,i,r,n,s,o,a,l,c,h){this.url=e,this.params=t,this.queryParams=i,this.fragment=r,this.data=n,this.outlet=s,this.component=o,this.routeConfig=a,this._urlSegment=l,this._lastPathIndex=c,this._resolve=h}get root(){return this._routerState.root}get parent(){return this._routerState.parent(this)}get firstChild(){return this._routerState.firstChild(this)}get children(){return this._routerState.children(this)}get pathFromRoot(){return this._routerState.pathFromRoot(this)}get paramMap(){return this._paramMap||(this._paramMap=ey(this.params)),this._paramMap}get queryParamMap(){return this._queryParamMap||(this._queryParamMap=ey(this.queryParams)),this._queryParamMap}toString(){return`Route(url:'${this.url.map(e=>e.toString()).join("/")}', path:'${this.routeConfig?this.routeConfig.path:""}')`}}class Hy extends Ry{constructor(e,t){super(t),this.url=e,Uy(this,t)}toString(){return Vy(this._root)}}function Uy(e,t){t.value._routerState=e,t.children.forEach(t=>Uy(e,t))}function Vy(e){const t=e.children.length>0?` { ${e.children.map(Vy).join(", ")} } `:"";return`${e.value}${t}`}function qy(e){if(e.snapshot){const t=e.snapshot,i=e._futureSnapshot;e.snapshot=i,ry(t.queryParams,i.queryParams)||e.queryParams.next(i.queryParams),t.fragment!==i.fragment&&e.fragment.next(i.fragment),ry(t.params,i.params)||e.params.next(i.params),function(e,t){if(e.length!==t.length)return!1;for(let i=0;iry(e.parameters,r[t].parameters))&&!(!e.parent!=!t.parent)&&(!e.parent||zy(e.parent,t.parent))}function Wy(e){return"object"==typeof e&&null!=e&&!e.outlets&&!e.segmentPath}function $y(e,t,i,r,n){let s={};return r&&ay(r,(e,t)=>{s[t]=Array.isArray(e)?e.map(e=>""+e):""+e}),new hy(i.root===e?t:function e(t,i,r){const n={};return ay(t.children,(t,s)=>{n[s]=t===i?r:e(t,i,r)}),new uy(t.segments,n)}(i.root,e,t),s,n)}class Ky{constructor(e,t,i){if(this.isAbsolute=e,this.numberOfDoubleDots=t,this.commands=i,e&&i.length>0&&Wy(i[0]))throw new Error("Root segment cannot have matrix parameters");const r=i.find(e=>"object"==typeof e&&null!=e&&e.outlets);if(r&&r!==oy(i))throw new Error("{outlets:{}} has to be the last command")}toRoot(){return this.isAbsolute&&1===this.commands.length&&"/"==this.commands[0]}}class Gy{constructor(e,t,i){this.segmentGroup=e,this.processChildren=t,this.index=i}}function Zy(e){return"object"==typeof e&&null!=e&&e.outlets?e.outlets.primary:""+e}function Yy(e,t,i){if(e||(e=new uy([],{})),0===e.segments.length&&e.hasChildren())return Xy(e,t,i);const r=function(e,t,i){let r=0,n=t;const s={match:!1,pathIndex:0,commandIndex:0};for(;n=i.length)return s;const t=e.segments[n],o=Zy(i[r]),a=r0&&void 0===o)break;if(o&&a&&"object"==typeof a&&void 0===a.outlets){if(!tb(o,a,t))return s;r+=2}else{if(!tb(o,{},t))return s;r++}n++}return{match:!0,pathIndex:n,commandIndex:r}}(e,t,i),n=i.slice(r.commandIndex);if(r.match&&r.pathIndex{null!==i&&(n[r]=Yy(e.children[r],t,i))}),ay(e.children,(e,t)=>{void 0===r[t]&&(n[t]=e)}),new uy(e.segments,n)}}function Qy(e,t,i){const r=e.segments.slice(0,t);let n=0;for(;n{null!==e&&(t[i]=Qy(new uy([],{}),0,e))}),t}function eb(e){const t={};return ay(e,(e,i)=>t[i]=""+e),t}function tb(e,t,i){return e==i.path&&ry(t,i.parameters)}class ib{constructor(e,t,i,r){this.routeReuseStrategy=e,this.futureState=t,this.currState=i,this.forwardEvent=r}activate(e){const t=this.futureState._root,i=this.currState?this.currState._root:null;this.deactivateChildRoutes(t,i,e),qy(this.futureState.root),this.activateChildRoutes(t,i,e)}deactivateChildRoutes(e,t,i){const r=Iy(t);e.children.forEach(e=>{const t=e.value.outlet;this.deactivateRoutes(e,r[t],i),delete r[t]}),ay(r,(e,t)=>{this.deactivateRouteAndItsChildren(e,i)})}deactivateRoutes(e,t,i){const r=e.value,n=t?t.value:null;if(r===n)if(r.component){const n=i.getContext(r.outlet);n&&this.deactivateChildRoutes(e,t,n.children)}else this.deactivateChildRoutes(e,t,i);else n&&this.deactivateRouteAndItsChildren(t,i)}deactivateRouteAndItsChildren(e,t){this.routeReuseStrategy.shouldDetach(e.value.snapshot)?this.detachAndStoreRouteSubtree(e,t):this.deactivateRouteAndOutlet(e,t)}detachAndStoreRouteSubtree(e,t){const i=t.getContext(e.value.outlet);if(i&&i.outlet){const t=i.outlet.detach(),r=i.children.onOutletDeactivated();this.routeReuseStrategy.store(e.value.snapshot,{componentRef:t,route:e,contexts:r})}}deactivateRouteAndOutlet(e,t){const i=t.getContext(e.value.outlet);if(i){const r=Iy(e),n=e.value.component?i.children:t;ay(r,(e,t)=>this.deactivateRouteAndItsChildren(e,n)),i.outlet&&(i.outlet.deactivate(),i.children.onOutletDeactivated())}}activateChildRoutes(e,t,i){const r=Iy(t);e.children.forEach(e=>{this.activateRoutes(e,r[e.value.outlet],i),this.forwardEvent(new Xv(e.value.snapshot))}),e.children.length&&this.forwardEvent(new Zv(e.value.snapshot))}activateRoutes(e,t,i){const r=e.value,n=t?t.value:null;if(qy(r),r===n)if(r.component){const n=i.getOrCreateContext(r.outlet);this.activateChildRoutes(e,t,n.children)}else this.activateChildRoutes(e,t,i);else if(r.component){const t=i.getOrCreateContext(r.outlet);if(this.routeReuseStrategy.shouldAttach(r.snapshot)){const e=this.routeReuseStrategy.retrieve(r.snapshot);this.routeReuseStrategy.store(r.snapshot,null),t.children.onOutletReAttached(e.contexts),t.attachRef=e.componentRef,t.route=e.route.value,t.outlet&&t.outlet.attach(e.componentRef,e.route.value),rb(e.route)}else{const i=function(e){for(let t=e.parent;t;t=t.parent){const e=t.routeConfig;if(e&&e._loadedConfig)return e._loadedConfig;if(e&&e.component)return null}return null}(r.snapshot),n=i?i.module.componentFactoryResolver:null;t.attachRef=null,t.route=r,t.resolver=n,t.outlet&&t.outlet.activateWith(r,n),this.activateChildRoutes(e,null,t.children)}}else this.activateChildRoutes(e,null,i)}}function rb(e){qy(e.value),e.children.forEach(rb)}class nb{constructor(e,t){this.routes=e,this.module=t}}function sb(e){return"function"==typeof e}function ob(e){return e instanceof hy}class ab{constructor(e){this.segmentGroup=e||null}}class lb{constructor(e){this.urlTree=e}}function cb(e){return new v(t=>t.error(new ab(e)))}function hb(e){return new v(t=>t.error(new lb(e)))}function ub(e){return new v(t=>t.error(new Error(`Only absolute redirects can have named outlets. redirectTo: '${e}'`)))}class db{constructor(e,t,i,r,n){this.configLoader=t,this.urlSerializer=i,this.urlTree=r,this.config=n,this.allowRedirects=!0,this.ngModule=e.get(et)}apply(){return this.expandSegmentGroup(this.ngModule,this.config,this.urlTree.root,"primary").pipe(M(e=>this.createUrlTree(e,this.urlTree.queryParams,this.urlTree.fragment))).pipe(Ev(e=>{if(e instanceof lb)return this.allowRedirects=!1,this.match(e.urlTree);if(e instanceof ab)throw this.noMatchError(e);throw e}))}match(e){return this.expandSegmentGroup(this.ngModule,this.config,e.root,"primary").pipe(M(t=>this.createUrlTree(t,e.queryParams,e.fragment))).pipe(Ev(e=>{if(e instanceof ab)throw this.noMatchError(e);throw e}))}noMatchError(e){return new Error(`Cannot match any routes. URL Segment: '${e.segmentGroup}'`)}createUrlTree(e,t,i){const r=e.segments.length>0?new uy([],{primary:e}):e;return new hy(r,t,i)}expandSegmentGroup(e,t,i,r){return 0===i.segments.length&&i.hasChildren()?this.expandChildren(e,t,i).pipe(M(e=>new uy([],e))):this.expandSegment(e,i,t,i.segments,r,!0)}expandChildren(e,t,i){return function(e,t){if(0===Object.keys(e).length)return Hf({});const i=[],r=[],n={};return ay(e,(e,s)=>{const o=t(s,e).pipe(M(e=>n[s]=e));"primary"===s?i.push(o):r.push(o)}),Hf.apply(null,i.concat(r)).pipe(Uf(),function(e,t){const i=arguments.length>=2;return r=>r.pipe(e?Wf((t,i)=>e(t,i,r)):g,mv(1),i?Sv(t):yv(()=>new _v))}(),M(()=>n))}(i.children,(i,r)=>this.expandSegmentGroup(e,t,r,i))}expandSegment(e,t,i,r,n,s){return Hf(...i).pipe(M(o=>this.expandSegmentAgainstRoute(e,t,i,o,r,n,s).pipe(Ev(e=>{if(e instanceof ab)return Hf(null);throw e}))),Uf(),Tv(e=>!!e),Ev((e,i)=>{if(e instanceof _v||"EmptyError"===e.name){if(this.noLeftoversInUrl(t,r,n))return Hf(new uy([],{}));throw new ab(t)}throw e}))}noLeftoversInUrl(e,t,i){return 0===t.length&&!e.children[i]}expandSegmentAgainstRoute(e,t,i,r,n,s,o){return mb(r)!==s?cb(t):void 0===r.redirectTo?this.matchSegmentAgainstRoute(e,t,r,n):o&&this.allowRedirects?this.expandSegmentAgainstRouteUsingRedirect(e,t,i,r,n,s):cb(t)}expandSegmentAgainstRouteUsingRedirect(e,t,i,r,n,s){return"**"===r.path?this.expandWildCardWithParamsAgainstRouteUsingRedirect(e,i,r,s):this.expandRegularSegmentAgainstRouteUsingRedirect(e,t,i,r,n,s)}expandWildCardWithParamsAgainstRouteUsingRedirect(e,t,i,r){const n=this.applyRedirectCommands([],i.redirectTo,{});return i.redirectTo.startsWith("/")?hb(n):this.lineralizeSegments(i,n).pipe(H(i=>{const n=new uy(i,{});return this.expandSegment(e,n,t,i,r,!1)}))}expandRegularSegmentAgainstRouteUsingRedirect(e,t,i,r,n,s){const{matched:o,consumedSegments:a,lastChild:l,positionalParamSegments:c}=fb(t,r,n);if(!o)return cb(t);const h=this.applyRedirectCommands(a,r.redirectTo,c);return r.redirectTo.startsWith("/")?hb(h):this.lineralizeSegments(r,h).pipe(H(r=>this.expandSegment(e,t,i,r.concat(n.slice(l)),s,!1)))}matchSegmentAgainstRoute(e,t,i,r){if("**"===i.path)return i.loadChildren?this.configLoader.load(e.injector,i).pipe(M(e=>(i._loadedConfig=e,new uy(r,{})))):Hf(new uy(r,{}));const{matched:n,consumedSegments:s,lastChild:o}=fb(t,i,r);if(!n)return cb(t);const a=r.slice(o);return this.getChildConfig(e,i,r).pipe(H(e=>{const i=e.module,r=e.routes,{segmentGroup:n,slicedSegments:o}=function(e,t,i,r){return i.length>0&&function(e,t,i){return i.some(i=>_b(e,t,i)&&"primary"!==mb(i))}(e,i,r)?{segmentGroup:pb(new uy(t,function(e,t){const i={};i.primary=t;for(const r of e)""===r.path&&"primary"!==mb(r)&&(i[mb(r)]=new uy([],{}));return i}(r,new uy(i,e.children)))),slicedSegments:[]}:0===i.length&&function(e,t,i){return i.some(i=>_b(e,t,i))}(e,i,r)?{segmentGroup:pb(new uy(e.segments,function(e,t,i,r){const n={};for(const s of i)_b(e,t,s)&&!r[mb(s)]&&(n[mb(s)]=new uy([],{}));return Object.assign(Object.assign({},r),n)}(e,i,r,e.children))),slicedSegments:i}:{segmentGroup:e,slicedSegments:i}}(t,s,a,r);return 0===o.length&&n.hasChildren()?this.expandChildren(i,r,n).pipe(M(e=>new uy(s,e))):0===r.length&&0===o.length?Hf(new uy(s,{})):this.expandSegment(i,n,r,o,"primary",!0).pipe(M(e=>new uy(s.concat(e.segments),e.children)))}))}getChildConfig(e,t,i){return t.children?Hf(new nb(t.children,e)):t.loadChildren?void 0!==t._loadedConfig?Hf(t._loadedConfig):this.runCanLoadGuards(e.injector,t,i).pipe(H(i=>i?this.configLoader.load(e.injector,t).pipe(M(e=>(t._loadedConfig=e,e))):function(e){return new v(t=>t.error(ty(`Cannot load children because the guard of the route "path: '${e.path}'" returned false`)))}(t))):Hf(new nb([],e))}runCanLoadGuards(e,t,i){const r=t.canLoad;return r&&0!==r.length?B(r).pipe(M(r=>{const n=e.get(r);let s;if(function(e){return e&&sb(e.canLoad)}(n))s=n.canLoad(t,i);else{if(!sb(n))throw new Error("Invalid CanLoad guard");s=n(t,i)}return ly(s)})).pipe(Uf(),tp(e=>{if(!ob(e))return;const t=ty(`Redirecting to "${this.urlSerializer.serialize(e)}"`);throw t.url=e,t}),(n=e=>!0===e,e=>e.lift(new Rv(n,void 0,e)))):Hf(!0);var n}lineralizeSegments(e,t){let i=[],r=t.root;for(;;){if(i=i.concat(r.segments),0===r.numberOfChildren)return Hf(i);if(r.numberOfChildren>1||!r.children.primary)return ub(e.redirectTo);r=r.children.primary}}applyRedirectCommands(e,t,i){return this.applyRedirectCreatreUrlTree(t,this.urlSerializer.parse(t),e,i)}applyRedirectCreatreUrlTree(e,t,i,r){const n=this.createSegmentGroup(e,t.root,i,r);return new hy(n,this.createQueryParams(t.queryParams,this.urlTree.queryParams),t.fragment)}createQueryParams(e,t){const i={};return ay(e,(e,r)=>{if("string"==typeof e&&e.startsWith(":")){const n=e.substring(1);i[r]=t[n]}else i[r]=e}),i}createSegmentGroup(e,t,i,r){const n=this.createSegments(e,t.segments,i,r);let s={};return ay(t.children,(t,n)=>{s[n]=this.createSegmentGroup(e,t,i,r)}),new uy(n,s)}createSegments(e,t,i,r){return t.map(t=>t.path.startsWith(":")?this.findPosParam(e,t,r):this.findOrReturn(t,i))}findPosParam(e,t,i){const r=i[t.path.substring(1)];if(!r)throw new Error(`Cannot redirect to '${e}'. Cannot find '${t.path}'.`);return r}findOrReturn(e,t){let i=0;for(const r of t){if(r.path===e.path)return t.splice(i),r;i++}return e}}function fb(e,t,i){if(""===t.path)return"full"===t.pathMatch&&(e.hasChildren()||i.length>0)?{matched:!1,consumedSegments:[],lastChild:0,positionalParamSegments:{}}:{matched:!0,consumedSegments:[],lastChild:0,positionalParamSegments:{}};const r=(t.matcher||iy)(i,e,t);return r?{matched:!0,consumedSegments:r.consumed,lastChild:r.consumed.length,positionalParamSegments:r.posParams}:{matched:!1,consumedSegments:[],lastChild:0,positionalParamSegments:{}}}function pb(e){if(1===e.numberOfChildren&&e.children.primary){const t=e.children.primary;return new uy(e.segments.concat(t.segments),t.children)}return e}function _b(e,t,i){return(!(e.hasChildren()||t.length>0)||"full"!==i.pathMatch)&&""===i.path&&void 0!==i.redirectTo}function mb(e){return e.outlet||"primary"}class gb{constructor(e){this.path=e,this.route=this.path[this.path.length-1]}}class vb{constructor(e,t){this.component=e,this.route=t}}function yb(e,t,i){const r=e._root;return function e(t,i,r,n,s={canDeactivateChecks:[],canActivateChecks:[]}){const o=Iy(i);return t.children.forEach(t=>{!function(t,i,r,n,s={canDeactivateChecks:[],canActivateChecks:[]}){const o=t.value,a=i?i.value:null,l=r?r.getContext(t.value.outlet):null;if(a&&o.routeConfig===a.routeConfig){const c=function(e,t,i){if("function"==typeof i)return i(e,t);switch(i){case"pathParamsChange":return!fy(e.url,t.url);case"pathParamsOrQueryParamsChange":return!fy(e.url,t.url)||!ry(e.queryParams,t.queryParams);case"always":return!0;case"paramsOrQueryParamsChange":return!zy(e,t)||!ry(e.queryParams,t.queryParams);case"paramsChange":default:return!zy(e,t)}}(a,o,o.routeConfig.runGuardsAndResolvers);c?s.canActivateChecks.push(new gb(n)):(o.data=a.data,o._resolvedData=a._resolvedData),e(t,i,o.component?l?l.children:null:r,n,s),c&&s.canDeactivateChecks.push(new vb(l&&l.outlet&&l.outlet.component||null,a))}else a&&wb(i,l,s),s.canActivateChecks.push(new gb(n)),e(t,null,o.component?l?l.children:null:r,n,s)}(t,o[t.value.outlet],r,n.concat([t.value]),s),delete o[t.value.outlet]}),ay(o,(e,t)=>wb(e,r.getContext(t),s)),s}(r,t?t._root:null,i,[r.value])}function bb(e,t,i){const r=function(e){if(!e)return null;for(let t=e.parent;t;t=t.parent){const e=t.routeConfig;if(e&&e._loadedConfig)return e._loadedConfig}return null}(t);return(r?r.module.injector:i).get(e)}function wb(e,t,i){const r=Iy(e),n=e.value;ay(r,(e,r)=>{wb(e,n.component?t?t.children.getContext(r):null:t,i)}),i.canDeactivateChecks.push(new vb(n.component&&t&&t.outlet&&t.outlet.isActivated?t.outlet.component:null,n))}const Cb=Symbol("INITIAL_VALUE");function Sb(){return Gf(e=>Wg(...e.map(e=>e.pipe(Qf(1),v_(Cb)))).pipe(function(e,t){let i=!1;return arguments.length>=2&&(i=!0),function(r){return r.lift(new Pv(e,t,i))}}((e,t)=>{let i=!1;return t.reduce((e,r,n)=>{if(e!==Cb)return e;if(r===Cb&&(i=!0),!i){if(!1===r)return r;if(n===t.length-1||ob(r))return r}return e},e)},Cb),Wf(e=>e!==Cb),M(e=>ob(e)?e:!0===e),Qf(1)))}function kb(e,t){return null!==e&&t&&t(new Yv(e)),Hf(!0)}function xb(e,t){return null!==e&&t&&t(new Gv(e)),Hf(!0)}function Eb(e,t,i){const r=t.routeConfig?t.routeConfig.canActivate:null;return r&&0!==r.length?Hf(r.map(r=>Ff(()=>{const n=bb(r,t,i);let s;if(function(e){return e&&sb(e.canActivate)}(n))s=ly(n.canActivate(t,e));else{if(!sb(n))throw new Error("Invalid CanActivate guard");s=ly(n(t,e))}return s.pipe(Tv())}))).pipe(Sb()):Hf(!0)}function Ab(e,t,i){const r=t[t.length-1],n=t.slice(0,t.length-1).reverse().map(e=>function(e){const t=e.routeConfig?e.routeConfig.canActivateChild:null;return t&&0!==t.length?{node:e,guards:t}:null}(e)).filter(e=>null!==e).map(t=>Ff(()=>Hf(t.guards.map(n=>{const s=bb(n,t.node,i);let o;if(function(e){return e&&sb(e.canActivateChild)}(s))o=ly(s.canActivateChild(r,e));else{if(!sb(s))throw new Error("Invalid CanActivateChild guard");o=ly(s(r,e))}return o.pipe(Tv())})).pipe(Sb())));return Hf(n).pipe(Sb())}class Ob{}class Tb{constructor(e,t,i,r,n,s){this.rootComponentType=e,this.config=t,this.urlTree=i,this.url=r,this.paramsInheritanceStrategy=n,this.relativeLinkResolution=s}recognize(){try{const e=Pb(this.urlTree.root,[],[],this.config,this.relativeLinkResolution).segmentGroup,t=this.processSegmentGroup(this.config,e,"primary"),i=new By([],Object.freeze({}),Object.freeze(Object.assign({},this.urlTree.queryParams)),this.urlTree.fragment,{},"primary",this.rootComponentType,null,this.urlTree.root,-1,{}),r=new Dy(i,t),n=new Hy(this.url,r);return this.inheritParamsAndData(n._root),Hf(n)}catch(e){return new v(t=>t.error(e))}}inheritParamsAndData(e){const t=e.value,i=jy(t,this.paramsInheritanceStrategy);t.params=Object.freeze(i.params),t.data=Object.freeze(i.data),e.children.forEach(e=>this.inheritParamsAndData(e))}processSegmentGroup(e,t,i){return 0===t.segments.length&&t.hasChildren()?this.processChildren(e,t):this.processSegment(e,t,t.segments,i)}processChildren(e,t){const i=py(t,(t,i)=>this.processSegmentGroup(e,t,i));return function(e){const t={};e.forEach(e=>{const i=t[e.value.outlet];if(i){const t=i.url.map(e=>e.toString()).join("/"),r=e.value.url.map(e=>e.toString()).join("/");throw new Error(`Two segments cannot have the same outlet name: '${t}' and '${r}'.`)}t[e.value.outlet]=e.value})}(i),i.sort((e,t)=>"primary"===e.value.outlet?-1:"primary"===t.value.outlet?1:e.value.outlet.localeCompare(t.value.outlet)),i}processSegment(e,t,i,r){for(const s of e)try{return this.processSegmentAgainstRoute(s,t,i,r)}catch(n){if(!(n instanceof Ob))throw n}if(this.noLeftoversInUrl(t,i,r))return[];throw new Ob}noLeftoversInUrl(e,t,i){return 0===t.length&&!e.children[i]}processSegmentAgainstRoute(e,t,i,r){if(e.redirectTo)throw new Ob;if((e.outlet||"primary")!==r)throw new Ob;let n,s=[],o=[];if("**"===e.path){const s=i.length>0?oy(i).parameters:{};n=new By(i,s,Object.freeze(Object.assign({},this.urlTree.queryParams)),this.urlTree.fragment,Mb(e),r,e.component,e,Rb(t),Lb(t)+i.length,Fb(e))}else{const a=function(e,t,i){if(""===t.path){if("full"===t.pathMatch&&(e.hasChildren()||i.length>0))throw new Ob;return{consumedSegments:[],lastChild:0,parameters:{}}}const r=(t.matcher||iy)(i,e,t);if(!r)throw new Ob;const n={};ay(r.posParams,(e,t)=>{n[t]=e.path});const s=r.consumed.length>0?Object.assign(Object.assign({},n),r.consumed[r.consumed.length-1].parameters):n;return{consumedSegments:r.consumed,lastChild:r.consumed.length,parameters:s}}(t,e,i);s=a.consumedSegments,o=i.slice(a.lastChild),n=new By(s,a.parameters,Object.freeze(Object.assign({},this.urlTree.queryParams)),this.urlTree.fragment,Mb(e),r,e.component,e,Rb(t),Lb(t)+s.length,Fb(e))}const a=function(e){return e.children?e.children:e.loadChildren?e._loadedConfig.routes:[]}(e),{segmentGroup:l,slicedSegments:c}=Pb(t,s,o,a,this.relativeLinkResolution);if(0===c.length&&l.hasChildren()){const e=this.processChildren(a,l);return[new Dy(n,e)]}if(0===a.length&&0===c.length)return[new Dy(n,[])];const h=this.processSegment(a,l,c,"primary");return[new Dy(n,h)]}}function Rb(e){let t=e;for(;t._sourceSegment;)t=t._sourceSegment;return t}function Lb(e){let t=e,i=t._segmentIndexShift?t._segmentIndexShift:0;for(;t._sourceSegment;)t=t._sourceSegment,i+=t._segmentIndexShift?t._segmentIndexShift:0;return i-1}function Pb(e,t,i,r,n){if(i.length>0&&function(e,t,i){return i.some(i=>Db(e,t,i)&&"primary"!==Ib(i))}(e,i,r)){const n=new uy(t,function(e,t,i,r){const n={};n.primary=r,r._sourceSegment=e,r._segmentIndexShift=t.length;for(const s of i)if(""===s.path&&"primary"!==Ib(s)){const i=new uy([],{});i._sourceSegment=e,i._segmentIndexShift=t.length,n[Ib(s)]=i}return n}(e,t,r,new uy(i,e.children)));return n._sourceSegment=e,n._segmentIndexShift=t.length,{segmentGroup:n,slicedSegments:[]}}if(0===i.length&&function(e,t,i){return i.some(i=>Db(e,t,i))}(e,i,r)){const s=new uy(e.segments,function(e,t,i,r,n,s){const o={};for(const a of r)if(Db(e,i,a)&&!n[Ib(a)]){const i=new uy([],{});i._sourceSegment=e,i._segmentIndexShift="legacy"===s?e.segments.length:t.length,o[Ib(a)]=i}return Object.assign(Object.assign({},n),o)}(e,t,i,r,e.children,n));return s._sourceSegment=e,s._segmentIndexShift=t.length,{segmentGroup:s,slicedSegments:i}}const s=new uy(e.segments,e.children);return s._sourceSegment=e,s._segmentIndexShift=t.length,{segmentGroup:s,slicedSegments:i}}function Db(e,t,i){return(!(e.hasChildren()||t.length>0)||"full"!==i.pathMatch)&&""===i.path&&void 0===i.redirectTo}function Ib(e){return e.outlet||"primary"}function Mb(e){return e.data||{}}function Fb(e){return e.resolve||{}}function Nb(e){return function(t){return t.pipe(Gf(t=>{const i=e(t);return i?B(i).pipe(M(()=>t)):B([t])}))}}class jb{shouldDetach(e){return!1}store(e,t){}shouldAttach(e){return!1}retrieve(e){return null}shouldReuseRoute(e,t){return e.routeConfig===t.routeConfig}}let Bb=(()=>{class e{}return e.\u0275fac=function(t){return new(t||e)},e.\u0275cmp=pt({type:e,selectors:[["ng-component"]],decls:1,vars:0,template:function(e,t){1&e&&Oo(0,"router-outlet")},directives:function(){return[rw]},encapsulation:2}),e})();function Hb(e,t=""){for(let i=0;i{this.onLoadEndListener&&this.onLoadEndListener(t);const r=i.create(e);return new nb(sy(r.injector.get(zb)).map(qb),r)}))}loadModuleFactory(e){return"string"==typeof e?B(this.loader.load(e)):ly(e()).pipe(H(e=>e instanceof tt?Hf(e):B(this.compiler.compileModuleAsync(e))))}}class $b{constructor(){this.outlet=null,this.route=null,this.resolver=null,this.children=new Kb,this.attachRef=null}}class Kb{constructor(){this.contexts=new Map}onChildOutletCreated(e,t){const i=this.getOrCreateContext(e);i.outlet=t,this.contexts.set(e,i)}onChildOutletDestroyed(e){const t=this.getContext(e);t&&(t.outlet=null)}onOutletDeactivated(){const e=this.contexts;return this.contexts=new Map,e}onOutletReAttached(e){this.contexts=e}getOrCreateContext(e){let t=this.getContext(e);return t||(t=new $b,this.contexts.set(e,t)),t}getContext(e){return this.contexts.get(e)||null}}class Gb{shouldProcessUrl(e){return!0}extract(e){return e}merge(e,t){return e}}function Zb(e){throw e}function Yb(e,t,i){return t.parse("/")}function Xb(e,t){return Hf(null)}let Qb=(()=>{class e{constructor(e,t,i,r,n,s,o,a){this.rootComponentType=e,this.urlSerializer=t,this.rootContexts=i,this.location=r,this.config=a,this.lastSuccessfulNavigation=null,this.currentNavigation=null,this.navigationId=0,this.isNgZoneEnabled=!1,this.events=new S,this.errorHandler=Zb,this.malformedUriErrorHandler=Yb,this.navigated=!1,this.lastSuccessfulId=-1,this.hooks={beforePreactivation:Xb,afterPreactivation:Xb},this.urlHandlingStrategy=new Gb,this.routeReuseStrategy=new jb,this.onSameUrlNavigation="ignore",this.paramsInheritanceStrategy="emptyOnly",this.urlUpdateStrategy="deferred",this.relativeLinkResolution="legacy",this.ngModule=n.get(et),this.console=n.get(rc);const l=n.get(mc);this.isNgZoneEnabled=l instanceof mc,this.resetConfig(a),this.currentUrlTree=new hy(new uy([],{}),{},null),this.rawUrlTree=this.currentUrlTree,this.browserUrlTree=this.currentUrlTree,this.configLoader=new Wb(s,o,e=>this.triggerEvent(new $v(e)),e=>this.triggerEvent(new Kv(e))),this.routerState=Fy(this.currentUrlTree,this.rootComponentType),this.transitions=new pv({id:0,currentUrlTree:this.currentUrlTree,currentRawUrl:this.currentUrlTree,extractedUrl:this.urlHandlingStrategy.extract(this.currentUrlTree),urlAfterRedirects:this.urlHandlingStrategy.extract(this.currentUrlTree),rawUrl:this.currentUrlTree,extras:{},resolve:null,reject:null,promise:Promise.resolve(!0),source:"imperative",restoredState:null,currentSnapshot:this.routerState.snapshot,targetSnapshot:null,currentRouterState:this.routerState,targetRouterState:null,guards:{canActivateChecks:[],canDeactivateChecks:[]},guardsResult:null}),this.navigations=this.setupNavigations(this.transitions),this.processNavigations()}setupNavigations(e){const t=this.events;return e.pipe(Wf(e=>0!==e.id),M(e=>Object.assign(Object.assign({},e),{extractedUrl:this.urlHandlingStrategy.extract(e.rawUrl)})),Gf(e=>{let i=!1,r=!1;return Hf(e).pipe(tp(e=>{this.currentNavigation={id:e.id,initialUrl:e.currentRawUrl,extractedUrl:e.extractedUrl,trigger:e.source,extras:e.extras,previousNavigation:this.lastSuccessfulNavigation?Object.assign(Object.assign({},this.lastSuccessfulNavigation),{previousNavigation:null}):null}}),Gf(e=>{const i=!this.navigated||e.extractedUrl.toString()!==this.browserUrlTree.toString();if(("reload"===this.onSameUrlNavigation||i)&&this.urlHandlingStrategy.shouldProcessUrl(e.rawUrl))return Hf(e).pipe(Gf(e=>{const i=this.transitions.getValue();return t.next(new Nv(e.id,this.serializeUrl(e.extractedUrl),e.source,e.restoredState)),i!==this.transitions.getValue()?If:[e]}),Gf(e=>Promise.resolve(e)),(r=this.ngModule.injector,n=this.configLoader,s=this.urlSerializer,o=this.config,function(e){return e.pipe(Gf(e=>function(e,t,i,r,n){return new db(e,t,i,r,n).apply()}(r,n,s,e.extractedUrl,o).pipe(M(t=>Object.assign(Object.assign({},e),{urlAfterRedirects:t})))))}),tp(e=>{this.currentNavigation=Object.assign(Object.assign({},this.currentNavigation),{finalUrl:e.urlAfterRedirects})}),function(e,t,i,r,n){return function(s){return s.pipe(H(s=>function(e,t,i,r,n="emptyOnly",s="legacy"){return new Tb(e,t,i,r,n,s).recognize()}(e,t,s.urlAfterRedirects,i(s.urlAfterRedirects),r,n).pipe(M(e=>Object.assign(Object.assign({},s),{targetSnapshot:e})))))}}(this.rootComponentType,this.config,e=>this.serializeUrl(e),this.paramsInheritanceStrategy,this.relativeLinkResolution),tp(e=>{"eager"===this.urlUpdateStrategy&&(e.extras.skipLocationChange||this.setBrowserUrl(e.urlAfterRedirects,!!e.extras.replaceUrl,e.id,e.extras.state),this.browserUrlTree=e.urlAfterRedirects)}),tp(e=>{const i=new Uv(e.id,this.serializeUrl(e.extractedUrl),this.serializeUrl(e.urlAfterRedirects),e.targetSnapshot);t.next(i)}));var r,n,s,o;if(i&&this.rawUrlTree&&this.urlHandlingStrategy.shouldProcessUrl(this.rawUrlTree)){const{id:i,extractedUrl:r,source:n,restoredState:s,extras:o}=e,a=new Nv(i,this.serializeUrl(r),n,s);t.next(a);const l=Fy(r,this.rootComponentType).snapshot;return Hf(Object.assign(Object.assign({},e),{targetSnapshot:l,urlAfterRedirects:r,extras:Object.assign(Object.assign({},o),{skipLocationChange:!1,replaceUrl:!1})}))}return this.rawUrlTree=e.rawUrl,this.browserUrlTree=e.urlAfterRedirects,e.resolve(null),If}),Nb(e=>{const{targetSnapshot:t,id:i,extractedUrl:r,rawUrl:n,extras:{skipLocationChange:s,replaceUrl:o}}=e;return this.hooks.beforePreactivation(t,{navigationId:i,appliedUrlTree:r,rawUrlTree:n,skipLocationChange:!!s,replaceUrl:!!o})}),tp(e=>{const t=new Vv(e.id,this.serializeUrl(e.extractedUrl),this.serializeUrl(e.urlAfterRedirects),e.targetSnapshot);this.triggerEvent(t)}),M(e=>Object.assign(Object.assign({},e),{guards:yb(e.targetSnapshot,e.currentSnapshot,this.rootContexts)})),function(e,t){return function(i){return i.pipe(H(i=>{const{targetSnapshot:r,currentSnapshot:n,guards:{canActivateChecks:s,canDeactivateChecks:o}}=i;return 0===o.length&&0===s.length?Hf(Object.assign(Object.assign({},i),{guardsResult:!0})):function(e,t,i,r){return B(e).pipe(H(e=>function(e,t,i,r,n){const s=t&&t.routeConfig?t.routeConfig.canDeactivate:null;return s&&0!==s.length?Hf(s.map(s=>{const o=bb(s,t,n);let a;if(function(e){return e&&sb(e.canDeactivate)}(o))a=ly(o.canDeactivate(e,t,i,r));else{if(!sb(o))throw new Error("Invalid CanDeactivate guard");a=ly(o(e,t,i,r))}return a.pipe(Tv())})).pipe(Sb()):Hf(!0)}(e.component,e.route,i,t,r)),Tv(e=>!0!==e,!0))}(o,r,n,e).pipe(H(i=>i&&"boolean"==typeof i?function(e,t,i,r){return B(t).pipe(kp(t=>B([xb(t.route.parent,r),kb(t.route,r),Ab(e,t.path,i),Eb(e,t.route,i)]).pipe(Uf(),Tv(e=>!0!==e,!0))),Tv(e=>!0!==e,!0))}(r,s,e,t):Hf(i)),M(e=>Object.assign(Object.assign({},i),{guardsResult:e})))}))}}(this.ngModule.injector,e=>this.triggerEvent(e)),tp(e=>{if(ob(e.guardsResult)){const t=ty(`Redirecting to "${this.serializeUrl(e.guardsResult)}"`);throw t.url=e.guardsResult,t}}),tp(e=>{const t=new qv(e.id,this.serializeUrl(e.extractedUrl),this.serializeUrl(e.urlAfterRedirects),e.targetSnapshot,!!e.guardsResult);this.triggerEvent(t)}),Wf(e=>{if(!e.guardsResult){this.resetUrlToCurrentUrlTree();const i=new Bv(e.id,this.serializeUrl(e.extractedUrl),"");return t.next(i),e.resolve(!1),!1}return!0}),Nb(e=>{if(e.guards.canActivateChecks.length)return Hf(e).pipe(tp(e=>{const t=new zv(e.id,this.serializeUrl(e.extractedUrl),this.serializeUrl(e.urlAfterRedirects),e.targetSnapshot);this.triggerEvent(t)}),Gf(e=>{let i=!1;return Hf(e).pipe((r=this.paramsInheritanceStrategy,n=this.ngModule.injector,function(e){return e.pipe(H(e=>{const{targetSnapshot:t,guards:{canActivateChecks:i}}=e;if(!i.length)return Hf(e);let s=0;return B(i).pipe(kp(e=>function(e,t,i,r){return function(e,t,i,r){const n=Object.keys(e);if(0===n.length)return Hf({});const s={};return B(n).pipe(H(n=>function(e,t,i,r){const n=bb(e,t,r);return ly(n.resolve?n.resolve(t,i):n(t,i))}(e[n],t,i,r).pipe(tp(e=>{s[n]=e}))),mv(1),H(()=>Object.keys(s).length===n.length?Hf(s):If))}(e._resolve,e,t,r).pipe(M(t=>(e._resolvedData=t,e.data=Object.assign(Object.assign({},e.data),jy(e,i).resolve),null)))}(e.route,t,r,n)),tp(()=>s++),mv(1),H(t=>s===i.length?Hf(e):If))}))}),tp({next:()=>i=!0,complete:()=>{if(!i){const i=new Bv(e.id,this.serializeUrl(e.extractedUrl),"At least one route resolver didn't emit any value.");t.next(i),e.resolve(!1)}}}));var r,n}),tp(e=>{const t=new Wv(e.id,this.serializeUrl(e.extractedUrl),this.serializeUrl(e.urlAfterRedirects),e.targetSnapshot);this.triggerEvent(t)}))}),Nb(e=>{const{targetSnapshot:t,id:i,extractedUrl:r,rawUrl:n,extras:{skipLocationChange:s,replaceUrl:o}}=e;return this.hooks.afterPreactivation(t,{navigationId:i,appliedUrlTree:r,rawUrlTree:n,skipLocationChange:!!s,replaceUrl:!!o})}),M(e=>{const t=function(e,t,i){const r=function e(t,i,r){if(r&&t.shouldReuseRoute(i.value,r.value.snapshot)){const n=r.value;n._futureSnapshot=i.value;const s=function(t,i,r){return i.children.map(i=>{for(const n of r.children)if(t.shouldReuseRoute(n.value.snapshot,i.value))return e(t,i,n);return e(t,i)})}(t,i,r);return new Dy(n,s)}{const r=t.retrieve(i.value);if(r){const e=r.route;return function e(t,i){if(t.value.routeConfig!==i.value.routeConfig)throw new Error("Cannot reattach ActivatedRouteSnapshot created from a different route");if(t.children.length!==i.children.length)throw new Error("Cannot reattach ActivatedRouteSnapshot with a different number of children");i.value._futureSnapshot=t.value;for(let r=0;re(t,i));return new Dy(r,s)}}var n}(e,t._root,i?i._root:void 0);return new My(r,t)}(this.routeReuseStrategy,e.targetSnapshot,e.currentRouterState);return Object.assign(Object.assign({},e),{targetRouterState:t})}),tp(e=>{this.currentUrlTree=e.urlAfterRedirects,this.rawUrlTree=this.urlHandlingStrategy.merge(this.currentUrlTree,e.rawUrl),this.routerState=e.targetRouterState,"deferred"===this.urlUpdateStrategy&&(e.extras.skipLocationChange||this.setBrowserUrl(this.rawUrlTree,!!e.extras.replaceUrl,e.id,e.extras.state),this.browserUrlTree=e.urlAfterRedirects)}),(s=this.rootContexts,o=this.routeReuseStrategy,a=e=>this.triggerEvent(e),M(e=>(new ib(o,e.targetRouterState,e.currentRouterState,a).activate(s),e))),tp({next(){i=!0},complete(){i=!0}}),(n=()=>{if(!i&&!r){this.resetUrlToCurrentUrlTree();const i=new Bv(e.id,this.serializeUrl(e.extractedUrl),`Navigation ID ${e.id} is not equal to the current navigation id ${this.navigationId}`);t.next(i),e.resolve(!1)}this.currentNavigation=null},e=>e.lift(new Iv(n))),Ev(i=>{if(r=!0,(n=i)&&n.ngNavigationCancelingError){const r=ob(i.url);r||(this.navigated=!0,this.resetStateAndUrl(e.currentRouterState,e.currentUrlTree,e.rawUrl));const n=new Bv(e.id,this.serializeUrl(e.extractedUrl),i.message);t.next(n),r?setTimeout(()=>{const t=this.urlHandlingStrategy.merge(i.url,this.rawUrlTree);return this.scheduleNavigation(t,"imperative",null,{skipLocationChange:e.extras.skipLocationChange,replaceUrl:"eager"===this.urlUpdateStrategy},{resolve:e.resolve,reject:e.reject,promise:e.promise})},0):e.resolve(!1)}else{this.resetStateAndUrl(e.currentRouterState,e.currentUrlTree,e.rawUrl);const r=new Hv(e.id,this.serializeUrl(e.extractedUrl),i);t.next(r);try{e.resolve(this.errorHandler(i))}catch(s){e.reject(s)}}var n;return If}));var n,s,o,a}))}resetRootComponentType(e){this.rootComponentType=e,this.routerState.root.component=this.rootComponentType}getTransition(){const e=this.transitions.value;return e.urlAfterRedirects=this.browserUrlTree,e}setTransition(e){this.transitions.next(Object.assign(Object.assign({},this.getTransition()),e))}initialNavigation(){this.setUpLocationChangeListener(),0===this.navigationId&&this.navigateByUrl(this.location.path(!0),{replaceUrl:!0})}setUpLocationChangeListener(){this.locationSubscription||(this.locationSubscription=this.location.subscribe(e=>{let t=this.parseUrl(e.url);const i="popstate"===e.type?"popstate":"hashchange",r=e.state&&e.state.navigationId?e.state:null;setTimeout(()=>{this.scheduleNavigation(t,i,r,{replaceUrl:!0})},0)}))}get url(){return this.serializeUrl(this.currentUrlTree)}getCurrentNavigation(){return this.currentNavigation}triggerEvent(e){this.events.next(e)}resetConfig(e){Hb(e),this.config=e.map(qb),this.navigated=!1,this.lastSuccessfulId=-1}ngOnDestroy(){this.dispose()}dispose(){this.locationSubscription&&(this.locationSubscription.unsubscribe(),this.locationSubscription=null)}createUrlTree(e,t={}){const{relativeTo:i,queryParams:r,fragment:n,preserveQueryParams:s,queryParamsHandling:o,preserveFragment:a}=t;kr()&&s&&console&&console.warn&&console.warn("preserveQueryParams is deprecated, use queryParamsHandling instead.");const l=i||this.routerState.root,c=a?this.currentUrlTree.fragment:n;let h=null;if(o)switch(o){case"merge":h=Object.assign(Object.assign({},this.currentUrlTree.queryParams),r);break;case"preserve":h=this.currentUrlTree.queryParams;break;default:h=r||null}else h=s?this.currentUrlTree.queryParams:r||null;return null!==h&&(h=this.removeEmptyProps(h)),function(e,t,i,r,n){if(0===i.length)return $y(t.root,t.root,t,r,n);const s=function(e){if("string"==typeof e[0]&&1===e.length&&"/"===e[0])return new Ky(!0,0,e);let t=0,i=!1;const r=e.reduce((e,r,n)=>{if("object"==typeof r&&null!=r){if(r.outlets){const t={};return ay(r.outlets,(e,i)=>{t[i]="string"==typeof e?e.split("/"):e}),[...e,{outlets:t}]}if(r.segmentPath)return[...e,r.segmentPath]}return"string"!=typeof r?[...e,r]:0===n?(r.split("/").forEach((r,n)=>{0==n&&"."===r||(0==n&&""===r?i=!0:".."===r?t++:""!=r&&e.push(r))}),e):[...e,r]},[]);return new Ky(i,t,r)}(i);if(s.toRoot())return $y(t.root,new uy([],{}),t,r,n);const o=function(e,t,i){if(e.isAbsolute)return new Gy(t.root,!0,0);if(-1===i.snapshot._lastPathIndex){const e=i.snapshot._urlSegment;return new Gy(e,e===t.root,0)}const r=Wy(e.commands[0])?0:1;return function(e,t,i){let r=e,n=t,s=i;for(;s>n;){if(s-=n,r=r.parent,!r)throw new Error("Invalid number of '../'");n=r.segments.length}return new Gy(r,!1,n-s)}(i.snapshot._urlSegment,i.snapshot._lastPathIndex+r,e.numberOfDoubleDots)}(s,t,e),a=o.processChildren?Xy(o.segmentGroup,o.index,s.commands):Yy(o.segmentGroup,o.index,s.commands);return $y(o.segmentGroup,a,t,r,n)}(l,this.currentUrlTree,e,h,c)}navigateByUrl(e,t={skipLocationChange:!1}){kr()&&this.isNgZoneEnabled&&!mc.isInAngularZone()&&this.console.warn("Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?");const i=ob(e)?e:this.parseUrl(e),r=this.urlHandlingStrategy.merge(i,this.rawUrlTree);return this.scheduleNavigation(r,"imperative",null,t)}navigate(e,t={skipLocationChange:!1}){return function(e){for(let t=0;t{const r=e[i];return null!=r&&(t[i]=r),t},{})}processNavigations(){this.navigations.subscribe(e=>{this.navigated=!0,this.lastSuccessfulId=e.id,this.events.next(new jv(e.id,this.serializeUrl(e.extractedUrl),this.serializeUrl(this.currentUrlTree))),this.lastSuccessfulNavigation=this.currentNavigation,this.currentNavigation=null,e.resolve(!0)},e=>{this.console.warn("Unhandled Navigation Error: ")})}scheduleNavigation(e,t,i,r,n){const s=this.getTransition();if(s&&"imperative"!==t&&"imperative"===s.source&&s.rawUrl.toString()===e.toString())return Promise.resolve(!0);if(s&&"hashchange"==t&&"popstate"===s.source&&s.rawUrl.toString()===e.toString())return Promise.resolve(!0);if(s&&"popstate"==t&&"hashchange"===s.source&&s.rawUrl.toString()===e.toString())return Promise.resolve(!0);let o,a,l;n?(o=n.resolve,a=n.reject,l=n.promise):l=new Promise((e,t)=>{o=e,a=t});const c=++this.navigationId;return this.setTransition({id:c,source:t,restoredState:i,currentUrlTree:this.currentUrlTree,currentRawUrl:this.rawUrlTree,rawUrl:e,extras:r,resolve:o,reject:a,promise:l,currentSnapshot:this.routerState.snapshot,currentRouterState:this.routerState}),l.catch(e=>Promise.reject(e))}setBrowserUrl(e,t,i,r){const n=this.urlSerializer.serialize(e);r=r||{},this.location.isCurrentPathEqualTo(n)||t?this.location.replaceState(n,"",Object.assign(Object.assign({},r),{navigationId:i})):this.location.go(n,"",Object.assign(Object.assign({},r),{navigationId:i}))}resetStateAndUrl(e,t,i){this.routerState=e,this.currentUrlTree=t,this.rawUrlTree=this.urlHandlingStrategy.merge(this.currentUrlTree,i),this.resetUrlToCurrentUrlTree()}resetUrlToCurrentUrlTree(){this.location.replaceState(this.urlSerializer.serialize(this.rawUrlTree),"",{navigationId:this.lastSuccessfulId})}}return e.\u0275fac=function(t){return new(t||e)(Ze($s),Ze(_y),Ze(Kb),Ze(lh),Ze(ao),Ze(Nc),Ze(fc),Ze(void 0))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),Jb=(()=>{class e{constructor(e,t,i,r,n){this.router=e,this.route=t,this.commands=[],this.onChanges=new S,null==i&&r.setAttribute(n.nativeElement,"tabindex","0")}ngOnChanges(e){this.onChanges.next(this)}set routerLink(e){this.commands=null!=e?Array.isArray(e)?e:[e]:[]}set preserveQueryParams(e){kr()&&console&&console.warn&&console.warn("preserveQueryParams is deprecated!, use queryParamsHandling instead."),this.preserve=e}onClick(){const e={skipLocationChange:tw(this.skipLocationChange),replaceUrl:tw(this.replaceUrl),state:this.state};return this.router.navigateByUrl(this.urlTree,e),!0}get urlTree(){return this.router.createUrlTree(this.commands,{relativeTo:this.route,queryParams:this.queryParams,fragment:this.fragment,preserveQueryParams:tw(this.preserve),queryParamsHandling:this.queryParamsHandling,preserveFragment:tw(this.preserveFragment)})}}return e.\u0275fac=function(t){return new(t||e)(Co(Qb),Co(Ny),So("tabindex"),Co(Fa),Co(Pa))},e.\u0275dir=bt({type:e,selectors:[["","routerLink","",5,"a",5,"area"]],hostBindings:function(e,t){1&e&&Io("click",(function(){return t.onClick()}))},inputs:{routerLink:"routerLink",preserveQueryParams:"preserveQueryParams",queryParams:"queryParams",fragment:"fragment",queryParamsHandling:"queryParamsHandling",preserveFragment:"preserveFragment",skipLocationChange:"skipLocationChange",replaceUrl:"replaceUrl",state:"state"},features:[Dt]}),e})(),ew=(()=>{class e{constructor(e,t,i){this.router=e,this.route=t,this.locationStrategy=i,this.commands=[],this.onChanges=new S,this.subscription=e.events.subscribe(e=>{e instanceof jv&&this.updateTargetUrlAndHref()})}set routerLink(e){this.commands=null!=e?Array.isArray(e)?e:[e]:[]}set preserveQueryParams(e){kr()&&console&&console.warn&&console.warn("preserveQueryParams is deprecated, use queryParamsHandling instead."),this.preserve=e}ngOnChanges(e){this.updateTargetUrlAndHref(),this.onChanges.next(this)}ngOnDestroy(){this.subscription.unsubscribe()}onClick(e,t,i,r){if(0!==e||t||i||r)return!0;if("string"==typeof this.target&&"_self"!=this.target)return!0;const n={skipLocationChange:tw(this.skipLocationChange),replaceUrl:tw(this.replaceUrl),state:this.state};return this.router.navigateByUrl(this.urlTree,n),!1}updateTargetUrlAndHref(){this.href=this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.urlTree))}get urlTree(){return this.router.createUrlTree(this.commands,{relativeTo:this.route,queryParams:this.queryParams,fragment:this.fragment,preserveQueryParams:tw(this.preserve),queryParamsHandling:this.queryParamsHandling,preserveFragment:tw(this.preserveFragment)})}}return e.\u0275fac=function(t){return new(t||e)(Co(Qb),Co(Ny),Co(rh))},e.\u0275dir=bt({type:e,selectors:[["a","routerLink",""],["area","routerLink",""]],hostVars:2,hostBindings:function(e,t){1&e&&Io("click",(function(e){return t.onClick(e.button,e.ctrlKey,e.metaKey,e.shiftKey)})),2&e&&(fa("href",t.href,Zr),vo("target",t.target))},inputs:{routerLink:"routerLink",preserveQueryParams:"preserveQueryParams",target:"target",queryParams:"queryParams",fragment:"fragment",queryParamsHandling:"queryParamsHandling",preserveFragment:"preserveFragment",skipLocationChange:"skipLocationChange",replaceUrl:"replaceUrl",state:"state"},features:[Dt]}),e})();function tw(e){return""===e||!!e}let iw=(()=>{class e{constructor(e,t,i,r,n,s){this.router=e,this.element=t,this.renderer=i,this.cdr=r,this.link=n,this.linkWithHref=s,this.classes=[],this.isActive=!1,this.routerLinkActiveOptions={exact:!1},this.routerEventsSubscription=e.events.subscribe(e=>{e instanceof jv&&this.update()})}ngAfterContentInit(){B([this.links.changes,this.linksWithHrefs.changes,Hf(null)]).pipe(q()).subscribe(e=>{this.update(),this.subscribeToEachLinkOnChanges()})}subscribeToEachLinkOnChanges(){var e;null===(e=this.linkInputChangesSubscription)||void 0===e||e.unsubscribe();const t=[...this.links.toArray(),...this.linksWithHrefs.toArray(),this.link,this.linkWithHref].filter(e=>!!e).map(e=>e.onChanges);this.linkInputChangesSubscription=B(t).pipe(q()).subscribe(e=>{this.isActive!==this.isLinkActive(this.router)(e)&&this.update()})}set routerLinkActive(e){const t=Array.isArray(e)?e:e.split(" ");this.classes=t.filter(e=>!!e)}ngOnChanges(e){this.update()}ngOnDestroy(){var e;this.routerEventsSubscription.unsubscribe(),null===(e=this.linkInputChangesSubscription)||void 0===e||e.unsubscribe()}update(){this.links&&this.linksWithHrefs&&this.router.navigated&&Promise.resolve().then(()=>{const e=this.hasActiveLinks();this.isActive!==e&&(this.isActive=e,this.cdr.markForCheck(),this.classes.forEach(t=>{e?this.renderer.addClass(this.element.nativeElement,t):this.renderer.removeClass(this.element.nativeElement,t)}))})}isLinkActive(e){return t=>e.isActive(t.urlTree,this.routerLinkActiveOptions.exact)}hasActiveLinks(){const e=this.isLinkActive(this.router);return this.link&&e(this.link)||this.linkWithHref&&e(this.linkWithHref)||this.links.some(e)||this.linksWithHrefs.some(e)}}return e.\u0275fac=function(t){return new(t||e)(Co(Qb),Co(Pa),Co(Fa),Co(zs),Co(Jb,8),Co(ew,8))},e.\u0275dir=bt({type:e,selectors:[["","routerLinkActive",""]],contentQueries:function(e,t,i){var r;1&e&&(Ul(i,Jb,!0),Ul(i,ew,!0)),2&e&&(Nl(r=zl())&&(t.links=r),Nl(r=zl())&&(t.linksWithHrefs=r))},inputs:{routerLinkActiveOptions:"routerLinkActiveOptions",routerLinkActive:"routerLinkActive"},exportAs:["routerLinkActive"],features:[Dt]}),e})(),rw=(()=>{class e{constructor(e,t,i,r,n){this.parentContexts=e,this.location=t,this.resolver=i,this.changeDetector=n,this.activated=null,this._activatedRoute=null,this.activateEvents=new El,this.deactivateEvents=new El,this.name=r||"primary",e.onChildOutletCreated(this.name,this)}ngOnDestroy(){this.parentContexts.onChildOutletDestroyed(this.name)}ngOnInit(){if(!this.activated){const e=this.parentContexts.getContext(this.name);e&&e.route&&(e.attachRef?this.attach(e.attachRef,e.route):this.activateWith(e.route,e.resolver||null))}}get isActivated(){return!!this.activated}get component(){if(!this.activated)throw new Error("Outlet is not activated");return this.activated.instance}get activatedRoute(){if(!this.activated)throw new Error("Outlet is not activated");return this._activatedRoute}get activatedRouteData(){return this._activatedRoute?this._activatedRoute.snapshot.data:{}}detach(){if(!this.activated)throw new Error("Outlet is not activated");this.location.detach();const e=this.activated;return this.activated=null,this._activatedRoute=null,e}attach(e,t){this.activated=e,this._activatedRoute=t,this.location.insert(e.hostView)}deactivate(){if(this.activated){const e=this.component;this.activated.destroy(),this.activated=null,this._activatedRoute=null,this.deactivateEvents.emit(e)}}activateWith(e,t){if(this.isActivated)throw new Error("Cannot activate an already activated outlet");this._activatedRoute=e;const i=(t=t||this.resolver).resolveComponentFactory(e._futureSnapshot.routeConfig.component),r=this.parentContexts.getOrCreateContext(this.name).children,n=new nw(e,r,this.location.injector);this.activated=this.location.createComponent(i,this.location.length,n),this.changeDetector.markForCheck(),this.activateEvents.emit(this.activated.instance)}}return e.\u0275fac=function(t){return new(t||e)(Co(Kb),Co(nl),Co(La),So("name"),Co(zs))},e.\u0275dir=bt({type:e,selectors:[["router-outlet"]],outputs:{activateEvents:"activate",deactivateEvents:"deactivate"},exportAs:["outlet"]}),e})();class nw{constructor(e,t,i){this.route=e,this.childContexts=t,this.parent=i}get(e,t){return e===Ny?this.route:e===Kb?this.childContexts:this.parent.get(e,t)}}class sw{}class ow{preload(e,t){return Hf(null)}}let aw=(()=>{class e{constructor(e,t,i,r,n){this.router=e,this.injector=r,this.preloadingStrategy=n,this.loader=new Wb(t,i,t=>e.triggerEvent(new $v(t)),t=>e.triggerEvent(new Kv(t)))}setUpPreloading(){this.subscription=this.router.events.pipe(Wf(e=>e instanceof jv),kp(()=>this.preload())).subscribe(()=>{})}preload(){const e=this.injector.get(et);return this.processRoutes(e,this.router.config)}ngOnDestroy(){this.subscription&&this.subscription.unsubscribe()}processRoutes(e,t){const i=[];for(const r of t)if(r.loadChildren&&!r.canLoad&&r._loadedConfig){const e=r._loadedConfig;i.push(this.processRoutes(e.module,e.routes))}else r.loadChildren&&!r.canLoad?i.push(this.preloadConfig(e,r)):r.children&&i.push(this.processRoutes(e,r.children));return B(i).pipe(q(),M(e=>{}))}preloadConfig(e,t){return this.preloadingStrategy.preload(t,()=>this.loader.load(e.injector,t).pipe(H(e=>(t._loadedConfig=e,this.processRoutes(e.module,e.routes)))))}}return e.\u0275fac=function(t){return new(t||e)(Ze(Qb),Ze(Nc),Ze(fc),Ze(ao),Ze(sw))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),lw=(()=>{class e{constructor(e,t,i={}){this.router=e,this.viewportScroller=t,this.options=i,this.lastId=0,this.lastSource="imperative",this.restoredId=0,this.store={},i.scrollPositionRestoration=i.scrollPositionRestoration||"disabled",i.anchorScrolling=i.anchorScrolling||"disabled"}init(){"disabled"!==this.options.scrollPositionRestoration&&this.viewportScroller.setHistoryScrollRestoration("manual"),this.routerEventsSubscription=this.createScrollEvents(),this.scrollEventsSubscription=this.consumeScrollEvents()}createScrollEvents(){return this.router.events.subscribe(e=>{e instanceof Nv?(this.store[this.lastId]=this.viewportScroller.getScrollPosition(),this.lastSource=e.navigationTrigger,this.restoredId=e.restoredState?e.restoredState.navigationId:0):e instanceof jv&&(this.lastId=e.id,this.scheduleScrollEvent(e,this.router.parseUrl(e.urlAfterRedirects).fragment))})}consumeScrollEvents(){return this.router.events.subscribe(e=>{e instanceof Qv&&(e.position?"top"===this.options.scrollPositionRestoration?this.viewportScroller.scrollToPosition([0,0]):"enabled"===this.options.scrollPositionRestoration&&this.viewportScroller.scrollToPosition(e.position):e.anchor&&"enabled"===this.options.anchorScrolling?this.viewportScroller.scrollToAnchor(e.anchor):"disabled"!==this.options.scrollPositionRestoration&&this.viewportScroller.scrollToPosition([0,0]))})}scheduleScrollEvent(e,t){this.router.triggerEvent(new Qv(e,"popstate"===this.lastSource?this.store[this.restoredId]:null,t))}ngOnDestroy(){this.routerEventsSubscription&&this.routerEventsSubscription.unsubscribe(),this.scrollEventsSubscription&&this.scrollEventsSubscription.unsubscribe()}}return e.\u0275fac=function(t){return new(t||e)(Ze(Qb),Ze(Dh),Ze(void 0))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();const cw=new Be("ROUTER_CONFIGURATION"),hw=new Be("ROUTER_FORROOT_GUARD"),uw=[lh,{provide:_y,useClass:my},{provide:Qb,useFactory:function(e,t,i,r,n,s,o,a={},l,c){const h=new Qb(null,e,t,i,r,n,s,sy(o));if(l&&(h.urlHandlingStrategy=l),c&&(h.routeReuseStrategy=c),a.errorHandler&&(h.errorHandler=a.errorHandler),a.malformedUriErrorHandler&&(h.malformedUriErrorHandler=a.malformedUriErrorHandler),a.enableTracing){const e=$c();h.events.subscribe(t=>{e.logGroup("Router Event: "+t.constructor.name),e.log(t.toString()),e.log(t),e.logGroupEnd()})}return a.onSameUrlNavigation&&(h.onSameUrlNavigation=a.onSameUrlNavigation),a.paramsInheritanceStrategy&&(h.paramsInheritanceStrategy=a.paramsInheritanceStrategy),a.urlUpdateStrategy&&(h.urlUpdateStrategy=a.urlUpdateStrategy),a.relativeLinkResolution&&(h.relativeLinkResolution=a.relativeLinkResolution),h},deps:[_y,Kb,lh,ao,Nc,fc,zb,cw,[class{},new se],[class{},new se]]},Kb,{provide:Ny,useFactory:function(e){return e.routerState.root},deps:[Qb]},{provide:Nc,useClass:Hc},aw,ow,class{preload(e,t){return t().pipe(Ev(()=>Hf(null)))}},{provide:cw,useValue:{enableTracing:!1}}];function dw(){return new Rc("Router",Qb)}let fw=(()=>{class e{constructor(e,t){}static forRoot(t,i){return{ngModule:e,providers:[uw,gw(t),{provide:hw,useFactory:mw,deps:[[Qb,new se,new ae]]},{provide:cw,useValue:i||{}},{provide:rh,useFactory:_w,deps:[Gc,[new ne(sh),new se],cw]},{provide:lw,useFactory:pw,deps:[Qb,Dh,cw]},{provide:sw,useExisting:i&&i.preloadingStrategy?i.preloadingStrategy:ow},{provide:Rc,multi:!0,useFactory:dw},[vw,{provide:Zl,multi:!0,useFactory:yw,deps:[vw]},{provide:ww,useFactory:bw,deps:[vw]},{provide:ic,multi:!0,useExisting:ww}]]}}static forChild(t){return{ngModule:e,providers:[gw(t)]}}}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)(Ze(hw,8),Ze(Qb,8))}}),e})();function pw(e,t,i){return i.scrollOffset&&t.setOffset(i.scrollOffset),new lw(e,t,i)}function _w(e,t,i={}){return i.useHash?new ah(e,t):new oh(e,t)}function mw(e){if(e)throw new Error("RouterModule.forRoot() called twice. Lazy loaded modules should use RouterModule.forChild() instead.");return"guarded"}function gw(e){return[{provide:lo,multi:!0,useValue:e},{provide:zb,multi:!0,useValue:e}]}let vw=(()=>{class e{constructor(e){this.injector=e,this.initNavigation=!1,this.resultOfPreactivationDone=new S}appInitializer(){return this.injector.get(Yc,Promise.resolve(null)).then(()=>{let e=null;const t=new Promise(t=>e=t),i=this.injector.get(Qb),r=this.injector.get(cw);if(this.isLegacyDisabled(r)||this.isLegacyEnabled(r))e(!0);else if("disabled"===r.initialNavigation)i.setUpLocationChangeListener(),e(!0);else{if("enabled"!==r.initialNavigation)throw new Error(`Invalid initialNavigation options: '${r.initialNavigation}'`);i.hooks.afterPreactivation=()=>this.initNavigation?Hf(null):(this.initNavigation=!0,e(!0),this.resultOfPreactivationDone),i.initialNavigation()}return t})}bootstrapListener(e){const t=this.injector.get(cw),i=this.injector.get(aw),r=this.injector.get(lw),n=this.injector.get(Qb),s=this.injector.get(Mc);e===s.components[0]&&(this.isLegacyEnabled(t)?n.initialNavigation():this.isLegacyDisabled(t)&&n.setUpLocationChangeListener(),i.setUpPreloading(),r.init(),n.resetRootComponentType(s.componentTypes[0]),this.resultOfPreactivationDone.next(null),this.resultOfPreactivationDone.complete())}isLegacyEnabled(e){return"legacy_enabled"===e.initialNavigation||!0===e.initialNavigation||void 0===e.initialNavigation}isLegacyDisabled(e){return"legacy_disabled"===e.initialNavigation||!1===e.initialNavigation}}return e.\u0275fac=function(t){return new(t||e)(Ze(ao))},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})();function yw(e){return e.appInitializer.bind(e)}function bw(e){return e.bootstrapListener.bind(e)}const ww=new Be("Router Initializer");function Cw(e,t,i,r){return new(i||(i=Promise))((function(n,s){function o(e){try{l(r.next(e))}catch(t){s(t)}}function a(e){try{l(r.throw(e))}catch(t){s(t)}}function l(e){var t;e.done?n(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(o,a)}l((r=r.apply(e,t||[])).next())}))}function Sw(){return location.origin}function kw(e){let t="";if(e)for(const i in e)e.hasOwnProperty(i)&&null!=e[i]&&(t+=`${t?"&":""}${i}=${encodeURIComponent(e[i])}`);return t}const xw={subject:"Latest Commit",author:"system",commit:"HEAD",date:(new Date).toISOString()};let Ew=(()=>{class e{constructor(e){this._http=e,this._sidebar=new pv(!0),this._repo_list=new pv([]),this._active_repo=new pv(null),this._active_driver=new pv(null),this._active_commit=new pv(null),this._test_statuses=new pv({}),this.sidebar=this._sidebar.asObservable(),this.repositories=this._repo_list.asObservable(),this.active_repo=this._active_repo.asObservable(),this.test_statuses=this._test_statuses.asObservable(),this.active_commit=this._active_commit.asObservable(),this.driver_list=this._active_repo.pipe(Dm(300),Gf(e=>this.loadDrivers({repository:"Public"===e?void 0:e})),E_()),this.driver_versions=this._active_driver.pipe(Gf(e=>this.loadDriverVersions(e)),E_()),this.active_driver=this._active_driver.asObservable(),this.driver_commits=Wg([this._active_repo,this._active_driver]).pipe(Gf(e=>{const[t,i]=e;return this.loadDriverCommits(i,{repository:t})}),te()),this.loadRepositories(),this._test_statuses.next(JSON.parse(localStorage.getItem("HARNESS.statuses")||"{}")),this._test_statuses.subscribe(e=>localStorage.setItem("HARNESS.statuses",JSON.stringify(e)))}getRepository(){return this._active_repo.getValue()}getDriver(){return this._active_driver.getValue()}toggleSidebar(){this._sidebar.next(!this._sidebar.getValue())}getCommit(){return this._active_commit.getValue()}setTestStatus(e){const t=Object.assign({},this._test_statuses.getValue());t[`${this._active_repo.getValue()}|${this._active_driver.getValue()}`]=e,this._test_statuses.next(t)}setCommit(e){this._active_commit.next(e)}setRepository(e){e!==this._active_repo.getValue()&&this._active_repo.next(e)}setDriver(e){this._active_driver.next(e)}loadRepositories(){return Cw(this,void 0,void 0,(function*(){const e=Sw()+"/build/repositories",t=["Public",...(yield this._http.get(e).toPromise()).filter(e=>"."!==e[0])];this._repo_list.next(t),this._active_repo.getValue()||this._active_repo.next(t[0])}))}loadRepositoryCommits(e={}){return Cw(this,void 0,void 0,(function*(){const e=Sw()+"/build/repositories_commits",t=yield this._http.get(e).toPromise();return[xw,...t]}))}loadDrivers(e={}){return Cw(this,void 0,void 0,(function*(){const t=kw(e),i=`${Sw()}/build${t?"?"+t:""}`;return this._http.get(i).toPromise()}))}loadDriverCommits(e,t={}){return Cw(this,void 0,void 0,(function*(){const t=`${Sw()}/build/${encodeURIComponent(e)}/commits`,i=yield this._http.get(t).toPromise();return this._active_commit.next(xw),[xw,...i]}))}loadDriverVersions(e){return Cw(this,void 0,void 0,(function*(){const t=`${Sw()}/build/${encodeURIComponent(e)}`;return this._http.get(t).toPromise()}))}cleanDriverVersions(e,t){return Cw(this,void 0,void 0,(function*(){const i=kw(t),r=`${Sw()}/build/${encodeURIComponent(e)}${i?"?"+i:""}`;return this._http.delete(r).toPromise()}))}compileDriver(e){return Cw(this,void 0,void 0,(function*(){const t=kw(e),i=Sw()+"/build";return this._http.post(i,t).toPromise()}))}}return e.\u0275fac=function(t){return new(t||e)(Ze(Up))},e.\u0275prov=ue({token:e,factory:e.\u0275fac,providedIn:"root"}),e})();var Aw=i("6i1C");let Ow=(()=>{class e{constructor(e,t){this._http=e,this._build=t,this._active_spec=new pv(null),this._active_commit=new pv(null),this._settings=new pv({}),this.active_spec=this._active_spec.asObservable(),this.active_commit=this._active_commit.asObservable(),this.settings=this._settings.asObservable(),this.spec_list=this._build.active_repo.pipe(Gf(e=>this.loadSpecFiles({repository:"Public"===e?void 0:e})),E_()),this.commit_list=this._active_spec.pipe(Wf(e=>!!e),Gf(e=>this.loadSpecCommits(e,{repository:"Public"===e?void 0:e})),E_()),Wg([this._build.active_driver,this.spec_list]).subscribe(e=>Cw(this,void 0,void 0,(function*(){const[t,i]=e,r=i.map(e=>({spec:e,similarity:Object(Aw.stringSimilarity)(e,t)}));r.sort((e,t)=>t.similarity-e.similarity),this._active_spec.next(r[0].spec)})))}setSpec(e){this._active_spec.next(e)}setCommit(e){this._active_commit.next(e)}setSettings(e){this._settings.next(Object.assign(Object.assign({},this._settings.getValue()),e))}loadSpecFiles(e={}){return Cw(this,void 0,void 0,(function*(){const t=kw(e),i=`${Sw()}/test${t?"?"+t:""}`;return this._http.get(i).toPromise()}))}loadSpecCommits(e,t){return Cw(this,void 0,void 0,(function*(){const t=`${Sw()}/test/${encodeURIComponent(e)}/commits`,i=yield this._http.get(t).toPromise();return this._active_commit.next(xw),[xw,...i]}))}runSpec(e){var t,i;return Cw(this,void 0,void 0,(function*(){const r=this._build.getRepository()||e.repository,n=kw(e={repository:"Public"===r?void 0:r,driver:this._build.getDriver()||e.driver,spec:this._active_spec.getValue()||e.spec,commit:(null===(t=this._build.getCommit())||void 0===t?void 0:t.commit)||e.commit,spec_commit:(null===(i=this._active_commit.getValue())||void 0===i?void 0:i.commit)||e.spec_commit,force:this._settings.getValue().force||e.force,debug:this._settings.getValue().debug_symbols||e.debug}),s=`${Sw()}/test${n?"?"+n:""}`;return this._http.post(s,e,{responseType:"text"}).toPromise()}))}}return e.\u0275fac=function(t){return new(t||e)(Ze(Up),Ze(Ew))},e.\u0275prov=ue({token:e,factory:e.\u0275fac,providedIn:"root"}),e})();const Tw=["underline"],Rw=["connectionContainer"],Lw=["inputContainer"],Pw=["label"];function Dw(e,t){1&e&&(To(0),Eo(1,"div",14),Oo(2,"div",15),Oo(3,"div",16),Oo(4,"div",17),Ao(),Eo(5,"div",18),Oo(6,"div",15),Oo(7,"div",16),Oo(8,"div",17),Ao(),Ro())}function Iw(e,t){1&e&&(Eo(0,"div",19),Vo(1,1),Ao())}function Mw(e,t){if(1&e&&(To(0),Vo(1,2),Eo(2,"span"),ha(3),Ao(),Ro()),2&e){const e=Bo(2);gn(3),ua(e._control.placeholder)}}function Fw(e,t){1&e&&Vo(0,3,["*ngSwitchCase","true"])}function Nw(e,t){1&e&&(Eo(0,"span",23),ha(1," *"),Ao())}function jw(e,t){if(1&e){const e=Lo();Eo(0,"label",20,21),Io("cdkObserveContent",(function(){return ni(e),Bo().updateOutlineGap()})),bo(2,Mw,4,1,"ng-container",12),bo(3,Fw,1,0,"ng-content",12),bo(4,Nw,2,0,"span",22),Ao()}if(2&e){const e=Bo();Xo("mat-empty",e._control.empty&&!e._shouldAlwaysFloat())("mat-form-field-empty",e._control.empty&&!e._shouldAlwaysFloat())("mat-accent","accent"==e.color)("mat-warn","warn"==e.color),ko("cdkObserveContentDisabled","outline"!=e.appearance)("id",e._labelId)("ngSwitch",e._hasLabel()),vo("for",e._control.id)("aria-owns",e._control.id),gn(2),ko("ngSwitchCase",!1),gn(1),ko("ngSwitchCase",!0),gn(1),ko("ngIf",!e.hideRequiredMarker&&e._control.required&&!e._control.disabled)}}function Bw(e,t){1&e&&(Eo(0,"div",24),Vo(1,4),Ao())}function Hw(e,t){if(1&e&&(Eo(0,"div",25,26),Oo(2,"span",27),Ao()),2&e){const e=Bo();gn(2),Xo("mat-accent","accent"==e.color)("mat-warn","warn"==e.color)}}function Uw(e,t){1&e&&(Eo(0,"div"),Vo(1,5),Ao()),2&e&&ko("@transitionMessages",Bo()._subscriptAnimationState)}function Vw(e,t){if(1&e&&(Eo(0,"div",31),ha(1),Ao()),2&e){const e=Bo(2);ko("id",e._hintLabelId),gn(1),ua(e.hintLabel)}}function qw(e,t){if(1&e&&(Eo(0,"div",28),bo(1,Vw,2,2,"div",29),Vo(2,6),Oo(3,"div",30),Vo(4,7),Ao()),2&e){const e=Bo();ko("@transitionMessages",e._subscriptAnimationState),gn(1),ko("ngIf",e.hintLabel)}}const zw=["*",[["","matPrefix",""]],[["mat-placeholder"]],[["mat-label"]],[["","matSuffix",""]],[["mat-error"]],[["mat-hint",3,"align","end"]],[["mat-hint","align","end"]]],Ww=["*","[matPrefix]","mat-placeholder","mat-label","[matSuffix]","mat-error","mat-hint:not([align='end'])","mat-hint[align='end']"],$w=new Be("MatError"),Kw={transitionMessages:uu("transitionMessages",[_u("enter",pu({opacity:1,transform:"translateY(0%)"})),mu("void => enter",[pu({opacity:0,transform:"translateY(-100%)"}),du("300ms cubic-bezier(0.55, 0, 0.55, 0.2)")])])};let Gw=(()=>{class e{}return e.\u0275fac=function(t){return new(t||e)},e.\u0275dir=bt({type:e}),e})();const Zw=new Be("MatHint");let Yw=(()=>{class e{}return e.\u0275fac=function(t){return new(t||e)},e.\u0275dir=bt({type:e,selectors:[["mat-label"]]}),e})(),Xw=(()=>{class e{}return e.\u0275fac=function(t){return new(t||e)},e.\u0275dir=bt({type:e,selectors:[["mat-placeholder"]]}),e})();const Qw=new Be("MatPrefix");let Jw=(()=>{class e{}return e.\u0275fac=function(t){return new(t||e)},e.\u0275dir=bt({type:e,selectors:[["","matPrefix",""]],features:[Oa([{provide:Qw,useExisting:e}])]}),e})();const eC=new Be("MatSuffix");let tC=0;class iC{constructor(e){this._elementRef=e}}const rC=sg(iC,"primary"),nC=new Be("MAT_FORM_FIELD_DEFAULT_OPTIONS"),sC=new Be("MatFormField");let oC=(()=>{class e extends rC{constructor(e,t,i,r,n,s,o,a){super(e),this._elementRef=e,this._changeDetectorRef=t,this._dir=r,this._defaults=n,this._platform=s,this._ngZone=o,this._outlineGapCalculationNeededImmediately=!1,this._outlineGapCalculationNeededOnStable=!1,this._destroyed=new S,this._showAlwaysAnimate=!1,this._subscriptAnimationState="",this._hintLabel="",this._hintLabelId="mat-hint-"+tC++,this._labelId="mat-form-field-label-"+tC++,this._labelOptions=i||{},this.floatLabel=this._getDefaultFloatLabelState(),this._animationsEnabled="NoopAnimations"!==a,this.appearance=n&&n.appearance?n.appearance:"legacy",this._hideRequiredMarker=!(!n||null==n.hideRequiredMarker)&&n.hideRequiredMarker}get appearance(){return this._appearance}set appearance(e){const t=this._appearance;this._appearance=e||this._defaults&&this._defaults.appearance||"legacy","outline"===this._appearance&&t!==e&&(this._outlineGapCalculationNeededOnStable=!0)}get hideRequiredMarker(){return this._hideRequiredMarker}set hideRequiredMarker(e){this._hideRequiredMarker=r_(e)}_shouldAlwaysFloat(){return"always"===this.floatLabel&&!this._showAlwaysAnimate}_canLabelFloat(){return"never"!==this.floatLabel}get hintLabel(){return this._hintLabel}set hintLabel(e){this._hintLabel=e,this._processHints()}get floatLabel(){return"legacy"!==this.appearance&&"never"===this._floatLabel?"auto":this._floatLabel}set floatLabel(e){e!==this._floatLabel&&(this._floatLabel=e||this._getDefaultFloatLabelState(),this._changeDetectorRef.markForCheck())}get _control(){return this._explicitFormFieldControl||this._controlNonStatic||this._controlStatic}set _control(e){this._explicitFormFieldControl=e}getLabelId(){return this._hasFloatingLabel()?this._labelId:null}getConnectedOverlayOrigin(){return this._connectionContainerRef||this._elementRef}ngAfterContentInit(){this._validateControlChild();const e=this._control;e.controlType&&this._elementRef.nativeElement.classList.add("mat-form-field-type-"+e.controlType),e.stateChanges.pipe(v_(null)).subscribe(()=>{this._validatePlaceholders(),this._syncDescribedByIds(),this._changeDetectorRef.markForCheck()}),e.ngControl&&e.ngControl.valueChanges&&e.ngControl.valueChanges.pipe(__(this._destroyed)).subscribe(()=>this._changeDetectorRef.markForCheck()),this._ngZone.runOutsideAngular(()=>{this._ngZone.onStable.pipe(__(this._destroyed)).subscribe(()=>{this._outlineGapCalculationNeededOnStable&&this.updateOutlineGap()})}),W(this._prefixChildren.changes,this._suffixChildren.changes).subscribe(()=>{this._outlineGapCalculationNeededOnStable=!0,this._changeDetectorRef.markForCheck()}),this._hintChildren.changes.pipe(v_(null)).subscribe(()=>{this._processHints(),this._changeDetectorRef.markForCheck()}),this._errorChildren.changes.pipe(v_(null)).subscribe(()=>{this._syncDescribedByIds(),this._changeDetectorRef.markForCheck()}),this._dir&&this._dir.change.pipe(__(this._destroyed)).subscribe(()=>{"function"==typeof requestAnimationFrame?this._ngZone.runOutsideAngular(()=>{requestAnimationFrame(()=>this.updateOutlineGap())}):this.updateOutlineGap()})}ngAfterContentChecked(){this._validateControlChild(),this._outlineGapCalculationNeededImmediately&&this.updateOutlineGap()}ngAfterViewInit(){this._subscriptAnimationState="enter",this._changeDetectorRef.detectChanges()}ngOnDestroy(){this._destroyed.next(),this._destroyed.complete()}_shouldForward(e){const t=this._control?this._control.ngControl:null;return t&&t[e]}_hasPlaceholder(){return!!(this._control&&this._control.placeholder||this._placeholderChild)}_hasLabel(){return!(!this._labelChildNonStatic&&!this._labelChildStatic)}_shouldLabelFloat(){return this._canLabelFloat()&&(this._control&&this._control.shouldLabelFloat||this._shouldAlwaysFloat())}_hideControlPlaceholder(){return"legacy"===this.appearance&&!this._hasLabel()||this._hasLabel()&&!this._shouldLabelFloat()}_hasFloatingLabel(){return this._hasLabel()||"legacy"===this.appearance&&this._hasPlaceholder()}_getDisplayedMessages(){return this._errorChildren&&this._errorChildren.length>0&&this._control.errorState?"error":"hint"}_animateAndLockLabel(){this._hasFloatingLabel()&&this._canLabelFloat()&&(this._animationsEnabled&&this._label&&(this._showAlwaysAnimate=!0,Bf(this._label.nativeElement,"transitionend").pipe(Qf(1)).subscribe(()=>{this._showAlwaysAnimate=!1})),this.floatLabel="always",this._changeDetectorRef.markForCheck())}_validatePlaceholders(){}_processHints(){this._validateHints(),this._syncDescribedByIds()}_validateHints(){}_getDefaultFloatLabelState(){return this._defaults&&this._defaults.floatLabel||this._labelOptions.float||"auto"}_syncDescribedByIds(){if(this._control){let e=[];if(this._control.userAriaDescribedBy&&"string"==typeof this._control.userAriaDescribedBy&&e.push(...this._control.userAriaDescribedBy.split(" ")),"hint"===this._getDisplayedMessages()){const t=this._hintChildren?this._hintChildren.find(e=>"start"===e.align):null,i=this._hintChildren?this._hintChildren.find(e=>"end"===e.align):null;t?e.push(t.id):this._hintLabel&&e.push(this._hintLabelId),i&&e.push(i.id)}else this._errorChildren&&e.push(...this._errorChildren.map(e=>e.id));this._control.setDescribedByIds(e)}}_validateControlChild(){}updateOutlineGap(){const e=this._label?this._label.nativeElement:null;if("outline"!==this.appearance||!e||!e.children.length||!e.textContent.trim())return;if(!this._platform.isBrowser)return;if(!this._isAttachedToDOM())return void(this._outlineGapCalculationNeededImmediately=!0);let t=0,i=0;const r=this._connectionContainerRef.nativeElement,n=r.querySelectorAll(".mat-form-field-outline-start"),s=r.querySelectorAll(".mat-form-field-outline-gap");if(this._label&&this._label.nativeElement.children.length){const n=r.getBoundingClientRect();if(0===n.width&&0===n.height)return this._outlineGapCalculationNeededOnStable=!0,void(this._outlineGapCalculationNeededImmediately=!1);const s=this._getStartEnd(n),o=e.children,a=this._getStartEnd(o[0].getBoundingClientRect());let l=0;for(let e=0;e0?.75*l+10:0}for(let o=0;o{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[Lh,rg,Hm],rg]}),e})();function lC(e,t){return new v(i=>{const r=e.length;if(0===r)return void i.complete();const n=new Array(r);let s=0,o=0;for(let a=0;a{c||(c=!0,o++),n[a]=e},error:e=>i.error(e),complete:()=>{s++,s!==r&&c||(o===r&&i.next(t?t.reduce((e,t,i)=>(e[t]=n[i],e),{}):n),i.complete())}}))}})}const cC=new Be("NgValueAccessor"),hC={provide:cC,useExisting:ke(()=>uC),multi:!0};let uC=(()=>{class e{constructor(e,t){this._renderer=e,this._elementRef=t,this.onChange=e=>{},this.onTouched=()=>{}}writeValue(e){this._renderer.setProperty(this._elementRef.nativeElement,"checked",e)}registerOnChange(e){this.onChange=e}registerOnTouched(e){this.onTouched=e}setDisabledState(e){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",e)}}return e.\u0275fac=function(t){return new(t||e)(Co(Fa),Co(Pa))},e.\u0275dir=bt({type:e,selectors:[["input","type","checkbox","formControlName",""],["input","type","checkbox","formControl",""],["input","type","checkbox","ngModel",""]],hostBindings:function(e,t){1&e&&Io("change",(function(e){return t.onChange(e.target.checked)}))("blur",(function(){return t.onTouched()}))},features:[Oa([hC])]}),e})();const dC={provide:cC,useExisting:ke(()=>pC),multi:!0},fC=new Be("CompositionEventMode");let pC=(()=>{class e{constructor(e,t,i){this._renderer=e,this._elementRef=t,this._compositionMode=i,this.onChange=e=>{},this.onTouched=()=>{},this._composing=!1,null==this._compositionMode&&(this._compositionMode=!function(){const e=$c()?$c().getUserAgent():"";return/android (\d+)/.test(e.toLowerCase())}())}writeValue(e){this._renderer.setProperty(this._elementRef.nativeElement,"value",null==e?"":e)}registerOnChange(e){this.onChange=e}registerOnTouched(e){this.onTouched=e}setDisabledState(e){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",e)}_handleInput(e){(!this._compositionMode||this._compositionMode&&!this._composing)&&this.onChange(e)}_compositionStart(){this._composing=!0}_compositionEnd(e){this._composing=!1,this._compositionMode&&this.onChange(e)}}return e.\u0275fac=function(t){return new(t||e)(Co(Fa),Co(Pa),Co(fC,8))},e.\u0275dir=bt({type:e,selectors:[["input","formControlName","",3,"type","checkbox"],["textarea","formControlName",""],["input","formControl","",3,"type","checkbox"],["textarea","formControl",""],["input","ngModel","",3,"type","checkbox"],["textarea","ngModel",""],["","ngDefaultControl",""]],hostBindings:function(e,t){1&e&&Io("input",(function(e){return t._handleInput(e.target.value)}))("blur",(function(){return t.onTouched()}))("compositionstart",(function(){return t._compositionStart()}))("compositionend",(function(e){return t._compositionEnd(e.target.value)}))},features:[Oa([dC])]}),e})(),_C=(()=>{class e{get value(){return this.control?this.control.value:null}get valid(){return this.control?this.control.valid:null}get invalid(){return this.control?this.control.invalid:null}get pending(){return this.control?this.control.pending:null}get disabled(){return this.control?this.control.disabled:null}get enabled(){return this.control?this.control.enabled:null}get errors(){return this.control?this.control.errors:null}get pristine(){return this.control?this.control.pristine:null}get dirty(){return this.control?this.control.dirty:null}get touched(){return this.control?this.control.touched:null}get status(){return this.control?this.control.status:null}get untouched(){return this.control?this.control.untouched:null}get statusChanges(){return this.control?this.control.statusChanges:null}get valueChanges(){return this.control?this.control.valueChanges:null}get path(){return null}reset(e){this.control&&this.control.reset(e)}hasError(e,t){return!!this.control&&this.control.hasError(e,t)}getError(e,t){return this.control?this.control.getError(e,t):null}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275dir=bt({type:e}),e})(),mC=(()=>{class e extends _C{get formDirective(){return null}get path(){return null}}return e.\u0275fac=function(t){return gC(t||e)},e.\u0275dir=bt({type:e,features:[ma]}),e})();const gC=pr(mC);function vC(){throw new Error("unimplemented")}class yC extends _C{constructor(){super(...arguments),this._parent=null,this.name=null,this.valueAccessor=null,this._rawValidators=[],this._rawAsyncValidators=[]}get validator(){return vC()}get asyncValidator(){return vC()}}let bC=(()=>{class e extends class{constructor(e){this._cd=e}get ngClassUntouched(){return!!this._cd.control&&this._cd.control.untouched}get ngClassTouched(){return!!this._cd.control&&this._cd.control.touched}get ngClassPristine(){return!!this._cd.control&&this._cd.control.pristine}get ngClassDirty(){return!!this._cd.control&&this._cd.control.dirty}get ngClassValid(){return!!this._cd.control&&this._cd.control.valid}get ngClassInvalid(){return!!this._cd.control&&this._cd.control.invalid}get ngClassPending(){return!!this._cd.control&&this._cd.control.pending}}{constructor(e){super(e)}}return e.\u0275fac=function(t){return new(t||e)(Co(yC,2))},e.\u0275dir=bt({type:e,selectors:[["","formControlName",""],["","ngModel",""],["","formControl",""]],hostVars:14,hostBindings:function(e,t){2&e&&Xo("ng-untouched",t.ngClassUntouched)("ng-touched",t.ngClassTouched)("ng-pristine",t.ngClassPristine)("ng-dirty",t.ngClassDirty)("ng-valid",t.ngClassValid)("ng-invalid",t.ngClassInvalid)("ng-pending",t.ngClassPending)},features:[ma]}),e})();function wC(e){return null==e||0===e.length}function CC(e){return null!=e&&"number"==typeof e.length}const SC=new Be("NgValidators"),kC=new Be("NgAsyncValidators"),xC=/^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;class EC{static min(e){return t=>{if(wC(t.value)||wC(e))return null;const i=parseFloat(t.value);return!isNaN(i)&&i{if(wC(t.value)||wC(e))return null;const i=parseFloat(t.value);return!isNaN(i)&&i>e?{max:{max:e,actual:t.value}}:null}}static required(e){return wC(e.value)?{required:!0}:null}static requiredTrue(e){return!0===e.value?null:{required:!0}}static email(e){return wC(e.value)||xC.test(e.value)?null:{email:!0}}static minLength(e){return t=>wC(t.value)||!CC(t.value)?null:t.value.lengthCC(t.value)&&t.value.length>e?{maxlength:{requiredLength:e,actualLength:t.value.length}}:null}static pattern(e){if(!e)return EC.nullValidator;let t,i;return"string"==typeof e?(i="","^"!==e.charAt(0)&&(i+="^"),i+=e,"$"!==e.charAt(e.length-1)&&(i+="$"),t=new RegExp(i)):(i=e.toString(),t=e),e=>{if(wC(e.value))return null;const r=e.value;return t.test(r)?null:{pattern:{requiredPattern:i,actualValue:r}}}}static nullValidator(e){return null}static compose(e){if(!e)return null;const t=e.filter(AC);return 0==t.length?null:function(e){return TC(RC(e,t))}}static composeAsync(e){if(!e)return null;const t=e.filter(AC);return 0==t.length?null:function(e){return function(...e){if(1===e.length){const t=e[0];if(l(t))return lC(t,null);if(c(t)&&Object.getPrototypeOf(t)===Object.prototype){const e=Object.keys(t);return lC(e.map(e=>t[e]),e)}}if("function"==typeof e[e.length-1]){const t=e.pop();return lC(e=1===e.length&&l(e[0])?e[0]:e,null).pipe(M(e=>t(...e)))}return lC(e,null)}(RC(e,t).map(OC)).pipe(M(TC))}}}function AC(e){return null!=e}function OC(e){const t=Po(e)?B(e):e;if(!Do(t))throw new Error("Expected validator to return Promise or Observable.");return t}function TC(e){let t={};return e.forEach(e=>{t=null!=e?Object.assign(Object.assign({},t),e):t}),0===Object.keys(t).length?null:t}function RC(e,t){return t.map(t=>t(e))}function LC(e){return e.map(e=>function(e){return!e.validate}(e)?e:t=>e.validate(t))}const PC={provide:cC,useExisting:ke(()=>DC),multi:!0};let DC=(()=>{class e{constructor(e,t){this._renderer=e,this._elementRef=t,this.onChange=e=>{},this.onTouched=()=>{}}writeValue(e){this._renderer.setProperty(this._elementRef.nativeElement,"value",null==e?"":e)}registerOnChange(e){this.onChange=t=>{e(""==t?null:parseFloat(t))}}registerOnTouched(e){this.onTouched=e}setDisabledState(e){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",e)}}return e.\u0275fac=function(t){return new(t||e)(Co(Fa),Co(Pa))},e.\u0275dir=bt({type:e,selectors:[["input","type","number","formControlName",""],["input","type","number","formControl",""],["input","type","number","ngModel",""]],hostBindings:function(e,t){1&e&&Io("input",(function(e){return t.onChange(e.target.value)}))("blur",(function(){return t.onTouched()}))},features:[Oa([PC])]}),e})();const IC={provide:cC,useExisting:ke(()=>FC),multi:!0};let MC=(()=>{class e{constructor(){this._accessors=[]}add(e,t){this._accessors.push([e,t])}remove(e){for(let t=this._accessors.length-1;t>=0;--t)if(this._accessors[t][1]===e)return void this._accessors.splice(t,1)}select(e){this._accessors.forEach(t=>{this._isSameGroup(t,e)&&t[1]!==e&&t[1].fireUncheck(e.value)})}_isSameGroup(e,t){return!!e[0].control&&e[0]._parent===t._control._parent&&e[1].name===t.name}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),FC=(()=>{class e{constructor(e,t,i,r){this._renderer=e,this._elementRef=t,this._registry=i,this._injector=r,this.onChange=()=>{},this.onTouched=()=>{}}ngOnInit(){this._control=this._injector.get(yC),this._checkName(),this._registry.add(this._control,this)}ngOnDestroy(){this._registry.remove(this)}writeValue(e){this._state=e===this.value,this._renderer.setProperty(this._elementRef.nativeElement,"checked",this._state)}registerOnChange(e){this._fn=e,this.onChange=()=>{e(this.value),this._registry.select(this)}}fireUncheck(e){this.writeValue(e)}registerOnTouched(e){this.onTouched=e}setDisabledState(e){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",e)}_checkName(){this.name&&this.formControlName&&this.name!==this.formControlName&&this._throwNameError(),!this.name&&this.formControlName&&(this.name=this.formControlName)}_throwNameError(){throw new Error('\n If you define both a name and a formControlName attribute on your radio button, their values\n must match. Ex: \n ')}}return e.\u0275fac=function(t){return new(t||e)(Co(Fa),Co(Pa),Co(MC),Co(ao))},e.\u0275dir=bt({type:e,selectors:[["input","type","radio","formControlName",""],["input","type","radio","formControl",""],["input","type","radio","ngModel",""]],hostBindings:function(e,t){1&e&&Io("change",(function(){return t.onChange()}))("blur",(function(){return t.onTouched()}))},inputs:{name:"name",formControlName:"formControlName",value:"value"},features:[Oa([IC])]}),e})();const NC={provide:cC,useExisting:ke(()=>jC),multi:!0};let jC=(()=>{class e{constructor(e,t){this._renderer=e,this._elementRef=t,this.onChange=e=>{},this.onTouched=()=>{}}writeValue(e){this._renderer.setProperty(this._elementRef.nativeElement,"value",parseFloat(e))}registerOnChange(e){this.onChange=t=>{e(""==t?null:parseFloat(t))}}registerOnTouched(e){this.onTouched=e}setDisabledState(e){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",e)}}return e.\u0275fac=function(t){return new(t||e)(Co(Fa),Co(Pa))},e.\u0275dir=bt({type:e,selectors:[["input","type","range","formControlName",""],["input","type","range","formControl",""],["input","type","range","ngModel",""]],hostBindings:function(e,t){1&e&&Io("change",(function(e){return t.onChange(e.target.value)}))("input",(function(e){return t.onChange(e.target.value)}))("blur",(function(){return t.onTouched()}))},features:[Oa([NC])]}),e})();const BC='\n
\n \n
\n\n In your class:\n\n this.myGroup = new FormGroup({\n firstName: new FormControl()\n });',HC='\n
\n
\n \n
\n
\n\n In your class:\n\n this.myGroup = new FormGroup({\n person: new FormGroup({ firstName: new FormControl() })\n });',UC='\n
\n
\n \n
\n
',VC={provide:cC,useExisting:ke(()=>qC),multi:!0};let qC=(()=>{class e{constructor(e,t){this._renderer=e,this._elementRef=t,this._optionMap=new Map,this._idCounter=0,this.onChange=e=>{},this.onTouched=()=>{},this._compareWith=Object.is}set compareWith(e){if("function"!=typeof e)throw new Error("compareWith must be a function, but received "+JSON.stringify(e));this._compareWith=e}writeValue(e){this.value=e;const t=this._getOptionId(e);null==t&&this._renderer.setProperty(this._elementRef.nativeElement,"selectedIndex",-1);const i=function(e,t){return null==e?""+t:(t&&"object"==typeof t&&(t="Object"),`${e}: ${t}`.slice(0,50))}(t,e);this._renderer.setProperty(this._elementRef.nativeElement,"value",i)}registerOnChange(e){this.onChange=t=>{this.value=this._getOptionValue(t),e(this.value)}}registerOnTouched(e){this.onTouched=e}setDisabledState(e){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",e)}_registerOption(){return(this._idCounter++).toString()}_getOptionId(e){for(const t of Array.from(this._optionMap.keys()))if(this._compareWith(this._optionMap.get(t),e))return t;return null}_getOptionValue(e){const t=function(e){return e.split(":")[0]}(e);return this._optionMap.has(t)?this._optionMap.get(t):e}}return e.\u0275fac=function(t){return new(t||e)(Co(Fa),Co(Pa))},e.\u0275dir=bt({type:e,selectors:[["select","formControlName","",3,"multiple",""],["select","formControl","",3,"multiple",""],["select","ngModel","",3,"multiple",""]],hostBindings:function(e,t){1&e&&Io("change",(function(e){return t.onChange(e.target.value)}))("blur",(function(){return t.onTouched()}))},inputs:{compareWith:"compareWith"},features:[Oa([VC])]}),e})();const zC={provide:cC,useExisting:ke(()=>WC),multi:!0};let WC=(()=>{class e{constructor(e,t){this._renderer=e,this._elementRef=t,this._optionMap=new Map,this._idCounter=0,this.onChange=e=>{},this.onTouched=()=>{},this._compareWith=Object.is}set compareWith(e){if("function"!=typeof e)throw new Error("compareWith must be a function, but received "+JSON.stringify(e));this._compareWith=e}writeValue(e){let t;if(this.value=e,Array.isArray(e)){const i=e.map(e=>this._getOptionId(e));t=(e,t)=>{e._setSelected(i.indexOf(t.toString())>-1)}}else t=(e,t)=>{e._setSelected(!1)};this._optionMap.forEach(t)}registerOnChange(e){this.onChange=t=>{const i=[];if(void 0!==t.selectedOptions){const e=t.selectedOptions;for(let t=0;t{e._pendingValue=i,e._pendingChange=!0,e._pendingDirty=!0,"change"===e.updateOn&&GC(e,t)})}(e,t),function(e,t){e.registerOnChange((e,i)=>{t.valueAccessor.writeValue(e),i&&t.viewToModelUpdate(e)})}(e,t),function(e,t){t.valueAccessor.registerOnTouched(()=>{e._pendingTouched=!0,"blur"===e.updateOn&&e._pendingChange&&GC(e,t),"submit"!==e.updateOn&&e.markAsTouched()})}(e,t),t.valueAccessor.setDisabledState&&e.registerOnDisabledChange(e=>{t.valueAccessor.setDisabledState(e)}),t._rawValidators.forEach(t=>{t.registerOnValidatorChange&&t.registerOnValidatorChange(()=>e.updateValueAndValidity())}),t._rawAsyncValidators.forEach(t=>{t.registerOnValidatorChange&&t.registerOnValidatorChange(()=>e.updateValueAndValidity())})}function GC(e,t){e._pendingDirty&&e.markAsDirty(),e.setValue(e._pendingValue,{emitModelToViewChange:!1}),t.viewToModelUpdate(e._pendingValue),e._pendingChange=!1}function ZC(e,t){null==e&&XC(t,"Cannot find control with"),e.validator=EC.compose([e.validator,t.validator]),e.asyncValidator=EC.composeAsync([e.asyncValidator,t.asyncValidator])}function YC(e){return XC(e,"There is no FormControl instance attached to form control element with")}function XC(e,t){let i;throw i=e.path.length>1?`path: '${e.path.join(" -> ")}'`:e.path[0]?`name: '${e.path}'`:"unspecified name attribute",new Error(`${t} ${i}`)}function QC(e){return null!=e?EC.compose(LC(e)):null}function JC(e){return null!=e?EC.composeAsync(LC(e)):null}const eS=[uC,jC,DC,qC,WC,FC];function tS(e,t){e._syncPendingControls(),t.forEach(e=>{const t=e.control;"submit"===t.updateOn&&t._pendingChange&&(e.viewToModelUpdate(t._pendingValue),t._pendingChange=!1)})}function iS(e,t){const i=e.indexOf(t);i>-1&&e.splice(i,1)}function rS(e){const t=sS(e)?e.validators:e;return Array.isArray(t)?QC(t):t||null}function nS(e,t){const i=sS(t)?t.asyncValidators:e;return Array.isArray(i)?JC(i):i||null}function sS(e){return null!=e&&!Array.isArray(e)&&"object"==typeof e}class oS{constructor(e,t){this.validator=e,this.asyncValidator=t,this._onCollectionChange=()=>{},this.pristine=!0,this.touched=!1,this._onDisabledChange=[]}get parent(){return this._parent}get valid(){return"VALID"===this.status}get invalid(){return"INVALID"===this.status}get pending(){return"PENDING"==this.status}get disabled(){return"DISABLED"===this.status}get enabled(){return"DISABLED"!==this.status}get dirty(){return!this.pristine}get untouched(){return!this.touched}get updateOn(){return this._updateOn?this._updateOn:this.parent?this.parent.updateOn:"change"}setValidators(e){this.validator=rS(e)}setAsyncValidators(e){this.asyncValidator=nS(e)}clearValidators(){this.validator=null}clearAsyncValidators(){this.asyncValidator=null}markAsTouched(e={}){this.touched=!0,this._parent&&!e.onlySelf&&this._parent.markAsTouched(e)}markAllAsTouched(){this.markAsTouched({onlySelf:!0}),this._forEachChild(e=>e.markAllAsTouched())}markAsUntouched(e={}){this.touched=!1,this._pendingTouched=!1,this._forEachChild(e=>{e.markAsUntouched({onlySelf:!0})}),this._parent&&!e.onlySelf&&this._parent._updateTouched(e)}markAsDirty(e={}){this.pristine=!1,this._parent&&!e.onlySelf&&this._parent.markAsDirty(e)}markAsPristine(e={}){this.pristine=!0,this._pendingDirty=!1,this._forEachChild(e=>{e.markAsPristine({onlySelf:!0})}),this._parent&&!e.onlySelf&&this._parent._updatePristine(e)}markAsPending(e={}){this.status="PENDING",!1!==e.emitEvent&&this.statusChanges.emit(this.status),this._parent&&!e.onlySelf&&this._parent.markAsPending(e)}disable(e={}){const t=this._parentMarkedDirty(e.onlySelf);this.status="DISABLED",this.errors=null,this._forEachChild(t=>{t.disable(Object.assign(Object.assign({},e),{onlySelf:!0}))}),this._updateValue(),!1!==e.emitEvent&&(this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._updateAncestors(Object.assign(Object.assign({},e),{skipPristineCheck:t})),this._onDisabledChange.forEach(e=>e(!0))}enable(e={}){const t=this._parentMarkedDirty(e.onlySelf);this.status="VALID",this._forEachChild(t=>{t.enable(Object.assign(Object.assign({},e),{onlySelf:!0}))}),this.updateValueAndValidity({onlySelf:!0,emitEvent:e.emitEvent}),this._updateAncestors(Object.assign(Object.assign({},e),{skipPristineCheck:t})),this._onDisabledChange.forEach(e=>e(!1))}_updateAncestors(e){this._parent&&!e.onlySelf&&(this._parent.updateValueAndValidity(e),e.skipPristineCheck||this._parent._updatePristine(),this._parent._updateTouched())}setParent(e){this._parent=e}updateValueAndValidity(e={}){this._setInitialStatus(),this._updateValue(),this.enabled&&(this._cancelExistingSubscription(),this.errors=this._runValidator(),this.status=this._calculateStatus(),"VALID"!==this.status&&"PENDING"!==this.status||this._runAsyncValidator(e.emitEvent)),!1!==e.emitEvent&&(this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._parent&&!e.onlySelf&&this._parent.updateValueAndValidity(e)}_updateTreeValidity(e={emitEvent:!0}){this._forEachChild(t=>t._updateTreeValidity(e)),this.updateValueAndValidity({onlySelf:!0,emitEvent:e.emitEvent})}_setInitialStatus(){this.status=this._allControlsDisabled()?"DISABLED":"VALID"}_runValidator(){return this.validator?this.validator(this):null}_runAsyncValidator(e){if(this.asyncValidator){this.status="PENDING";const t=OC(this.asyncValidator(this));this._asyncValidationSubscription=t.subscribe(t=>this.setErrors(t,{emitEvent:e}))}}_cancelExistingSubscription(){this._asyncValidationSubscription&&this._asyncValidationSubscription.unsubscribe()}setErrors(e,t={}){this.errors=e,this._updateControlsErrors(!1!==t.emitEvent)}get(e){return function(e,t,i){if(null==t)return null;if(Array.isArray(t)||(t=t.split(".")),Array.isArray(t)&&0===t.length)return null;let r=e;return t.forEach(e=>{r=r instanceof lS?r.controls.hasOwnProperty(e)?r.controls[e]:null:r instanceof cS&&r.at(e)||null}),r}(this,e)}getError(e,t){const i=t?this.get(t):this;return i&&i.errors?i.errors[e]:null}hasError(e,t){return!!this.getError(e,t)}get root(){let e=this;for(;e._parent;)e=e._parent;return e}_updateControlsErrors(e){this.status=this._calculateStatus(),e&&this.statusChanges.emit(this.status),this._parent&&this._parent._updateControlsErrors(e)}_initObservables(){this.valueChanges=new El,this.statusChanges=new El}_calculateStatus(){return this._allControlsDisabled()?"DISABLED":this.errors?"INVALID":this._anyControlsHaveStatus("PENDING")?"PENDING":this._anyControlsHaveStatus("INVALID")?"INVALID":"VALID"}_anyControlsHaveStatus(e){return this._anyControls(t=>t.status===e)}_anyControlsDirty(){return this._anyControls(e=>e.dirty)}_anyControlsTouched(){return this._anyControls(e=>e.touched)}_updatePristine(e={}){this.pristine=!this._anyControlsDirty(),this._parent&&!e.onlySelf&&this._parent._updatePristine(e)}_updateTouched(e={}){this.touched=this._anyControlsTouched(),this._parent&&!e.onlySelf&&this._parent._updateTouched(e)}_isBoxedValue(e){return"object"==typeof e&&null!==e&&2===Object.keys(e).length&&"value"in e&&"disabled"in e}_registerOnCollectionChange(e){this._onCollectionChange=e}_setUpdateStrategy(e){sS(e)&&null!=e.updateOn&&(this._updateOn=e.updateOn)}_parentMarkedDirty(e){return!e&&this._parent&&this._parent.dirty&&!this._parent._anyControlsDirty()}}class aS extends oS{constructor(e=null,t,i){super(rS(t),nS(i,t)),this._onChange=[],this._applyFormState(e),this._setUpdateStrategy(t),this.updateValueAndValidity({onlySelf:!0,emitEvent:!1}),this._initObservables()}setValue(e,t={}){this.value=this._pendingValue=e,this._onChange.length&&!1!==t.emitModelToViewChange&&this._onChange.forEach(e=>e(this.value,!1!==t.emitViewToModelChange)),this.updateValueAndValidity(t)}patchValue(e,t={}){this.setValue(e,t)}reset(e=null,t={}){this._applyFormState(e),this.markAsPristine(t),this.markAsUntouched(t),this.setValue(this.value,t),this._pendingChange=!1}_updateValue(){}_anyControls(e){return!1}_allControlsDisabled(){return this.disabled}registerOnChange(e){this._onChange.push(e)}_clearChangeFns(){this._onChange=[],this._onDisabledChange=[],this._onCollectionChange=()=>{}}registerOnDisabledChange(e){this._onDisabledChange.push(e)}_forEachChild(e){}_syncPendingControls(){return!("submit"!==this.updateOn||(this._pendingDirty&&this.markAsDirty(),this._pendingTouched&&this.markAsTouched(),!this._pendingChange)||(this.setValue(this._pendingValue,{onlySelf:!0,emitModelToViewChange:!1}),0))}_applyFormState(e){this._isBoxedValue(e)?(this.value=this._pendingValue=e.value,e.disabled?this.disable({onlySelf:!0,emitEvent:!1}):this.enable({onlySelf:!0,emitEvent:!1})):this.value=this._pendingValue=e}}class lS extends oS{constructor(e,t,i){super(rS(t),nS(i,t)),this.controls=e,this._initObservables(),this._setUpdateStrategy(t),this._setUpControls(),this.updateValueAndValidity({onlySelf:!0,emitEvent:!1})}registerControl(e,t){return this.controls[e]?this.controls[e]:(this.controls[e]=t,t.setParent(this),t._registerOnCollectionChange(this._onCollectionChange),t)}addControl(e,t){this.registerControl(e,t),this.updateValueAndValidity(),this._onCollectionChange()}removeControl(e){this.controls[e]&&this.controls[e]._registerOnCollectionChange(()=>{}),delete this.controls[e],this.updateValueAndValidity(),this._onCollectionChange()}setControl(e,t){this.controls[e]&&this.controls[e]._registerOnCollectionChange(()=>{}),delete this.controls[e],t&&this.registerControl(e,t),this.updateValueAndValidity(),this._onCollectionChange()}contains(e){return this.controls.hasOwnProperty(e)&&this.controls[e].enabled}setValue(e,t={}){this._checkAllValuesPresent(e),Object.keys(e).forEach(i=>{this._throwIfControlMissing(i),this.controls[i].setValue(e[i],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)}patchValue(e,t={}){Object.keys(e).forEach(i=>{this.controls[i]&&this.controls[i].patchValue(e[i],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)}reset(e={},t={}){this._forEachChild((i,r)=>{i.reset(e[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this._updatePristine(t),this._updateTouched(t),this.updateValueAndValidity(t)}getRawValue(){return this._reduceChildren({},(e,t,i)=>(e[i]=t instanceof aS?t.value:t.getRawValue(),e))}_syncPendingControls(){let e=this._reduceChildren(!1,(e,t)=>!!t._syncPendingControls()||e);return e&&this.updateValueAndValidity({onlySelf:!0}),e}_throwIfControlMissing(e){if(!Object.keys(this.controls).length)throw new Error("\n There are no form controls registered with this group yet. If you're using ngModel,\n you may want to check next tick (e.g. use setTimeout).\n ");if(!this.controls[e])throw new Error(`Cannot find form control with name: ${e}.`)}_forEachChild(e){Object.keys(this.controls).forEach(t=>e(this.controls[t],t))}_setUpControls(){this._forEachChild(e=>{e.setParent(this),e._registerOnCollectionChange(this._onCollectionChange)})}_updateValue(){this.value=this._reduceValue()}_anyControls(e){for(const t of Object.keys(this.controls)){const i=this.controls[t];if(this.contains(t)&&e(i))return!0}return!1}_reduceValue(){return this._reduceChildren({},(e,t,i)=>((t.enabled||this.disabled)&&(e[i]=t.value),e))}_reduceChildren(e,t){let i=e;return this._forEachChild((e,r)=>{i=t(i,e,r)}),i}_allControlsDisabled(){for(const e of Object.keys(this.controls))if(this.controls[e].enabled)return!1;return Object.keys(this.controls).length>0||this.disabled}_checkAllValuesPresent(e){this._forEachChild((t,i)=>{if(void 0===e[i])throw new Error(`Must supply a value for form control with name: '${i}'.`)})}}class cS extends oS{constructor(e,t,i){super(rS(t),nS(i,t)),this.controls=e,this._initObservables(),this._setUpdateStrategy(t),this._setUpControls(),this.updateValueAndValidity({onlySelf:!0,emitEvent:!1})}at(e){return this.controls[e]}push(e){this.controls.push(e),this._registerControl(e),this.updateValueAndValidity(),this._onCollectionChange()}insert(e,t){this.controls.splice(e,0,t),this._registerControl(t),this.updateValueAndValidity()}removeAt(e){this.controls[e]&&this.controls[e]._registerOnCollectionChange(()=>{}),this.controls.splice(e,1),this.updateValueAndValidity()}setControl(e,t){this.controls[e]&&this.controls[e]._registerOnCollectionChange(()=>{}),this.controls.splice(e,1),t&&(this.controls.splice(e,0,t),this._registerControl(t)),this.updateValueAndValidity(),this._onCollectionChange()}get length(){return this.controls.length}setValue(e,t={}){this._checkAllValuesPresent(e),e.forEach((e,i)=>{this._throwIfControlMissing(i),this.at(i).setValue(e,{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)}patchValue(e,t={}){e.forEach((e,i)=>{this.at(i)&&this.at(i).patchValue(e,{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)}reset(e=[],t={}){this._forEachChild((i,r)=>{i.reset(e[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this._updatePristine(t),this._updateTouched(t),this.updateValueAndValidity(t)}getRawValue(){return this.controls.map(e=>e instanceof aS?e.value:e.getRawValue())}clear(){this.controls.length<1||(this._forEachChild(e=>e._registerOnCollectionChange(()=>{})),this.controls.splice(0),this.updateValueAndValidity())}_syncPendingControls(){let e=this.controls.reduce((e,t)=>!!t._syncPendingControls()||e,!1);return e&&this.updateValueAndValidity({onlySelf:!0}),e}_throwIfControlMissing(e){if(!this.controls.length)throw new Error("\n There are no form controls registered with this array yet. If you're using ngModel,\n you may want to check next tick (e.g. use setTimeout).\n ");if(!this.at(e))throw new Error("Cannot find form control at index "+e)}_forEachChild(e){this.controls.forEach((t,i)=>{e(t,i)})}_updateValue(){this.value=this.controls.filter(e=>e.enabled||this.disabled).map(e=>e.value)}_anyControls(e){return this.controls.some(t=>t.enabled&&e(t))}_setUpControls(){this._forEachChild(e=>this._registerControl(e))}_checkAllValuesPresent(e){this._forEachChild((t,i)=>{if(void 0===e[i])throw new Error(`Must supply a value for form control at index: ${i}.`)})}_allControlsDisabled(){for(const e of this.controls)if(e.enabled)return!1;return this.controls.length>0||this.disabled}_registerControl(e){e.setParent(this),e._registerOnCollectionChange(this._onCollectionChange)}}const hS={provide:mC,useExisting:ke(()=>dS)},uS=(()=>Promise.resolve(null))();let dS=(()=>{class e extends mC{constructor(e,t){super(),this.submitted=!1,this._directives=[],this.ngSubmit=new El,this.form=new lS({},QC(e),JC(t))}ngAfterViewInit(){this._setUpdateStrategy()}get formDirective(){return this}get control(){return this.form}get path(){return[]}get controls(){return this.form.controls}addControl(e){uS.then(()=>{const t=this._findContainer(e.path);e.control=t.registerControl(e.name,e.control),KC(e.control,e),e.control.updateValueAndValidity({emitEvent:!1}),this._directives.push(e)})}getControl(e){return this.form.get(e.path)}removeControl(e){uS.then(()=>{const t=this._findContainer(e.path);t&&t.removeControl(e.name),iS(this._directives,e)})}addFormGroup(e){uS.then(()=>{const t=this._findContainer(e.path),i=new lS({});ZC(i,e),t.registerControl(e.name,i),i.updateValueAndValidity({emitEvent:!1})})}removeFormGroup(e){uS.then(()=>{const t=this._findContainer(e.path);t&&t.removeControl(e.name)})}getFormGroup(e){return this.form.get(e.path)}updateModel(e,t){uS.then(()=>{this.form.get(e.path).setValue(t)})}setValue(e){this.control.setValue(e)}onSubmit(e){return this.submitted=!0,tS(this.form,this._directives),this.ngSubmit.emit(e),!1}onReset(){this.resetForm()}resetForm(e){this.form.reset(e),this.submitted=!1}_setUpdateStrategy(){this.options&&null!=this.options.updateOn&&(this.form._updateOn=this.options.updateOn)}_findContainer(e){return e.pop(),e.length?this.form.get(e):this.form}}return e.\u0275fac=function(t){return new(t||e)(Co(SC,10),Co(kC,10))},e.\u0275dir=bt({type:e,selectors:[["form",3,"ngNoForm","",3,"formGroup",""],["ng-form"],["","ngForm",""]],hostBindings:function(e,t){1&e&&Io("submit",(function(e){return t.onSubmit(e)}))("reset",(function(){return t.onReset()}))},inputs:{options:["ngFormOptions","options"]},outputs:{ngSubmit:"ngSubmit"},exportAs:["ngForm"],features:[Oa([hS]),ma]}),e})(),fS=(()=>{class e extends mC{ngOnInit(){this._checkParentType(),this.formDirective.addFormGroup(this)}ngOnDestroy(){this.formDirective&&this.formDirective.removeFormGroup(this)}get control(){return this.formDirective.getFormGroup(this)}get path(){return $C(null==this.name?this.name:this.name.toString(),this._parent)}get formDirective(){return this._parent?this._parent.formDirective:null}get validator(){return QC(this._validators)}get asyncValidator(){return JC(this._asyncValidators)}_checkParentType(){}}return e.\u0275fac=function(t){return pS(t||e)},e.\u0275dir=bt({type:e,features:[ma]}),e})();const pS=pr(fS);class _S{static modelParentException(){throw new Error(`\n ngModel cannot be used to register form controls with a parent formGroup directive. Try using\n formGroup's partner directive "formControlName" instead. Example:\n\n ${BC}\n\n Or, if you'd like to avoid registering this form control, indicate that it's standalone in ngModelOptions:\n\n Example:\n\n \n
\n \n \n
\n `)}static formGroupNameException(){throw new Error(`\n ngModel cannot be used to register form controls with a parent formGroupName or formArrayName directive.\n\n Option 1: Use formControlName instead of ngModel (reactive strategy):\n\n ${HC}\n\n Option 2: Update ngModel's parent be ngModelGroup (template-driven strategy):\n\n ${UC}`)}static missingNameException(){throw new Error('If ngModel is used within a form tag, either the name attribute must be set or the form\n control must be defined as \'standalone\' in ngModelOptions.\n\n Example 1: \n Example 2: ')}static modelGroupParentException(){throw new Error(`\n ngModelGroup cannot be used with a parent formGroup directive.\n\n Option 1: Use formGroupName instead of ngModelGroup (reactive strategy):\n\n ${HC}\n\n Option 2: Use a regular form tag instead of the formGroup directive (template-driven strategy):\n\n ${UC}`)}}const mS={provide:mC,useExisting:ke(()=>gS)};let gS=(()=>{class e extends fS{constructor(e,t,i){super(),this._parent=e,this._validators=t,this._asyncValidators=i}_checkParentType(){this._parent instanceof e||this._parent instanceof dS||_S.modelGroupParentException()}}return e.\u0275fac=function(t){return new(t||e)(Co(mC,5),Co(SC,10),Co(kC,10))},e.\u0275dir=bt({type:e,selectors:[["","ngModelGroup",""]],inputs:{name:["ngModelGroup","name"]},exportAs:["ngModelGroup"],features:[Oa([mS]),ma]}),e})();const vS={provide:yC,useExisting:ke(()=>bS)},yS=(()=>Promise.resolve(null))();let bS=(()=>{class e extends yC{constructor(e,t,i,r){super(),this.control=new aS,this._registered=!1,this.update=new El,this._parent=e,this._rawValidators=t||[],this._rawAsyncValidators=i||[],this.valueAccessor=function(e,t){if(!t)return null;Array.isArray(t)||XC(e,"Value accessor was not provided as an array for form control with");let i=void 0,r=void 0,n=void 0;return t.forEach(t=>{var s;t.constructor===pC?i=t:(s=t,eS.some(e=>s.constructor===e)?(r&&XC(e,"More than one built-in value accessor matches form control with"),r=t):(n&&XC(e,"More than one custom value accessor matches form control with"),n=t))}),n||r||i||(XC(e,"No valid value accessor for form control with"),null)}(this,r)}ngOnChanges(e){this._checkForErrors(),this._registered||this._setUpControl(),"isDisabled"in e&&this._updateDisabled(e),function(e,t){if(!e.hasOwnProperty("model"))return!1;const i=e.model;return!!i.isFirstChange()||!Object.is(t,i.currentValue)}(e,this.viewModel)&&(this._updateValue(this.model),this.viewModel=this.model)}ngOnDestroy(){this.formDirective&&this.formDirective.removeControl(this)}get path(){return this._parent?$C(this.name,this._parent):[this.name]}get formDirective(){return this._parent?this._parent.formDirective:null}get validator(){return QC(this._rawValidators)}get asyncValidator(){return JC(this._rawAsyncValidators)}viewToModelUpdate(e){this.viewModel=e,this.update.emit(e)}_setUpControl(){this._setUpdateStrategy(),this._isStandalone()?this._setUpStandalone():this.formDirective.addControl(this),this._registered=!0}_setUpdateStrategy(){this.options&&null!=this.options.updateOn&&(this.control._updateOn=this.options.updateOn)}_isStandalone(){return!this._parent||!(!this.options||!this.options.standalone)}_setUpStandalone(){KC(this.control,this),this.control.updateValueAndValidity({emitEvent:!1})}_checkForErrors(){this._isStandalone()||this._checkParentType(),this._checkName()}_checkParentType(){!(this._parent instanceof gS)&&this._parent instanceof fS?_S.formGroupNameException():this._parent instanceof gS||this._parent instanceof dS||_S.modelParentException()}_checkName(){this.options&&this.options.name&&(this.name=this.options.name),this._isStandalone()||this.name||_S.missingNameException()}_updateValue(e){yS.then(()=>{this.control.setValue(e,{emitViewToModelChange:!1})})}_updateDisabled(e){const t=e.isDisabled.currentValue,i=""===t||t&&"false"!==t;yS.then(()=>{i&&!this.control.disabled?this.control.disable():!i&&this.control.disabled&&this.control.enable()})}}return e.\u0275fac=function(t){return new(t||e)(Co(mC,9),Co(SC,10),Co(kC,10),Co(cC,10))},e.\u0275dir=bt({type:e,selectors:[["","ngModel","",3,"formControlName","",3,"formControl",""]],inputs:{name:"name",isDisabled:["disabled","isDisabled"],model:["ngModel","model"],options:["ngModelOptions","options"]},outputs:{update:"ngModelChange"},exportAs:["ngModel"],features:[Oa([vS]),ma,Dt]}),e})();const wS=new Be("NgModelWithFormControlWarning"),CS={provide:mC,useExisting:ke(()=>SS)};let SS=(()=>{class e extends mC{constructor(e,t){super(),this._validators=e,this._asyncValidators=t,this.submitted=!1,this.directives=[],this.form=null,this.ngSubmit=new El}ngOnChanges(e){this._checkFormPresent(),e.hasOwnProperty("form")&&(this._updateValidators(),this._updateDomValue(),this._updateRegistrations())}get formDirective(){return this}get control(){return this.form}get path(){return[]}addControl(e){const t=this.form.get(e.path);return KC(t,e),t.updateValueAndValidity({emitEvent:!1}),this.directives.push(e),t}getControl(e){return this.form.get(e.path)}removeControl(e){iS(this.directives,e)}addFormGroup(e){const t=this.form.get(e.path);ZC(t,e),t.updateValueAndValidity({emitEvent:!1})}removeFormGroup(e){}getFormGroup(e){return this.form.get(e.path)}addFormArray(e){const t=this.form.get(e.path);ZC(t,e),t.updateValueAndValidity({emitEvent:!1})}removeFormArray(e){}getFormArray(e){return this.form.get(e.path)}updateModel(e,t){this.form.get(e.path).setValue(t)}onSubmit(e){return this.submitted=!0,tS(this.form,this.directives),this.ngSubmit.emit(e),!1}onReset(){this.resetForm()}resetForm(e){this.form.reset(e),this.submitted=!1}_updateDomValue(){this.directives.forEach(e=>{const t=this.form.get(e.path);e.control!==t&&(function(e,t){t.valueAccessor.registerOnChange(()=>YC(t)),t.valueAccessor.registerOnTouched(()=>YC(t)),t._rawValidators.forEach(e=>{e.registerOnValidatorChange&&e.registerOnValidatorChange(null)}),t._rawAsyncValidators.forEach(e=>{e.registerOnValidatorChange&&e.registerOnValidatorChange(null)}),e&&e._clearChangeFns()}(e.control,e),t&&KC(t,e),e.control=t)}),this.form._updateTreeValidity({emitEvent:!1})}_updateRegistrations(){this.form._registerOnCollectionChange(()=>this._updateDomValue()),this._oldForm&&this._oldForm._registerOnCollectionChange(()=>{}),this._oldForm=this.form}_updateValidators(){const e=QC(this._validators);this.form.validator=EC.compose([this.form.validator,e]);const t=JC(this._asyncValidators);this.form.asyncValidator=EC.composeAsync([this.form.asyncValidator,t])}_checkFormPresent(){this.form||class{static controlParentException(){throw new Error("formControlName must be used with a parent formGroup directive. You'll want to add a formGroup\n directive and pass it an existing FormGroup instance (you can create one in your class).\n\n Example:\n\n "+BC)}static ngModelGroupException(){throw new Error(`formControlName cannot be used with an ngModelGroup parent. It is only compatible with parents\n that also have a "form" prefix: formGroupName, formArrayName, or formGroup.\n\n Option 1: Update the parent to be formGroupName (reactive form strategy)\n\n ${HC}\n\n Option 2: Use ngModel instead of formControlName (template-driven strategy)\n\n ${UC}`)}static missingFormException(){throw new Error("formGroup expects a FormGroup instance. Please pass one in.\n\n Example:\n\n "+BC)}static groupParentException(){throw new Error("formGroupName must be used with a parent formGroup directive. You'll want to add a formGroup\n directive and pass it an existing FormGroup instance (you can create one in your class).\n\n Example:\n\n "+HC)}static arrayParentException(){throw new Error('formArrayName must be used with a parent formGroup directive. You\'ll want to add a formGroup\n directive and pass it an existing FormGroup instance (you can create one in your class).\n\n Example:\n\n \n
\n
\n
\n \n
\n
\n
\n\n In your class:\n\n this.cityArray = new FormArray([new FormControl(\'SF\')]);\n this.myGroup = new FormGroup({\n cities: this.cityArray\n });')}static disabledAttrWarning(){console.warn("\n It looks like you're using the disabled attribute with a reactive form directive. If you set disabled to true\n when you set up this control in your component class, the disabled attribute will actually be set in the DOM for\n you. We recommend using this approach to avoid 'changed after checked' errors.\n\n Example:\n form = new FormGroup({\n first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),\n last: new FormControl('Drew', Validators.required)\n });\n ")}static ngModelWarning(e){console.warn(`\n It looks like you're using ngModel on the same form field as ${e}.\n Support for using the ngModel input property and ngModelChange event with\n reactive form directives has been deprecated in Angular v6 and will be removed\n in a future version of Angular.\n\n For more information on this, see our API docs here:\n https://angular.io/api/forms/${"formControl"===e?"FormControlDirective":"FormControlName"}#use-with-ngmodel\n `)}}.missingFormException()}}return e.\u0275fac=function(t){return new(t||e)(Co(SC,10),Co(kC,10))},e.\u0275dir=bt({type:e,selectors:[["","formGroup",""]],hostBindings:function(e,t){1&e&&Io("submit",(function(e){return t.onSubmit(e)}))("reset",(function(){return t.onReset()}))},inputs:{form:["formGroup","form"]},outputs:{ngSubmit:"ngSubmit"},exportAs:["ngForm"],features:[Oa([CS]),ma,Dt]}),e})(),kS=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)}}),e})(),xS=(()=>{class e{group(e,t=null){const i=this._reduceControls(e);let r=null,n=null,s=void 0;return null!=t&&(function(e){return void 0!==e.asyncValidators||void 0!==e.validators||void 0!==e.updateOn}(t)?(r=null!=t.validators?t.validators:null,n=null!=t.asyncValidators?t.asyncValidators:null,s=null!=t.updateOn?t.updateOn:void 0):(r=null!=t.validator?t.validator:null,n=null!=t.asyncValidator?t.asyncValidator:null)),new lS(i,{asyncValidators:n,updateOn:s,validators:r})}control(e,t,i){return new aS(e,t,i)}array(e,t,i){const r=e.map(e=>this._createControl(e));return new cS(r,t,i)}_reduceControls(e){const t={};return Object.keys(e).forEach(i=>{t[i]=this._createControl(e[i])}),t}_createControl(e){return e instanceof aS||e instanceof lS||e instanceof cS?e:Array.isArray(e)?this.control(e[0],e.length>1?e[1]:null,e.length>2?e[2]:null):this.control(e)}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({token:e,factory:e.\u0275fac}),e})(),ES=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[MC],imports:[kS]}),e})(),AS=(()=>{class e{static withConfig(t){return{ngModule:e,providers:[{provide:wS,useValue:t.warnOnNgModelWithFormControl}]}}}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[xS,MC],imports:[kS]}),e})();const OS=["trigger"],TS=["panel"];function RS(e,t){if(1&e&&(Eo(0,"span",8),ha(1),Ao()),2&e){const e=Bo();gn(1),ua(e.placeholder||"\xa0")}}function LS(e,t){if(1&e&&(Eo(0,"span"),ha(1),Ao()),2&e){const e=Bo(2);gn(1),ua(e.triggerValue||"\xa0")}}function PS(e,t){1&e&&Vo(0,0,["*ngSwitchCase","true"])}function DS(e,t){1&e&&(Eo(0,"span",9),bo(1,LS,2,1,"span",10),bo(2,PS,1,0,"ng-content",11),Ao()),2&e&&(ko("ngSwitch",!!Bo().customTrigger),gn(2),ko("ngSwitchCase",!0))}function IS(e,t){if(1&e){const e=Lo();Eo(0,"div",12),Eo(1,"div",13,14),Io("@transformPanel.done",(function(t){return ni(e),Bo()._panelDoneAnimatingStream.next(t.toState)}))("keydown",(function(t){return ni(e),Bo()._handleKeydown(t)})),Vo(3,1),Ao(),Ao()}if(2&e){const e=Bo();ko("@transformPanelWrap",void 0),gn(1),"mat-select-panel ",i=e._getPanelTheme(),"",ta(ot,Jo,yo(ii(),"mat-select-panel ",i,""),!0),Yo("transform-origin",e._transformOrigin)("font-size",e._triggerFontSize,"px"),ko("ngClass",e.panelClass)("@transformPanel",e.multiple?"showing-multiple":"showing"),vo("id",e.id+"-panel")("aria-multiselectable",e.multiple)("aria-label",e.ariaLabel||null)("aria-labelledby",e._getPanelAriaLabelledby())}var i}const MS=[[["mat-select-trigger"]],"*"],FS=["mat-select-trigger","*"],NS={transformPanelWrap:uu("transformPanelWrap",[mu("* => void",vu("@transformPanel",[gu()],{optional:!0}))]),transformPanel:uu("transformPanel",[_u("void",pu({transform:"scaleY(0.8)",minWidth:"100%",opacity:0})),_u("showing",pu({opacity:1,minWidth:"calc(100% + 32px)",transform:"scaleY(1)"})),_u("showing-multiple",pu({opacity:1,minWidth:"calc(100% + 64px)",transform:"scaleY(1)"})),mu("void => *",du("120ms cubic-bezier(0, 0, 0.2, 1)")),mu("* => void",du("100ms 25ms linear",pu({opacity:0})))])};let jS=0;const BS=new Be("mat-select-scroll-strategy"),HS=new Be("MAT_SELECT_CONFIG"),US={provide:BS,deps:[Em],useFactory:function(e){return()=>e.scrollStrategies.reposition()}};class VS{constructor(e,t){this.source=e,this.value=t}}class qS{constructor(e,t,i,r,n){this._elementRef=e,this._defaultErrorStateMatcher=t,this._parentForm=i,this._parentFormGroup=r,this.ngControl=n}}const zS=og(ag(ng(lg(qS)))),WS=new Be("MatSelectTrigger");let $S=(()=>{class e extends zS{constructor(e,t,i,r,n,s,o,a,l,c,h,u,d,f){super(n,r,o,a,c),this._viewportRuler=e,this._changeDetectorRef=t,this._ngZone=i,this._dir=s,this._parentFormField=l,this.ngControl=c,this._liveAnnouncer=d,this._panelOpen=!1,this._required=!1,this._scrollTop=0,this._multiple=!1,this._compareWith=(e,t)=>e===t,this._uid="mat-select-"+jS++,this._triggerAriaLabelledBy=null,this._destroy=new S,this._triggerFontSize=0,this._onChange=()=>{},this._onTouched=()=>{},this._valueId="mat-select-value-"+jS++,this._transformOrigin="top",this._panelDoneAnimatingStream=new S,this._offsetY=0,this._positions=[{originX:"start",originY:"top",overlayX:"start",overlayY:"top"},{originX:"start",originY:"bottom",overlayX:"start",overlayY:"bottom"}],this._disableOptionCentering=!1,this._focused=!1,this.controlType="mat-select",this.ariaLabel="",this.optionSelectionChanges=Ff(()=>{const e=this.options;return e?e.changes.pipe(v_(e),Gf(()=>W(...e.map(e=>e.onSelectionChange)))):this._ngZone.onStable.pipe(Qf(1),Gf(()=>this.optionSelectionChanges))}),this.openedChange=new El,this._openedStream=this.openedChange.pipe(Wf(e=>e),M(()=>{})),this._closedStream=this.openedChange.pipe(Wf(e=>!e),M(()=>{})),this.selectionChange=new El,this.valueChange=new El,this.ngControl&&(this.ngControl.valueAccessor=this),this._scrollStrategyFactory=u,this._scrollStrategy=this._scrollStrategyFactory(),this.tabIndex=parseInt(h)||0,this.id=this.id,f&&(null!=f.disableOptionCentering&&(this.disableOptionCentering=f.disableOptionCentering),null!=f.typeaheadDebounceInterval&&(this.typeaheadDebounceInterval=f.typeaheadDebounceInterval))}get focused(){return this._focused||this._panelOpen}get placeholder(){return this._placeholder}set placeholder(e){this._placeholder=e,this.stateChanges.next()}get required(){return this._required}set required(e){this._required=r_(e),this.stateChanges.next()}get multiple(){return this._multiple}set multiple(e){this._multiple=r_(e)}get disableOptionCentering(){return this._disableOptionCentering}set disableOptionCentering(e){this._disableOptionCentering=r_(e)}get compareWith(){return this._compareWith}set compareWith(e){this._compareWith=e,this._selectionModel&&this._initializeSelection()}get value(){return this._value}set value(e){e!==this._value&&(this.options&&this._setSelectionByValue(e),this._value=e)}get typeaheadDebounceInterval(){return this._typeaheadDebounceInterval}set typeaheadDebounceInterval(e){this._typeaheadDebounceInterval=n_(e)}get id(){return this._id}set id(e){this._id=e||this._uid,this.stateChanges.next()}ngOnInit(){this._selectionModel=new H_(this.multiple),this.stateChanges.next(),this._panelDoneAnimatingStream.pipe(e=>e.lift(new l_(void 0,void 0)),__(this._destroy)).subscribe(()=>{this.panelOpen?(this._scrollTop=0,this.openedChange.emit(!0)):(this.openedChange.emit(!1),this.overlayDir.offsetX=0,this._changeDetectorRef.markForCheck())}),this._viewportRuler.change().pipe(__(this._destroy)).subscribe(()=>{this._panelOpen&&(this._triggerRect=this.trigger.nativeElement.getBoundingClientRect(),this._changeDetectorRef.markForCheck())})}ngAfterContentInit(){this._initKeyManager(),this._selectionModel.changed.pipe(__(this._destroy)).subscribe(e=>{e.added.forEach(e=>e.select()),e.removed.forEach(e=>e.deselect())}),this.options.changes.pipe(v_(null),__(this._destroy)).subscribe(()=>{this._resetOptions(),this._initializeSelection()})}ngDoCheck(){const e=this._getTriggerAriaLabelledby();if(e!==this._triggerAriaLabelledBy){const t=this._elementRef.nativeElement;this._triggerAriaLabelledBy=e,e?t.setAttribute("aria-labelledby",e):t.removeAttribute("aria-labelledby")}this.ngControl&&this.updateErrorState()}ngOnChanges(e){e.disabled&&this.stateChanges.next(),e.typeaheadDebounceInterval&&this._keyManager&&this._keyManager.withTypeAhead(this._typeaheadDebounceInterval)}ngOnDestroy(){this._destroy.next(),this._destroy.complete(),this.stateChanges.complete()}toggle(){this.panelOpen?this.close():this.open()}open(){!this.disabled&&this.options&&this.options.length&&!this._panelOpen&&(this._triggerRect=this.trigger.nativeElement.getBoundingClientRect(),this._triggerFontSize=parseInt(getComputedStyle(this.trigger.nativeElement).fontSize||"0"),this._panelOpen=!0,this._keyManager.withHorizontalOrientation(null),this._calculateOverlayPosition(),this._highlightCorrectOption(),this._changeDetectorRef.markForCheck(),this._ngZone.onStable.pipe(Qf(1)).subscribe(()=>{this._triggerFontSize&&this.overlayDir.overlayRef&&this.overlayDir.overlayRef.overlayElement&&(this.overlayDir.overlayRef.overlayElement.style.fontSize=this._triggerFontSize+"px")}))}close(){this._panelOpen&&(this._panelOpen=!1,this._keyManager.withHorizontalOrientation(this._isRtl()?"rtl":"ltr"),this._changeDetectorRef.markForCheck(),this._onTouched())}writeValue(e){this.value=e}registerOnChange(e){this._onChange=e}registerOnTouched(e){this._onTouched=e}setDisabledState(e){this.disabled=e,this._changeDetectorRef.markForCheck(),this.stateChanges.next()}get panelOpen(){return this._panelOpen}get selected(){return this.multiple?this._selectionModel.selected:this._selectionModel.selected[0]}get triggerValue(){if(this.empty)return"";if(this._multiple){const e=this._selectionModel.selected.map(e=>e.viewValue);return this._isRtl()&&e.reverse(),e.join(", ")}return this._selectionModel.selected[0].viewValue}_isRtl(){return!!this._dir&&"rtl"===this._dir.value}_handleKeydown(e){this.disabled||(this.panelOpen?this._handleOpenKeydown(e):this._handleClosedKeydown(e))}_handleClosedKeydown(e){const t=e.keyCode,i=40===t||38===t||37===t||39===t,r=13===t||32===t,n=this._keyManager;if(!n.isTyping()&&r&&!tm(e)||(this.multiple||e.altKey)&&i)e.preventDefault(),this.open();else if(!this.multiple){const t=this.selected;n.onKeydown(e);const i=this.selected;i&&t!==i&&this._liveAnnouncer.announce(i.viewValue,1e4)}}_handleOpenKeydown(e){const t=this._keyManager,i=e.keyCode,r=40===i||38===i,n=t.isTyping();if(r&&e.altKey)e.preventDefault(),this.close();else if(n||13!==i&&32!==i||!t.activeItem||tm(e))if(!n&&this._multiple&&65===i&&e.ctrlKey){e.preventDefault();const t=this.options.some(e=>!e.disabled&&!e.selected);this.options.forEach(e=>{e.disabled||(t?e.select():e.deselect())})}else{const i=t.activeItemIndex;t.onKeydown(e),this._multiple&&r&&e.shiftKey&&t.activeItem&&t.activeItemIndex!==i&&t.activeItem._selectViaInteraction()}else e.preventDefault(),t.activeItem._selectViaInteraction()}_onFocus(){this.disabled||(this._focused=!0,this.stateChanges.next())}_onBlur(){this._focused=!1,this.disabled||this.panelOpen||(this._onTouched(),this._changeDetectorRef.markForCheck(),this.stateChanges.next())}_onAttached(){this.overlayDir.positionChange.pipe(Qf(1)).subscribe(()=>{this._changeDetectorRef.detectChanges(),this._calculateOverlayOffsetX(),this.panel.nativeElement.scrollTop=this._scrollTop})}_getPanelTheme(){return this._parentFormField?"mat-"+this._parentFormField.color:""}get empty(){return!this._selectionModel||this._selectionModel.isEmpty()}_initializeSelection(){Promise.resolve().then(()=>{this._setSelectionByValue(this.ngControl?this.ngControl.value:this._value),this.stateChanges.next()})}_setSelectionByValue(e){if(this.multiple&&e)Array.isArray(e),this._selectionModel.clear(),e.forEach(e=>this._selectValue(e)),this._sortValues();else{this._selectionModel.clear();const t=this._selectValue(e);t?this._keyManager.updateActiveItem(t):this.panelOpen||this._keyManager.updateActiveItem(-1)}this._changeDetectorRef.markForCheck()}_selectValue(e){const t=this.options.find(t=>{try{return null!=t.value&&this._compareWith(t.value,e)}catch(i){return!1}});return t&&this._selectionModel.select(t),t}_initKeyManager(){this._keyManager=new Um(this.options).withTypeAhead(this._typeaheadDebounceInterval).withVerticalOrientation().withHorizontalOrientation(this._isRtl()?"rtl":"ltr").withHomeAndEnd().withAllowedModifierKeys(["shiftKey"]),this._keyManager.tabOut.pipe(__(this._destroy)).subscribe(()=>{this.panelOpen&&(!this.multiple&&this._keyManager.activeItem&&this._keyManager.activeItem._selectViaInteraction(),this.focus(),this.close())}),this._keyManager.change.pipe(__(this._destroy)).subscribe(()=>{this._panelOpen&&this.panel?this._scrollActiveOptionIntoView():this._panelOpen||this.multiple||!this._keyManager.activeItem||this._keyManager.activeItem._selectViaInteraction()})}_resetOptions(){const e=W(this.options.changes,this._destroy);this.optionSelectionChanges.pipe(__(e)).subscribe(e=>{this._onSelect(e.source,e.isUserInput),e.isUserInput&&!this.multiple&&this._panelOpen&&(this.close(),this.focus())}),W(...this.options.map(e=>e._stateChanges)).pipe(__(e)).subscribe(()=>{this._changeDetectorRef.markForCheck(),this.stateChanges.next()})}_onSelect(e,t){const i=this._selectionModel.isSelected(e);null!=e.value||this._multiple?(i!==e.selected&&(e.selected?this._selectionModel.select(e):this._selectionModel.deselect(e)),t&&this._keyManager.setActiveItem(e),this.multiple&&(this._sortValues(),t&&this.focus())):(e.deselect(),this._selectionModel.clear(),null!=this.value&&this._propagateChanges(e.value)),i!==this._selectionModel.isSelected(e)&&this._propagateChanges(),this.stateChanges.next()}_sortValues(){if(this.multiple){const e=this.options.toArray();this._selectionModel.sort((t,i)=>this.sortComparator?this.sortComparator(t,i,e):e.indexOf(t)-e.indexOf(i)),this.stateChanges.next()}}_propagateChanges(e){let t=null;t=this.multiple?this.selected.map(e=>e.value):this.selected?this.selected.value:e,this._value=t,this.valueChange.emit(t),this._onChange(t),this.selectionChange.emit(new VS(this,t)),this._changeDetectorRef.markForCheck()}_highlightCorrectOption(){this._keyManager&&(this.empty?this._keyManager.setFirstItemActive():this._keyManager.setActiveItem(this._selectionModel.selected[0]))}_scrollActiveOptionIntoView(){const e=this._keyManager.activeItemIndex||0,t=Pg(e,this.options,this.optionGroups),i=this._getItemHeight();var r,n,s;this.panel.nativeElement.scrollTop=(n=i,256,(r=(e+t)*i)<(s=this.panel.nativeElement.scrollTop)?r:r+n>s+256?Math.max(0,r-256+n):s)}focus(e){this._elementRef.nativeElement.focus(e)}_getOptionIndex(e){return this.options.reduce((t,i,r)=>void 0!==t?t:e===i?r:void 0,void 0)}_calculateOverlayPosition(){const e=this._getItemHeight(),t=this._getItemCount(),i=Math.min(t*e,256),r=t*e-i;let n=this.empty?0:this._getOptionIndex(this._selectionModel.selected[0]);n+=Pg(n,this.options,this.optionGroups);const s=i/2;this._scrollTop=this._calculateOverlayScroll(n,s,r),this._offsetY=this._calculateOverlayOffsetY(n,s,r),this._checkOverlayWithinViewport(r)}_calculateOverlayScroll(e,t,i){const r=this._getItemHeight();return Math.min(Math.max(0,r*e-t+r/2),i)}_getPanelAriaLabelledby(){if(this.ariaLabel)return null;const e=this._getLabelId();return this.ariaLabelledby?e+" "+this.ariaLabelledby:e}_getAriaActiveDescendant(){return this.panelOpen&&this._keyManager&&this._keyManager.activeItem?this._keyManager.activeItem.id:null}_getLabelId(){var e;return(null===(e=this._parentFormField)||void 0===e?void 0:e.getLabelId())||""}_calculateOverlayOffsetX(){const e=this.overlayDir.overlayRef.overlayElement.getBoundingClientRect(),t=this._viewportRuler.getViewportSize(),i=this._isRtl(),r=this.multiple?56:32;let n;if(this.multiple)n=40;else{let e=this._selectionModel.selected[0]||this.options.first;n=e&&e.group?32:16}i||(n*=-1);const s=0-(e.left+n-(i?r:0)),o=e.right+n-t.width+(i?0:r);s>0?n+=s+8:o>0&&(n-=o+8),this.overlayDir.offsetX=Math.round(n),this.overlayDir.overlayRef.updatePosition()}_calculateOverlayOffsetY(e,t,i){const r=this._getItemHeight(),n=(r-this._triggerRect.height)/2,s=Math.floor(256/r);let o;return this._disableOptionCentering?0:(o=0===this._scrollTop?e*r:this._scrollTop===i?(e-(this._getItemCount()-s))*r+(r-(this._getItemCount()*r-256)%r):t-r/2,Math.round(-1*o-n))}_checkOverlayWithinViewport(e){const t=this._getItemHeight(),i=this._viewportRuler.getViewportSize(),r=this._triggerRect.top-8,n=i.height-this._triggerRect.bottom-8,s=Math.abs(this._offsetY),o=Math.min(this._getItemCount()*t,256)-s-this._triggerRect.height;o>n?this._adjustPanelUp(o,n):s>r?this._adjustPanelDown(s,r,e):this._transformOrigin=this._getOriginBasedOnOption()}_adjustPanelUp(e,t){const i=Math.round(e-t);this._scrollTop-=i,this._offsetY-=i,this._transformOrigin=this._getOriginBasedOnOption(),this._scrollTop<=0&&(this._scrollTop=0,this._offsetY=0,this._transformOrigin="50% bottom 0px")}_adjustPanelDown(e,t,i){const r=Math.round(e-t);if(this._scrollTop+=r,this._offsetY+=r,this._transformOrigin=this._getOriginBasedOnOption(),this._scrollTop>=i)return this._scrollTop=i,this._offsetY=0,void(this._transformOrigin="50% top 0px")}_getOriginBasedOnOption(){const e=this._getItemHeight(),t=(e-this._triggerRect.height)/2;return`50% ${Math.abs(this._offsetY)-t+e/2}px 0px`}_getItemCount(){return this.options.length+this.optionGroups.length}_getItemHeight(){return 3*this._triggerFontSize}_getTriggerAriaLabelledby(){if(this.ariaLabel)return null;let e=this._getLabelId()+" "+this._valueId;return this.ariaLabelledby&&(e+=" "+this.ariaLabelledby),e}setDescribedByIds(e){this._ariaDescribedby=e.join(" ")}onContainerClick(){this.focus(),this.open()}get shouldLabelFloat(){return this._panelOpen||!this.empty}}return e.\u0275fac=function(t){return new(t||e)(Co(V_),Co(zs),Co(mc),Co(cg),Co(Pa),Co(j_,8),Co(dS,8),Co(SS,8),Co(sC,8),Co(yC,10),So("tabindex"),Co(BS),Co(zm),Co(HS,8))},e.\u0275cmp=pt({type:e,selectors:[["mat-select"]],contentQueries:function(e,t,i){var r;1&e&&(Ul(i,WS,!0),Ul(i,Lg,!0),Ul(i,Eg,!0)),2&e&&(Nl(r=zl())&&(t.customTrigger=r.first),Nl(r=zl())&&(t.options=r),Nl(r=zl())&&(t.optionGroups=r))},viewQuery:function(e,t){var i;1&e&&(Bl(OS,!0),Bl(TS,!0),Bl(Rm,!0)),2&e&&(Nl(i=zl())&&(t.trigger=i.first),Nl(i=zl())&&(t.panel=i.first),Nl(i=zl())&&(t.overlayDir=i.first))},hostAttrs:["role","combobox","aria-autocomplete","none","aria-haspopup","true",1,"mat-select"],hostVars:20,hostBindings:function(e,t){1&e&&Io("keydown",(function(e){return t._handleKeydown(e)}))("focus",(function(){return t._onFocus()}))("blur",(function(){return t._onBlur()})),2&e&&(vo("id",t.id)("tabindex",t.tabIndex)("aria-controls",t.panelOpen?t.id+"-panel":null)("aria-expanded",t.panelOpen)("aria-label",t.ariaLabel||null)("aria-required",t.required.toString())("aria-disabled",t.disabled.toString())("aria-invalid",t.errorState)("aria-describedby",t._ariaDescribedby||null)("aria-activedescendant",t._getAriaActiveDescendant()),Xo("mat-select-disabled",t.disabled)("mat-select-invalid",t.errorState)("mat-select-required",t.required)("mat-select-empty",t.empty)("mat-select-multiple",t.multiple))},inputs:{disabled:"disabled",disableRipple:"disableRipple",tabIndex:"tabIndex",ariaLabel:["aria-label","ariaLabel"],id:"id",disableOptionCentering:"disableOptionCentering",typeaheadDebounceInterval:"typeaheadDebounceInterval",placeholder:"placeholder",required:"required",multiple:"multiple",compareWith:"compareWith",value:"value",panelClass:"panelClass",ariaLabelledby:["aria-labelledby","ariaLabelledby"],errorStateMatcher:"errorStateMatcher",sortComparator:"sortComparator"},outputs:{openedChange:"openedChange",_openedStream:"opened",_closedStream:"closed",selectionChange:"selectionChange",valueChange:"valueChange"},exportAs:["matSelect"],features:[Oa([{provide:Gw,useExisting:e},{provide:Tg,useExisting:e}]),ma,Dt],ngContentSelectors:FS,decls:9,vars:10,consts:[["cdk-overlay-origin","",1,"mat-select-trigger",3,"click"],["origin","cdkOverlayOrigin","trigger",""],[1,"mat-select-value",3,"ngSwitch"],["class","mat-select-placeholder",4,"ngSwitchCase"],["class","mat-select-value-text",3,"ngSwitch",4,"ngSwitchCase"],[1,"mat-select-arrow-wrapper"],[1,"mat-select-arrow"],["cdk-connected-overlay","","cdkConnectedOverlayLockPosition","","cdkConnectedOverlayHasBackdrop","","cdkConnectedOverlayBackdropClass","cdk-overlay-transparent-backdrop",3,"cdkConnectedOverlayScrollStrategy","cdkConnectedOverlayOrigin","cdkConnectedOverlayOpen","cdkConnectedOverlayPositions","cdkConnectedOverlayMinWidth","cdkConnectedOverlayOffsetY","backdropClick","attach","detach"],[1,"mat-select-placeholder"],[1,"mat-select-value-text",3,"ngSwitch"],[4,"ngSwitchDefault"],[4,"ngSwitchCase"],[1,"mat-select-panel-wrap"],["role","listbox","tabindex","-1",3,"ngClass","keydown"],["panel",""]],template:function(e,t){if(1&e&&(Uo(MS),Eo(0,"div",0,1),Io("click",(function(){return t.toggle()})),Eo(3,"div",2),bo(4,RS,2,1,"span",3),bo(5,DS,3,2,"span",4),Ao(),Eo(6,"div",5),Oo(7,"div",6),Ao(),Ao(),bo(8,IS,4,14,"ng-template",7),Io("backdropClick",(function(){return t.close()}))("attach",(function(){return t._onAttached()}))("detach",(function(){return t.close()}))),2&e){const e=wo(1);gn(3),ko("ngSwitch",t.empty),vo("id",t._valueId),gn(1),ko("ngSwitchCase",!0),gn(1),ko("ngSwitchCase",!1),gn(3),ko("cdkConnectedOverlayScrollStrategy",t._scrollStrategy)("cdkConnectedOverlayOrigin",e)("cdkConnectedOverlayOpen",t.panelOpen)("cdkConnectedOverlayPositions",t._positions)("cdkConnectedOverlayMinWidth",null==t._triggerRect?null:t._triggerRect.width)("cdkConnectedOverlayOffsetY",t._offsetY)}},directives:[Tm,Sh,kh,Rm,xh,_h],styles:[".mat-select{display:inline-block;width:100%;outline:none}.mat-select-trigger{display:inline-table;cursor:pointer;position:relative;box-sizing:border-box}.mat-select-disabled .mat-select-trigger{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.mat-select-value{display:table-cell;max-width:0;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mat-select-value-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mat-select-arrow-wrapper{display:table-cell;vertical-align:middle}.mat-form-field-appearance-fill .mat-select-arrow-wrapper{transform:translateY(-50%)}.mat-form-field-appearance-outline .mat-select-arrow-wrapper{transform:translateY(-25%)}.mat-form-field-appearance-standard.mat-form-field-has-label .mat-select:not(.mat-select-empty) .mat-select-arrow-wrapper{transform:translateY(-50%)}.mat-form-field-appearance-standard .mat-select.mat-select-empty .mat-select-arrow-wrapper{transition:transform 400ms cubic-bezier(0.25, 0.8, 0.25, 1)}._mat-animation-noopable.mat-form-field-appearance-standard .mat-select.mat-select-empty .mat-select-arrow-wrapper{transition:none}.mat-select-arrow{width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid;margin:0 4px}.mat-select-panel-wrap{flex-basis:100%}.mat-select-panel{min-width:112px;max-width:280px;overflow:auto;-webkit-overflow-scrolling:touch;padding-top:0;padding-bottom:0;max-height:256px;min-width:100%;border-radius:4px}.cdk-high-contrast-active .mat-select-panel{outline:solid 1px}.mat-select-panel .mat-optgroup-label,.mat-select-panel .mat-option{font-size:inherit;line-height:3em;height:3em}.mat-form-field-type-mat-select:not(.mat-form-field-disabled) .mat-form-field-flex{cursor:pointer}.mat-form-field-type-mat-select .mat-form-field-label{width:calc(100% - 18px)}.mat-select-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}._mat-animation-noopable .mat-select-placeholder{transition:none}.mat-form-field-hide-placeholder .mat-select-placeholder{color:transparent;-webkit-text-fill-color:transparent;transition:none;display:block}\n"],encapsulation:2,data:{animation:[NS.transformPanelWrap,NS.transformPanel]},changeDetection:0}),e})(),KS=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[US],imports:[[Lh,Pm,Dg,rg],q_,aC,Dg,rg]}),e})();const GS=["input"],ZS=function(){return{enterDuration:150}},YS=["*"],XS=new Be("mat-checkbox-default-options",{providedIn:"root",factory:function(){return{color:"accent",clickAction:"check-indeterminate"}}}),QS=new Be("mat-checkbox-click-action");let JS=0;const ek={provide:cC,useExisting:ke(()=>nk),multi:!0};class tk{}class ik{constructor(e){this._elementRef=e}}const rk=ag(sg(og(ng(ik))));let nk=(()=>{class e extends rk{constructor(e,t,i,r,n,s,o,a){super(e),this._changeDetectorRef=t,this._focusMonitor=i,this._ngZone=r,this._clickAction=s,this._animationMode=o,this._options=a,this.ariaLabel="",this.ariaLabelledby=null,this._uniqueId="mat-checkbox-"+ ++JS,this.id=this._uniqueId,this.labelPosition="after",this.name=null,this.change=new El,this.indeterminateChange=new El,this._onTouched=()=>{},this._currentAnimationClass="",this._currentCheckState=0,this._controlValueAccessorChangeFn=()=>{},this._checked=!1,this._disabled=!1,this._indeterminate=!1,this._options=this._options||{},this._options.color&&(this.color=this.defaultColor=this._options.color),this.tabIndex=parseInt(n)||0,this._clickAction=this._clickAction||this._options.clickAction}get inputId(){return(this.id||this._uniqueId)+"-input"}get required(){return this._required}set required(e){this._required=r_(e)}ngAfterViewInit(){this._focusMonitor.monitor(this._elementRef,!0).subscribe(e=>{e||Promise.resolve().then(()=>{this._onTouched(),this._changeDetectorRef.markForCheck()})}),this._syncIndeterminate(this._indeterminate)}ngAfterViewChecked(){}ngOnDestroy(){this._focusMonitor.stopMonitoring(this._elementRef)}get checked(){return this._checked}set checked(e){e!=this.checked&&(this._checked=e,this._changeDetectorRef.markForCheck())}get disabled(){return this._disabled}set disabled(e){const t=r_(e);t!==this.disabled&&(this._disabled=t,this._changeDetectorRef.markForCheck())}get indeterminate(){return this._indeterminate}set indeterminate(e){const t=e!=this._indeterminate;this._indeterminate=r_(e),t&&(this._transitionCheckState(this._indeterminate?3:this.checked?1:2),this.indeterminateChange.emit(this._indeterminate)),this._syncIndeterminate(this._indeterminate)}_isRippleDisabled(){return this.disableRipple||this.disabled}_onLabelTextChange(){this._changeDetectorRef.detectChanges()}writeValue(e){this.checked=!!e}registerOnChange(e){this._controlValueAccessorChangeFn=e}registerOnTouched(e){this._onTouched=e}setDisabledState(e){this.disabled=e}_getAriaChecked(){return this.checked?"true":this.indeterminate?"mixed":"false"}_transitionCheckState(e){let t=this._currentCheckState,i=this._elementRef.nativeElement;if(t!==e&&(this._currentAnimationClass.length>0&&i.classList.remove(this._currentAnimationClass),this._currentAnimationClass=this._getAnimationClassForCheckStateTransition(t,e),this._currentCheckState=e,this._currentAnimationClass.length>0)){i.classList.add(this._currentAnimationClass);const e=this._currentAnimationClass;this._ngZone.runOutsideAngular(()=>{setTimeout(()=>{i.classList.remove(e)},1e3)})}}_emitChangeEvent(){const e=new tk;e.source=this,e.checked=this.checked,this._controlValueAccessorChangeFn(this.checked),this.change.emit(e)}toggle(){this.checked=!this.checked}_onInputClick(e){e.stopPropagation(),this.disabled||"noop"===this._clickAction?this.disabled||"noop"!==this._clickAction||(this._inputElement.nativeElement.checked=this.checked,this._inputElement.nativeElement.indeterminate=this.indeterminate):(this.indeterminate&&"check"!==this._clickAction&&Promise.resolve().then(()=>{this._indeterminate=!1,this.indeterminateChange.emit(this._indeterminate)}),this.toggle(),this._transitionCheckState(this._checked?1:2),this._emitChangeEvent())}focus(e="keyboard",t){this._focusMonitor.focusVia(this._inputElement,e,t)}_onInteractionEvent(e){e.stopPropagation()}_getAnimationClassForCheckStateTransition(e,t){if("NoopAnimations"===this._animationMode)return"";let i="";switch(e){case 0:if(1===t)i="unchecked-checked";else{if(3!=t)return"";i="unchecked-indeterminate"}break;case 2:i=1===t?"unchecked-checked":"unchecked-indeterminate";break;case 1:i=2===t?"checked-unchecked":"checked-indeterminate";break;case 3:i=1===t?"indeterminate-checked":"indeterminate-unchecked"}return"mat-checkbox-anim-"+i}_syncIndeterminate(e){const t=this._inputElement;t&&(t.nativeElement.indeterminate=e)}}return e.\u0275fac=function(t){return new(t||e)(Co(Pa),Co(zs),Co(Gm),Co(mc),So("tabindex"),Co(QS,8),Co(Lf,8),Co(XS,8))},e.\u0275cmp=pt({type:e,selectors:[["mat-checkbox"]],viewQuery:function(e,t){var i;1&e&&(Bl(GS,!0),Bl(gg,!0)),2&e&&(Nl(i=zl())&&(t._inputElement=i.first),Nl(i=zl())&&(t.ripple=i.first))},hostAttrs:[1,"mat-checkbox"],hostVars:12,hostBindings:function(e,t){2&e&&(fa("id",t.id),vo("tabindex",null),Xo("mat-checkbox-indeterminate",t.indeterminate)("mat-checkbox-checked",t.checked)("mat-checkbox-disabled",t.disabled)("mat-checkbox-label-before","before"==t.labelPosition)("_mat-animation-noopable","NoopAnimations"===t._animationMode))},inputs:{disableRipple:"disableRipple",color:"color",tabIndex:"tabIndex",ariaLabel:["aria-label","ariaLabel"],ariaLabelledby:["aria-labelledby","ariaLabelledby"],id:"id",labelPosition:"labelPosition",name:"name",required:"required",checked:"checked",disabled:"disabled",indeterminate:"indeterminate",ariaDescribedby:["aria-describedby","ariaDescribedby"],value:"value"},outputs:{change:"change",indeterminateChange:"indeterminateChange"},exportAs:["matCheckbox"],features:[Oa([ek]),ma],ngContentSelectors:YS,decls:17,vars:20,consts:[[1,"mat-checkbox-layout"],["label",""],[1,"mat-checkbox-inner-container"],["type","checkbox",1,"mat-checkbox-input","cdk-visually-hidden",3,"id","required","checked","disabled","tabIndex","change","click"],["input",""],["matRipple","",1,"mat-checkbox-ripple","mat-focus-indicator",3,"matRippleTrigger","matRippleDisabled","matRippleRadius","matRippleCentered","matRippleAnimation"],[1,"mat-ripple-element","mat-checkbox-persistent-ripple"],[1,"mat-checkbox-frame"],[1,"mat-checkbox-background"],["version","1.1","focusable","false","viewBox","0 0 24 24",0,"xml","space","preserve",1,"mat-checkbox-checkmark"],["fill","none","stroke","white","d","M4.1,12.7 9,17.6 20.3,6.3",1,"mat-checkbox-checkmark-path"],[1,"mat-checkbox-mixedmark"],[1,"mat-checkbox-label",3,"cdkObserveContent"],["checkboxLabel",""],[2,"display","none"]],template:function(e,t){if(1&e&&(Uo(),Eo(0,"label",0,1),Eo(2,"div",2),Eo(3,"input",3,4),Io("change",(function(e){return t._onInteractionEvent(e)}))("click",(function(e){return t._onInputClick(e)})),Ao(),Eo(5,"div",5),Oo(6,"div",6),Ao(),Oo(7,"div",7),Eo(8,"div",8),Ti(),Eo(9,"svg",9),Oo(10,"path",10),Ao(),ei.lFrame.currentNamespace=null,Oo(11,"div",11),Ao(),Ao(),Eo(12,"span",12,13),Io("cdkObserveContent",(function(){return t._onLabelTextChange()})),Eo(14,"span",14),ha(15,"\xa0"),Ao(),Vo(16),Ao(),Ao()),2&e){const e=wo(1),i=wo(13);vo("for",t.inputId),gn(2),Xo("mat-checkbox-inner-container-no-side-margin",!i.textContent||!i.textContent.trim()),gn(1),ko("id",t.inputId)("required",t.required)("checked",t.checked)("disabled",t.disabled)("tabIndex",t.tabIndex),vo("value",t.value)("name",t.name)("aria-label",t.ariaLabel||null)("aria-labelledby",t.ariaLabelledby)("aria-checked",t._getAriaChecked())("aria-describedby",t.ariaDescribedby),gn(2),ko("matRippleTrigger",e)("matRippleDisabled",t._isRippleDisabled())("matRippleRadius",20)("matRippleCentered",!0)("matRippleAnimation",Cl(19,ZS))}},directives:[gg,Bm],styles:["@keyframes mat-checkbox-fade-in-background{0%{opacity:0}50%{opacity:1}}@keyframes mat-checkbox-fade-out-background{0%,50%{opacity:1}100%{opacity:0}}@keyframes mat-checkbox-unchecked-checked-checkmark-path{0%,50%{stroke-dashoffset:22.910259}50%{animation-timing-function:cubic-bezier(0, 0, 0.2, 0.1)}100%{stroke-dashoffset:0}}@keyframes mat-checkbox-unchecked-indeterminate-mixedmark{0%,68.2%{transform:scaleX(0)}68.2%{animation-timing-function:cubic-bezier(0, 0, 0, 1)}100%{transform:scaleX(1)}}@keyframes mat-checkbox-checked-unchecked-checkmark-path{from{animation-timing-function:cubic-bezier(0.4, 0, 1, 1);stroke-dashoffset:0}to{stroke-dashoffset:-22.910259}}@keyframes mat-checkbox-checked-indeterminate-checkmark{from{animation-timing-function:cubic-bezier(0, 0, 0.2, 0.1);opacity:1;transform:rotate(0deg)}to{opacity:0;transform:rotate(45deg)}}@keyframes mat-checkbox-indeterminate-checked-checkmark{from{animation-timing-function:cubic-bezier(0.14, 0, 0, 1);opacity:0;transform:rotate(45deg)}to{opacity:1;transform:rotate(360deg)}}@keyframes mat-checkbox-checked-indeterminate-mixedmark{from{animation-timing-function:cubic-bezier(0, 0, 0.2, 0.1);opacity:0;transform:rotate(-45deg)}to{opacity:1;transform:rotate(0deg)}}@keyframes mat-checkbox-indeterminate-checked-mixedmark{from{animation-timing-function:cubic-bezier(0.14, 0, 0, 1);opacity:1;transform:rotate(0deg)}to{opacity:0;transform:rotate(315deg)}}@keyframes mat-checkbox-indeterminate-unchecked-mixedmark{0%{animation-timing-function:linear;opacity:1;transform:scaleX(1)}32.8%,100%{opacity:0;transform:scaleX(0)}}.mat-checkbox-background,.mat-checkbox-frame{top:0;left:0;right:0;bottom:0;position:absolute;border-radius:2px;box-sizing:border-box;pointer-events:none}.mat-checkbox{transition:background 400ms cubic-bezier(0.25, 0.8, 0.25, 1),box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);cursor:pointer;-webkit-tap-highlight-color:transparent}._mat-animation-noopable.mat-checkbox{transition:none;animation:none}.mat-checkbox .mat-ripple-element:not(.mat-checkbox-persistent-ripple){opacity:.16}.mat-checkbox-layout{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:inherit;align-items:baseline;vertical-align:middle;display:inline-flex;white-space:nowrap}.mat-checkbox-label{-webkit-user-select:auto;-moz-user-select:auto;-ms-user-select:auto;user-select:auto}.mat-checkbox-inner-container{display:inline-block;height:16px;line-height:0;margin:auto;margin-right:8px;order:0;position:relative;vertical-align:middle;white-space:nowrap;width:16px;flex-shrink:0}[dir=rtl] .mat-checkbox-inner-container{margin-left:8px;margin-right:auto}.mat-checkbox-inner-container-no-side-margin{margin-left:0;margin-right:0}.mat-checkbox-frame{background-color:transparent;transition:border-color 90ms cubic-bezier(0, 0, 0.2, 0.1);border-width:2px;border-style:solid}._mat-animation-noopable .mat-checkbox-frame{transition:none}.cdk-high-contrast-active .mat-checkbox.cdk-keyboard-focused .mat-checkbox-frame{border-style:dotted}.mat-checkbox-background{align-items:center;display:inline-flex;justify-content:center;transition:background-color 90ms cubic-bezier(0, 0, 0.2, 0.1),opacity 90ms cubic-bezier(0, 0, 0.2, 0.1)}._mat-animation-noopable .mat-checkbox-background{transition:none}.cdk-high-contrast-active .mat-checkbox .mat-checkbox-background{background:none}.mat-checkbox-persistent-ripple{width:100%;height:100%;transform:none}.mat-checkbox-inner-container:hover .mat-checkbox-persistent-ripple{opacity:.04}.mat-checkbox.cdk-keyboard-focused .mat-checkbox-persistent-ripple{opacity:.12}.mat-checkbox-persistent-ripple,.mat-checkbox.mat-checkbox-disabled .mat-checkbox-inner-container:hover .mat-checkbox-persistent-ripple{opacity:0}@media(hover: none){.mat-checkbox-inner-container:hover .mat-checkbox-persistent-ripple{display:none}}.mat-checkbox-checkmark{top:0;left:0;right:0;bottom:0;position:absolute;width:100%}.mat-checkbox-checkmark-path{stroke-dashoffset:22.910259;stroke-dasharray:22.910259;stroke-width:2.1333333333px}.cdk-high-contrast-black-on-white .mat-checkbox-checkmark-path{stroke:#000 !important}.mat-checkbox-mixedmark{width:calc(100% - 6px);height:2px;opacity:0;transform:scaleX(0) rotate(0deg);border-radius:2px}.cdk-high-contrast-active .mat-checkbox-mixedmark{height:0;border-top:solid 2px;margin-top:2px}.mat-checkbox-label-before .mat-checkbox-inner-container{order:1;margin-left:8px;margin-right:auto}[dir=rtl] .mat-checkbox-label-before .mat-checkbox-inner-container{margin-left:auto;margin-right:8px}.mat-checkbox-checked .mat-checkbox-checkmark{opacity:1}.mat-checkbox-checked .mat-checkbox-checkmark-path{stroke-dashoffset:0}.mat-checkbox-checked .mat-checkbox-mixedmark{transform:scaleX(1) rotate(-45deg)}.mat-checkbox-indeterminate .mat-checkbox-checkmark{opacity:0;transform:rotate(45deg)}.mat-checkbox-indeterminate .mat-checkbox-checkmark-path{stroke-dashoffset:0}.mat-checkbox-indeterminate .mat-checkbox-mixedmark{opacity:1;transform:scaleX(1) rotate(0deg)}.mat-checkbox-unchecked .mat-checkbox-background{background-color:transparent}.mat-checkbox-disabled{cursor:default}.cdk-high-contrast-active .mat-checkbox-disabled{opacity:.5}.mat-checkbox-anim-unchecked-checked .mat-checkbox-background{animation:180ms linear 0ms mat-checkbox-fade-in-background}.mat-checkbox-anim-unchecked-checked .mat-checkbox-checkmark-path{animation:180ms linear 0ms mat-checkbox-unchecked-checked-checkmark-path}.mat-checkbox-anim-unchecked-indeterminate .mat-checkbox-background{animation:180ms linear 0ms mat-checkbox-fade-in-background}.mat-checkbox-anim-unchecked-indeterminate .mat-checkbox-mixedmark{animation:90ms linear 0ms mat-checkbox-unchecked-indeterminate-mixedmark}.mat-checkbox-anim-checked-unchecked .mat-checkbox-background{animation:180ms linear 0ms mat-checkbox-fade-out-background}.mat-checkbox-anim-checked-unchecked .mat-checkbox-checkmark-path{animation:90ms linear 0ms mat-checkbox-checked-unchecked-checkmark-path}.mat-checkbox-anim-checked-indeterminate .mat-checkbox-checkmark{animation:90ms linear 0ms mat-checkbox-checked-indeterminate-checkmark}.mat-checkbox-anim-checked-indeterminate .mat-checkbox-mixedmark{animation:90ms linear 0ms mat-checkbox-checked-indeterminate-mixedmark}.mat-checkbox-anim-indeterminate-checked .mat-checkbox-checkmark{animation:500ms linear 0ms mat-checkbox-indeterminate-checked-checkmark}.mat-checkbox-anim-indeterminate-checked .mat-checkbox-mixedmark{animation:500ms linear 0ms mat-checkbox-indeterminate-checked-mixedmark}.mat-checkbox-anim-indeterminate-unchecked .mat-checkbox-background{animation:180ms linear 0ms mat-checkbox-fade-out-background}.mat-checkbox-anim-indeterminate-unchecked .mat-checkbox-mixedmark{animation:300ms linear 0ms mat-checkbox-indeterminate-unchecked-mixedmark}.mat-checkbox-input{bottom:0;left:50%}.mat-checkbox .mat-checkbox-ripple{position:absolute;left:calc(50% - 20px);top:calc(50% - 20px);height:40px;width:40px;z-index:1;pointer-events:none}\n"],encapsulation:2,changeDetection:0}),e})(),sk=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)}}),e})(),ok=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[vg,rg,Hm,sk],rg,sk]}),e})();function ak(e,t){if(1&e&&(Eo(0,"mat-option",8),ha(1),Ao()),2&e){const e=t.$implicit;ko("value",e),gn(1),da(" ",e.subject," ")}}function lk(e,t){if(1&e&&(Eo(0,"mat-option",8),ha(1),Ao()),2&e){const e=t.$implicit;ko("value",e),gn(1),da(" ",e," ")}}function ck(e,t){if(1&e&&(Eo(0,"mat-option",8),ha(1),Ao()),2&e){const e=t.$implicit;ko("value",e),gn(1),da(" ",e.subject," ")}}let hk=(()=>{class e{constructor(e,t){this._build=e,this._tests=t,this.repo=this._build.active_repo,this.driver=this._build.active_driver,this.driver_commit=this._build.active_commit,this.driver_commits=this._build.driver_commits,this.spec_commit=this._tests.active_commit,this.spec_commits=this._tests.commit_list,this.spec_file=this._tests.active_spec,this.specs=this._tests.spec_list,this.settings=this._tests.settings,this.setCommit=e=>this._build.setCommit(e),this.setSpecFile=e=>this._tests.setSpec(e),this.setSpecCommit=e=>this._tests.setCommit(e),this.setSettings=e=>this._tests.setSettings(e)}}return e.\u0275fac=function(t){return new(t||e)(Co(Ew),Co(Ow))},e.\u0275cmp=pt({type:e,selectors:[["workbench-form"]],decls:46,vars:30,consts:[[1,"py-2","flex","flex-col"],[1,"py-4"],[1,"flex","space-x-2","flex-wrap"],[1,"py-2","flex","flex-col","flex-1"],["appearance","outline"],[3,"ngModel","ngModelChange"],[3,"value",4,"ngFor","ngForOf"],[1,"py-2","flex","items-center"],[3,"value"]],template:function(e,t){1&e&&(Eo(0,"div",0),Eo(1,"label"),ha(2,"Repository"),Ao(),Eo(3,"div",1),ha(4),kl(5,"async"),Ao(),Ao(),Eo(6,"div",2),Eo(7,"div",3),Eo(8,"label"),ha(9,"Driver"),Ao(),Eo(10,"div",1),ha(11),kl(12,"async"),Ao(),Ao(),Eo(13,"div",3),Eo(14,"label"),ha(15,"Commit"),Ao(),Eo(16,"mat-form-field",4),Eo(17,"mat-select",5),Io("ngModelChange",(function(e){return t.setCommit(e)})),kl(18,"async"),bo(19,ak,2,2,"mat-option",6),kl(20,"async"),Ao(),Ao(),Ao(),Ao(),Eo(21,"div",2),Eo(22,"div",3),Eo(23,"label"),ha(24,"Spec File"),Ao(),Eo(25,"mat-form-field",4),Eo(26,"mat-select",5),Io("ngModelChange",(function(e){return t.setSpecFile(e)})),kl(27,"async"),bo(28,lk,2,2,"mat-option",6),kl(29,"async"),Ao(),Ao(),Ao(),Eo(30,"div",3),Eo(31,"label"),ha(32,"Spec File Commit"),Ao(),Eo(33,"mat-form-field",4),Eo(34,"mat-select",5),Io("ngModelChange",(function(e){return t.setSpecCommit(e)})),kl(35,"async"),bo(36,ck,2,2,"mat-option",6),kl(37,"async"),Ao(),Ao(),Ao(),Ao(),Eo(38,"div",7),Eo(39,"mat-checkbox",5),Io("ngModelChange",(function(e){return t.setSettings({force:e})})),kl(40,"async"),ha(41,"Force Recompilation"),Ao(),Ao(),Eo(42,"div",7),Eo(43,"mat-checkbox",5),Io("ngModelChange",(function(e){return t.setSettings({debug_symbols:e})})),kl(44,"async"),ha(45,"Compile with Debug Symbols"),Ao(),Ao()),2&e&&(gn(4),ua(xl(5,10,t.repo)),gn(7),ua(xl(12,12,t.driver)),gn(6),ko("ngModel",xl(18,14,t.driver_commit)),gn(2),ko("ngForOf",xl(20,16,t.driver_commits)),gn(7),ko("ngModel",xl(27,18,t.spec_file)),gn(2),ko("ngForOf",xl(29,20,t.specs)),gn(6),ko("ngModel",xl(35,22,t.spec_commit)),gn(2),ko("ngForOf",xl(37,24,t.spec_commits)),gn(3),ko("ngModel",xl(40,26,t.settings).force),gn(4),ko("ngModel",xl(44,28,t.settings).debug_symbols))},directives:[oC,$S,bC,bS,gh,nk,Lg],pipes:[Rh],styles:["[_nghost-%COMP%] {\n padding: 1rem;\n }\n\n label[_ngcontent-%COMP%] {\n width: 10rem;\n }\n\n mat-form-field[_ngcontent-%COMP%] {\n height: 3.5rem;\n min-width: 16rem;\n }"]}),e})(),uk=(()=>{class e{constructor(){this._timers={},this._intervals={},this._subscriptions={},this._initialised=new pv(!1),this.initialised=this._initialised.asObservable()}get is_initialised(){return this._initialised.getValue()}ngOnDestroy(){this.destroy()}destroy(){for(const e in this._timers)this._timers.hasOwnProperty(e)&&this.clearTimeout(e);for(const e in this._intervals)this._intervals.hasOwnProperty(e)&&this.clearInterval(e);for(const e in this._subscriptions)this._subscriptions.hasOwnProperty(e)&&this.unsub(e)}timeout(e,t,i=300){if(!(e&&t&&t instanceof Function))throw new Error(e?"Cannot create named timeout without a name":"Cannot create a timeout without a callback");this.clearTimeout(e),this._timers[e]=setTimeout(()=>{t(),this._timers[e]=null},i)}clearTimeout(e){this._timers[e]&&(clearTimeout(this._timers[e]),this._timers[e]=null)}interval(e,t,i=300){if(!(e&&t&&t instanceof Function))throw new Error(e?"Cannot create named interval without a name":"Cannot create a interval without a callback");this.clearInterval(e),this._intervals[e]=setInterval(()=>t(),i)}clearInterval(e){this._intervals[e]&&(clearInterval(this._intervals[e]),this._intervals[e]=null)}subscription(e,t){this.unsub(e),this._subscriptions[e]=t}unsub(e){this._subscriptions&&this._subscriptions[e]&&(this._subscriptions[e]instanceof u?this._subscriptions[e].unsubscribe():this._subscriptions[e](),this._subscriptions[e]=null)}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275prov=ue({token:e,factory:e.\u0275fac,providedIn:"root"}),e})();var dk=i("/POA");const fk=["terminal"],pk=["container"];let _k=(()=>{class e extends uk{ngOnInit(){this.terminal&&this.ngOnDestroy(),this.terminal=new dk.Terminal({theme:{background:"#212121",red:"#e53935",blue:"#1e88e5",yellow:"#fdd835",green:"#43a047"},fontSize:14}),this.terminal.open(this.terminal_element.nativeElement),this.timeout("init",()=>{this.resizeTerminal(),this.updateTerminalContents(this.content||"")})}ngOnChanges(e){e.content&&this.updateTerminalContents(this.content||""),e.resize&&this.timeout("resize",()=>this.resizeTerminal())}ngOnDestroy(){this.terminal.clear(),this.terminal.dispose()}resizeTerminal(){if(!this.terminal||!this.container_el)return;const e=this.terminal.getOption("fontSize"),t=this.terminal.getOption("lineHeight"),i=this.container_el.nativeElement.getBoundingClientRect(),r=Math.floor(i.width/(.6*e)),n=Math.floor(i.height/(t*e*1.2));this.terminal.resize(r-2,n)}updateTerminalContents(e){if(!this.terminal)return;this.terminal.clear();const t=e.split("\n");for(const i of t)this.terminal.writeln(i);this.timeout("scroll",()=>this.terminal.scrollToBottom(),50)}}return e.\u0275fac=function(t){return mk(t||e)},e.\u0275cmp=pt({type:e,selectors:[["a-terminal"]],viewQuery:function(e,t){var i;1&e&&(jl(fk,!0),jl(pk,!0)),2&e&&(Nl(i=zl())&&(t.terminal_element=i.first),Nl(i=zl())&&(t.container_el=i.first))},inputs:{content:"content",resize:"resize"},features:[ma,Dt],decls:4,vars:0,consts:[["name","terminal",1,"w-full","h-full","overflow-hidden",3,"resize"],["container",""],[1,"terminal"],["terminal",""]],template:function(e,t){1&e&&(Eo(0,"div",0,1),Io("resize",(function(){return t.resizeTerminal()}),!1,Zi),Oo(2,"div",2,3),Ao())},styles:['[name="terminal"][_ngcontent-%COMP%] {\n background-color: #212121;\n }\n\n .terminal[_ngcontent-%COMP%] {\n max-width: 100%;\n min-height: 100%;\n }']}),e})();const mk=pr(_k);function gk(e,t){if(1&e&&(Ti(),Oo(0,"circle",3)),2&e){const e=Bo();Yo("animation-name","mat-progress-spinner-stroke-rotate-"+e._spinnerAnimationLabel)("stroke-dashoffset",e._getStrokeDashOffset(),"px")("stroke-dasharray",e._getStrokeCircumference(),"px")("stroke-width",e._getCircleStrokeWidth(),"%"),vo("r",e._getCircleRadius())}}function vk(e,t){if(1&e&&(Ti(),Oo(0,"circle",3)),2&e){const e=Bo();Yo("stroke-dashoffset",e._getStrokeDashOffset(),"px")("stroke-dasharray",e._getStrokeCircumference(),"px")("stroke-width",e._getCircleStrokeWidth(),"%"),vo("r",e._getCircleRadius())}}function yk(e,t){if(1&e&&(Ti(),Oo(0,"circle",3)),2&e){const e=Bo();Yo("animation-name","mat-progress-spinner-stroke-rotate-"+e._spinnerAnimationLabel)("stroke-dashoffset",e._getStrokeDashOffset(),"px")("stroke-dasharray",e._getStrokeCircumference(),"px")("stroke-width",e._getCircleStrokeWidth(),"%"),vo("r",e._getCircleRadius())}}function bk(e,t){if(1&e&&(Ti(),Oo(0,"circle",3)),2&e){const e=Bo();Yo("stroke-dashoffset",e._getStrokeDashOffset(),"px")("stroke-dasharray",e._getStrokeCircumference(),"px")("stroke-width",e._getCircleStrokeWidth(),"%"),vo("r",e._getCircleRadius())}}const wk=".mat-progress-spinner{display:block;position:relative}.mat-progress-spinner svg{position:absolute;transform:rotate(-90deg);top:0;left:0;transform-origin:center;overflow:visible}.mat-progress-spinner circle{fill:transparent;transform-origin:center;transition:stroke-dashoffset 225ms linear}._mat-animation-noopable.mat-progress-spinner circle{transition:none;animation:none}.cdk-high-contrast-active .mat-progress-spinner circle{stroke:currentColor}.mat-progress-spinner.mat-progress-spinner-indeterminate-animation[mode=indeterminate]{animation:mat-progress-spinner-linear-rotate 2000ms linear infinite}._mat-animation-noopable.mat-progress-spinner.mat-progress-spinner-indeterminate-animation[mode=indeterminate]{transition:none;animation:none}.mat-progress-spinner.mat-progress-spinner-indeterminate-animation[mode=indeterminate] circle{transition-property:stroke;animation-duration:4000ms;animation-timing-function:cubic-bezier(0.35, 0, 0.25, 1);animation-iteration-count:infinite}._mat-animation-noopable.mat-progress-spinner.mat-progress-spinner-indeterminate-animation[mode=indeterminate] circle{transition:none;animation:none}.mat-progress-spinner.mat-progress-spinner-indeterminate-fallback-animation[mode=indeterminate]{animation:mat-progress-spinner-stroke-rotate-fallback 10000ms cubic-bezier(0.87, 0.03, 0.33, 1) infinite}._mat-animation-noopable.mat-progress-spinner.mat-progress-spinner-indeterminate-fallback-animation[mode=indeterminate]{transition:none;animation:none}.mat-progress-spinner.mat-progress-spinner-indeterminate-fallback-animation[mode=indeterminate] circle{transition-property:stroke}._mat-animation-noopable.mat-progress-spinner.mat-progress-spinner-indeterminate-fallback-animation[mode=indeterminate] circle{transition:none;animation:none}@keyframes mat-progress-spinner-linear-rotate{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@keyframes mat-progress-spinner-stroke-rotate-100{0%{stroke-dashoffset:268.606171575px;transform:rotate(0)}12.5%{stroke-dashoffset:56.5486677px;transform:rotate(0)}12.5001%{stroke-dashoffset:56.5486677px;transform:rotateX(180deg) rotate(72.5deg)}25%{stroke-dashoffset:268.606171575px;transform:rotateX(180deg) rotate(72.5deg)}25.0001%{stroke-dashoffset:268.606171575px;transform:rotate(270deg)}37.5%{stroke-dashoffset:56.5486677px;transform:rotate(270deg)}37.5001%{stroke-dashoffset:56.5486677px;transform:rotateX(180deg) rotate(161.5deg)}50%{stroke-dashoffset:268.606171575px;transform:rotateX(180deg) rotate(161.5deg)}50.0001%{stroke-dashoffset:268.606171575px;transform:rotate(180deg)}62.5%{stroke-dashoffset:56.5486677px;transform:rotate(180deg)}62.5001%{stroke-dashoffset:56.5486677px;transform:rotateX(180deg) rotate(251.5deg)}75%{stroke-dashoffset:268.606171575px;transform:rotateX(180deg) rotate(251.5deg)}75.0001%{stroke-dashoffset:268.606171575px;transform:rotate(90deg)}87.5%{stroke-dashoffset:56.5486677px;transform:rotate(90deg)}87.5001%{stroke-dashoffset:56.5486677px;transform:rotateX(180deg) rotate(341.5deg)}100%{stroke-dashoffset:268.606171575px;transform:rotateX(180deg) rotate(341.5deg)}}@keyframes mat-progress-spinner-stroke-rotate-fallback{0%{transform:rotate(0deg)}25%{transform:rotate(1170deg)}50%{transform:rotate(2340deg)}75%{transform:rotate(3510deg)}100%{transform:rotate(4680deg)}}\n";class Ck{constructor(e){this._elementRef=e}}const Sk=sg(Ck,"primary"),kk=new Be("mat-progress-spinner-default-options",{providedIn:"root",factory:function(){return{diameter:100}}});let xk=(()=>{class e extends Sk{constructor(t,i,r,n,s){super(t),this._elementRef=t,this._document=r,this._diameter=100,this._value=0,this._fallbackAnimation=!1,this.mode="determinate";const o=e._diameters;this._spinnerAnimationLabel=this._getSpinnerAnimationLabel(),o.has(r.head)||o.set(r.head,new Set([100])),this._fallbackAnimation=i.EDGE||i.TRIDENT,this._noopAnimations="NoopAnimations"===n&&!!s&&!s._forceAnimations,s&&(s.diameter&&(this.diameter=s.diameter),s.strokeWidth&&(this.strokeWidth=s.strokeWidth))}get diameter(){return this._diameter}set diameter(e){this._diameter=n_(e),this._spinnerAnimationLabel=this._getSpinnerAnimationLabel(),!this._fallbackAnimation&&this._styleRoot&&this._attachStyleNode()}get strokeWidth(){return this._strokeWidth||this.diameter/10}set strokeWidth(e){this._strokeWidth=n_(e)}get value(){return"determinate"===this.mode?this._value:0}set value(e){this._value=Math.max(0,Math.min(100,n_(e)))}ngOnInit(){const e=this._elementRef.nativeElement;this._styleRoot=F_(e)||this._document.head,this._attachStyleNode(),e.classList.add(`mat-progress-spinner-indeterminate${this._fallbackAnimation?"-fallback":""}-animation`)}_getCircleRadius(){return(this.diameter-10)/2}_getViewBox(){const e=2*this._getCircleRadius()+this.strokeWidth;return`0 0 ${e} ${e}`}_getStrokeCircumference(){return 2*Math.PI*this._getCircleRadius()}_getStrokeDashOffset(){return"determinate"===this.mode?this._getStrokeCircumference()*(100-this._value)/100:this._fallbackAnimation&&"indeterminate"===this.mode?.2*this._getStrokeCircumference():null}_getCircleStrokeWidth(){return this.strokeWidth/this.diameter*100}_attachStyleNode(){const t=this._styleRoot,i=this._diameter,r=e._diameters;let n=r.get(t);if(!n||!n.has(i)){const e=this._document.createElement("style");e.setAttribute("mat-spinner-animation",this._spinnerAnimationLabel),e.textContent=this._getAnimationText(),t.appendChild(e),n||(n=new Set,r.set(t,n)),n.add(i)}}_getAnimationText(){const e=this._getStrokeCircumference();return"\n @keyframes mat-progress-spinner-stroke-rotate-DIAMETER {\n 0% { stroke-dashoffset: START_VALUE; transform: rotate(0); }\n 12.5% { stroke-dashoffset: END_VALUE; transform: rotate(0); }\n 12.5001% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(72.5deg); }\n 25% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(72.5deg); }\n\n 25.0001% { stroke-dashoffset: START_VALUE; transform: rotate(270deg); }\n 37.5% { stroke-dashoffset: END_VALUE; transform: rotate(270deg); }\n 37.5001% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(161.5deg); }\n 50% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(161.5deg); }\n\n 50.0001% { stroke-dashoffset: START_VALUE; transform: rotate(180deg); }\n 62.5% { stroke-dashoffset: END_VALUE; transform: rotate(180deg); }\n 62.5001% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(251.5deg); }\n 75% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(251.5deg); }\n\n 75.0001% { stroke-dashoffset: START_VALUE; transform: rotate(90deg); }\n 87.5% { stroke-dashoffset: END_VALUE; transform: rotate(90deg); }\n 87.5001% { stroke-dashoffset: END_VALUE; transform: rotateX(180deg) rotate(341.5deg); }\n 100% { stroke-dashoffset: START_VALUE; transform: rotateX(180deg) rotate(341.5deg); }\n }\n".replace(/START_VALUE/g,""+.95*e).replace(/END_VALUE/g,""+.2*e).replace(/DIAMETER/g,""+this._spinnerAnimationLabel)}_getSpinnerAnimationLabel(){return this.diameter.toString().replace(".","_")}}return e.\u0275fac=function(t){return new(t||e)(Co(Pa),Co(T_),Co(Kc,8),Co(Lf,8),Co(kk))},e.\u0275cmp=pt({type:e,selectors:[["mat-progress-spinner"]],hostAttrs:["role","progressbar",1,"mat-progress-spinner"],hostVars:10,hostBindings:function(e,t){2&e&&(vo("aria-valuemin","determinate"===t.mode?0:null)("aria-valuemax","determinate"===t.mode?100:null)("aria-valuenow","determinate"===t.mode?t.value:null)("mode",t.mode),Yo("width",t.diameter,"px")("height",t.diameter,"px"),Xo("_mat-animation-noopable",t._noopAnimations))},inputs:{color:"color",mode:"mode",diameter:"diameter",strokeWidth:"strokeWidth",value:"value"},exportAs:["matProgressSpinner"],features:[ma],decls:3,vars:8,consts:[["preserveAspectRatio","xMidYMid meet","focusable","false",3,"ngSwitch"],["cx","50%","cy","50%",3,"animation-name","stroke-dashoffset","stroke-dasharray","stroke-width",4,"ngSwitchCase"],["cx","50%","cy","50%",3,"stroke-dashoffset","stroke-dasharray","stroke-width",4,"ngSwitchCase"],["cx","50%","cy","50%"]],template:function(e,t){1&e&&(Ti(),Eo(0,"svg",0),bo(1,gk,1,9,"circle",1),bo(2,vk,1,7,"circle",2),Ao()),2&e&&(Yo("width",t.diameter,"px")("height",t.diameter,"px"),ko("ngSwitch","indeterminate"===t.mode),vo("viewBox",t._getViewBox()),gn(1),ko("ngSwitchCase",!0),gn(1),ko("ngSwitchCase",!1))},directives:[Sh,kh],styles:[wk],encapsulation:2,changeDetection:0}),e._diameters=new WeakMap,e})(),Ek=(()=>{class e extends xk{constructor(e,t,i,r,n){super(e,t,i,r,n),this.mode="indeterminate"}}return e.\u0275fac=function(t){return new(t||e)(Co(Pa),Co(T_),Co(Kc,8),Co(Lf,8),Co(kk))},e.\u0275cmp=pt({type:e,selectors:[["mat-spinner"]],hostAttrs:["role","progressbar","mode","indeterminate",1,"mat-spinner","mat-progress-spinner"],hostVars:6,hostBindings:function(e,t){2&e&&(Yo("width",t.diameter,"px")("height",t.diameter,"px"),Xo("_mat-animation-noopable",t._noopAnimations))},inputs:{color:"color"},features:[ma],decls:3,vars:8,consts:[["preserveAspectRatio","xMidYMid meet","focusable","false",3,"ngSwitch"],["cx","50%","cy","50%",3,"animation-name","stroke-dashoffset","stroke-dasharray","stroke-width",4,"ngSwitchCase"],["cx","50%","cy","50%",3,"stroke-dashoffset","stroke-dasharray","stroke-width",4,"ngSwitchCase"],["cx","50%","cy","50%"]],template:function(e,t){1&e&&(Ti(),Eo(0,"svg",0),bo(1,yk,1,9,"circle",1),bo(2,bk,1,7,"circle",2),Ao()),2&e&&(Yo("width",t.diameter,"px")("height",t.diameter,"px"),ko("ngSwitch","indeterminate"===t.mode),vo("viewBox",t._getViewBox()),gn(1),ko("ngSwitchCase",!0),gn(1),ko("ngSwitchCase",!1))},directives:[Sh,kh],styles:[wk],encapsulation:2,changeDetection:0}),e})(),Ak=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[rg,Lh],rg]}),e})();const Ok=["body"];function Tk(e,t){if(1&e&&Oo(0,"a-terminal",10),2&e){const e=Bo();ko("content",e.results||"No test results to display")("resize",e.fullscreen)}}function Rk(e,t){1&e&&(Eo(0,"div",11),Oo(1,"mat-spinner",12),Ao()),2&e&&(gn(1),ko("diameter",48))}let Lk=(()=>{class e extends uk{constructor(e,t){super(),this._build=e,this._tests=t,this.results="",this.fullscreen=!1,this.running=!1,this.runTests=()=>Cw(this,void 0,void 0,(function*(){this.running=!0,this.results=this.processResults(yield this._tests.runSpec({}).catch(e=>e)),this.running=!1}))}ngOnInit(){this.subscription("driver",this._build.active_driver.subscribe(()=>this.results=""))}processResults(e){e instanceof Object&&(e=e.error);const t=e.indexOf("exited with 0")>=0;return this._build.setTestStatus(t?"passed":"failed"),this.timeout("scroll",()=>this._body_el.nativeElement.scrollTo(0,this._body_el.nativeElement.scrollHeight),10),e}}return e.\u0275fac=function(t){return new(t||e)(Co(Ew),Co(Ow))},e.\u0275cmp=pt({type:e,selectors:[["workbench-output"]],viewQuery:function(e,t){var i;1&e&&Bl(Ok,!0),2&e&&Nl(i=zl())&&(t._body_el=i.first)},features:[ma],decls:12,vars:5,consts:[["name","output"],[1,"flex","items-center","p-2","w-full"],["mat-button","",3,"click"],[1,"flex-1","w-0"],["mat-icon-button","",3,"click"],[1,"material-icons"],[1,"flex-1","w-full","overflow-auto"],["body",""],[3,"content","resize",4,"ngIf"],["class","absolute inset-0 bg-white bg-opacity-25 flex items-center justify-center",4,"ngIf"],[3,"content","resize"],[1,"absolute","inset-0","bg-white","bg-opacity-25","flex","items-center","justify-center"],[3,"diameter"]],template:function(e,t){1&e&&(Eo(0,"div",0),Eo(1,"div",1),Eo(2,"button",2),Io("click",(function(){return t.runTests()})),ha(3,"Run!"),Ao(),Oo(4,"div",3),Eo(5,"button",4),Io("click",(function(){return t.fullscreen=!t.fullscreen})),Eo(6,"i",5),ha(7),Ao(),Ao(),Ao(),Eo(8,"div",6,7),bo(10,Tk,1,2,"a-terminal",8),Ao(),bo(11,Rk,2,1,"div",9),Ao()),2&e&&(Qo("absolute inset-0 flex flex-col border-t border-white bg-gray-800 text-white "+(t.fullscreen?"fullscreen":"")),gn(7),ua(t.fullscreen?"keyboard_arrow_down":"keyboard_arrow_up"),gn(3),ko("ngIf",!t.running),gn(1),ko("ngIf",t.running))},directives:[Ug,yh,_k,Ek],styles:['[_nghost-%COMP%] {\n position: relative;\n height: 100%;\n width: 100%;\n }\n\n [name="output"][_ngcontent-%COMP%] {\n transition: top 200ms;\n top: 0;\n }\n\n .fullscreen[_ngcontent-%COMP%] {\n top: -24rem;\n }']}),e})();function Pk(e,t){1&e&&(To(0),Oo(1,"workbench-form",2),Oo(2,"workbench-output",3),Ro())}function Dk(e,t){1&e&&(Eo(0,"div",4),Eo(1,"i",5),ha(2,"arrow_back"),Ao(),Eo(3,"p"),ha(4,"Select a driver from the sidebar to begin"),Ao(),Ao())}let Ik=(()=>{class e{constructor(e,t){this._route=e,this._build=t}ngOnInit(){this._route.paramMap.subscribe(e=>{e.has("repo")&&this._build.setRepository(e.get("repo")),e.has("driver")&&this._build.setDriver(e.get("driver")),this.driver=e.get("driver")||""})}}return e.\u0275fac=function(t){return new(t||e)(Co(Ny),Co(Ew))},e.\u0275cmp=pt({type:e,selectors:[["app-workbench"]],decls:3,vars:2,consts:[[4,"ngIf","ngIfElse"],["empty_state",""],[1,"w-full"],[1,"w-full","flex-1","h-0"],[1,"absolute","inset-0","flex","flex-col","items-center","justify-center"],[1,"material-icons","m-4"]],template:function(e,t){if(1&e&&(bo(0,Pk,3,0,"ng-container",0),bo(1,Dk,5,0,"ng-template",null,1,Gl)),2&e){const e=wo(2);ko("ngIf",t.driver)("ngIfElse",e)}},directives:[yh,hk,Lk],styles:["[_nghost-%COMP%] {\n position: relative;\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n }\n\n i[_ngcontent-%COMP%] {\n font-size: 1.5rem;\n }"]}),e})();const Mk=[{path:"",component:Ik},{path:":repo",component:Ik},{path:":repo/:driver",component:Ik},{path:"**",redirectTo:""}];let Fk,Nk,jk,Bk,Hk=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[fw.forRoot(Mk,{useHash:!0})],fw]}),e})();function Uk(e,t,i,r="debug",n=!1,s="Spec Runner"){if(window.debug||n){const n=["color: #E91E63","color: #3F51B5","color: default"];i?console[r](`%c[${s}]%c[${e}] %c${t}`,...n,i):console[r](`%c[${s}]%c[${e}] %c${t}`,...n)}}const Vk=function(){return["/"]};let qk=(()=>{class e{constructor(e){this._build=e,this.show_sidebar=this._build.sidebar,this.toggle=()=>this._build.toggleSidebar()}}return e.\u0275fac=function(t){return new(t||e)(Co(Ew))},e.\u0275cmp=pt({type:e,selectors:[["topbar-header"]],decls:9,vars:5,consts:[["mat-icon-button","",1,"text-white",3,"click"],[1,"material-icons"],[1,"h-full",3,"routerLink"],["src","assets/logo-dark.svg",1,"h-10"],[1,"px-4","text-white"],[1,"flex-1","min-w-0"]],template:function(e,t){1&e&&(Eo(0,"button",0),Io("click",(function(){return t.toggle()})),Eo(1,"i",1),ha(2),kl(3,"async"),Ao(),Ao(),Eo(4,"a",2),Oo(5,"img",3),Ao(),Eo(6,"h2",4),ha(7,"Driver Spec Runner"),Ao(),Oo(8,"div",5)),2&e&&(gn(2),ua(xl(3,2,t.show_sidebar)?"close":"menu"),gn(2),ko("routerLink",Cl(4,Vk)))},directives:[Ug,ew],pipes:[Rh],styles:["[_nghost-%COMP%] {\n display: flex;\n align-items: center;\n padding: .5rem;\n width: 100%;\n background-color: #0A0D2E;\n }\n\n h2[_ngcontent-%COMP%] {\n margin: 0;\n }"]}),e})();const zk=M_({passive:!0});let Wk=(()=>{class e{constructor(e,t){this._platform=e,this._ngZone=t,this._monitoredElements=new Map}monitor(e){if(!this._platform.isBrowser)return If;const t=a_(e),i=this._monitoredElements.get(t);if(i)return i.subject;const r=new S,n="cdk-text-field-autofilled",s=e=>{"cdk-text-field-autofill-start"!==e.animationName||t.classList.contains(n)?"cdk-text-field-autofill-end"===e.animationName&&t.classList.contains(n)&&(t.classList.remove(n),this._ngZone.run(()=>r.next({target:e.target,isAutofilled:!1}))):(t.classList.add(n),this._ngZone.run(()=>r.next({target:e.target,isAutofilled:!0})))};return this._ngZone.runOutsideAngular(()=>{t.addEventListener("animationstart",s,zk),t.classList.add("cdk-text-field-autofill-monitored")}),this._monitoredElements.set(t,{subject:r,unlisten:()=>{t.removeEventListener("animationstart",s,zk)}}),r}stopMonitoring(e){const t=a_(e),i=this._monitoredElements.get(t);i&&(i.unlisten(),i.subject.complete(),t.classList.remove("cdk-text-field-autofill-monitored"),t.classList.remove("cdk-text-field-autofilled"),this._monitoredElements.delete(t))}ngOnDestroy(){this._monitoredElements.forEach((e,t)=>this.stopMonitoring(t))}}return e.\u0275fac=function(t){return new(t||e)(Ze(T_),Ze(mc))},e.\u0275prov=ue({factory:function(){return new e(Ze(T_),Ze(mc))},token:e,providedIn:"root"}),e})(),$k=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[R_]]}),e})();const Kk=new Be("MAT_INPUT_VALUE_ACCESSOR"),Gk=["button","checkbox","file","hidden","image","radio","range","reset","submit"];let Zk=0;class Yk{constructor(e,t,i,r){this._defaultErrorStateMatcher=e,this._parentForm=t,this._parentFormGroup=i,this.ngControl=r}}const Xk=lg(Yk);let Qk=(()=>{class e extends Xk{constructor(e,t,i,r,n,s,o,a,l,c){super(s,r,n,i),this._elementRef=e,this._platform=t,this.ngControl=i,this._autofillMonitor=a,this._formField=c,this._uid="mat-input-"+Zk++,this.focused=!1,this.stateChanges=new S,this.controlType="mat-input",this.autofilled=!1,this._disabled=!1,this._required=!1,this._type="text",this._readonly=!1,this._neverEmptyInputTypes=["date","datetime","datetime-local","month","time","week"].filter(e=>P_().has(e));const h=this._elementRef.nativeElement,u=h.nodeName.toLowerCase();this._inputValueAccessor=o||h,this._previousNativeValue=this.value,this.id=this.id,t.IOS&&l.runOutsideAngular(()=>{e.nativeElement.addEventListener("keyup",e=>{let t=e.target;t.value||t.selectionStart||t.selectionEnd||(t.setSelectionRange(1,1),t.setSelectionRange(0,0))})}),this._isServer=!this._platform.isBrowser,this._isNativeSelect="select"===u,this._isTextarea="textarea"===u,this._isNativeSelect&&(this.controlType=h.multiple?"mat-native-select-multiple":"mat-native-select")}get disabled(){return this.ngControl&&null!==this.ngControl.disabled?this.ngControl.disabled:this._disabled}set disabled(e){this._disabled=r_(e),this.focused&&(this.focused=!1,this.stateChanges.next())}get id(){return this._id}set id(e){this._id=e||this._uid}get required(){return this._required}set required(e){this._required=r_(e)}get type(){return this._type}set type(e){this._type=e||"text",this._validateType(),!this._isTextarea&&P_().has(this._type)&&(this._elementRef.nativeElement.type=this._type)}get value(){return this._inputValueAccessor.value}set value(e){e!==this.value&&(this._inputValueAccessor.value=e,this.stateChanges.next())}get readonly(){return this._readonly}set readonly(e){this._readonly=r_(e)}ngAfterViewInit(){this._platform.isBrowser&&this._autofillMonitor.monitor(this._elementRef.nativeElement).subscribe(e=>{this.autofilled=e.isAutofilled,this.stateChanges.next()})}ngOnChanges(){this.stateChanges.next()}ngOnDestroy(){this.stateChanges.complete(),this._platform.isBrowser&&this._autofillMonitor.stopMonitoring(this._elementRef.nativeElement)}ngDoCheck(){this.ngControl&&this.updateErrorState(),this._dirtyCheckNativeValue(),this._dirtyCheckPlaceholder()}focus(e){this._elementRef.nativeElement.focus(e)}_focusChanged(e){e===this.focused||this.readonly&&e||(this.focused=e,this.stateChanges.next())}_onInput(){}_dirtyCheckPlaceholder(){var e,t;const i=(null===(t=null===(e=this._formField)||void 0===e?void 0:e._hideControlPlaceholder)||void 0===t?void 0:t.call(e))?null:this.placeholder;if(i!==this._previousPlaceholder){const e=this._elementRef.nativeElement;this._previousPlaceholder=i,i?e.setAttribute("placeholder",i):e.removeAttribute("placeholder")}}_dirtyCheckNativeValue(){const e=this._elementRef.nativeElement.value;this._previousNativeValue!==e&&(this._previousNativeValue=e,this.stateChanges.next())}_validateType(){Gk.indexOf(this._type)}_isNeverEmpty(){return this._neverEmptyInputTypes.indexOf(this._type)>-1}_isBadInput(){let e=this._elementRef.nativeElement.validity;return e&&e.badInput}get empty(){return!(this._isNeverEmpty()||this._elementRef.nativeElement.value||this._isBadInput()||this.autofilled)}get shouldLabelFloat(){if(this._isNativeSelect){const e=this._elementRef.nativeElement,t=e.options[0];return this.focused||e.multiple||!this.empty||!!(e.selectedIndex>-1&&t&&t.label)}return this.focused||!this.empty}setDescribedByIds(e){e.length?this._elementRef.nativeElement.setAttribute("aria-describedby",e.join(" ")):this._elementRef.nativeElement.removeAttribute("aria-describedby")}onContainerClick(){this.focused||this.focus()}}return e.\u0275fac=function(t){return new(t||e)(Co(Pa),Co(T_),Co(yC,10),Co(dS,8),Co(SS,8),Co(cg),Co(Kk,10),Co(Wk),Co(mc),Co(sC,8))},e.\u0275dir=bt({type:e,selectors:[["input","matInput",""],["textarea","matInput",""],["select","matNativeControl",""],["input","matNativeControl",""],["textarea","matNativeControl",""]],hostAttrs:[1,"mat-input-element","mat-form-field-autofill-control"],hostVars:9,hostBindings:function(e,t){1&e&&Io("focus",(function(){return t._focusChanged(!0)}))("blur",(function(){return t._focusChanged(!1)}))("input",(function(){return t._onInput()})),2&e&&(fa("disabled",t.disabled)("required",t.required),vo("id",t.id)("data-placeholder",t.placeholder)("readonly",t.readonly&&!t._isNativeSelect||null)("aria-invalid",t.errorState)("aria-required",t.required.toString()),Xo("mat-input-server",t._isServer))},inputs:{id:"id",disabled:"disabled",required:"required",type:"type",value:"value",readonly:"readonly",placeholder:"placeholder",errorStateMatcher:"errorStateMatcher",userAriaDescribedBy:["aria-describedby","userAriaDescribedBy"]},exportAs:["matInput"],features:[Oa([{provide:Gw,useExisting:e}]),ma,Dt]}),e})(),Jk=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[cg],imports:[[$k,aC],$k,aC]}),e})(),ex=(()=>{class e{transform(e){if(e.indexOf("/")>=0){const t=e.split("/");return t.splice(0,1),`
${t.map(e=>`
${e}
`).join('
keyboard_arrow_right
')}
`}return e}}return e.\u0275fac=function(t){return new(t||e)},e.\u0275pipe=wt({name:"driverFormat",type:e,pure:!0}),e})();function tx(e,t){if(1&e&&(Eo(0,"mat-option",9),ha(1),Ao()),2&e){const e=t.$implicit;ko("value",e),gn(1),da(" ",e," ")}}const ix=function(e,t){return[e,t]};function rx(e,t){if(1&e){const e=Lo();Eo(0,"a",11),Io("click",(function(){ni(e);const i=t.$implicit;return Bo(2).setDriver(i)})),Eo(1,"div",12),Oo(2,"div",13),kl(3,"async"),kl(4,"async"),Oo(5,"div",14),kl(6,"driverFormat"),Ao(),Ao()}if(2&e){const e=t.$implicit,a=Bo(2);ko("routerLink",(i=10,r=ix,n="/"+a.repo,s=e,function(e,t,i,r,n,s,o){const a=t+i;return function(e,t,i,r){const n=go(e,t,i);return go(e,t+1,r)||n}(e,a,n,s)?mo(e,a+2,o?r.call(o,n,s):r(n,s)):Sl(e,a+2)}(ii(),ui(),i,r,n,s,o))),gn(2),Qo("mr-4 h-2 w-2 rounded-full border border-white "+(xl(3,4,a.statues)[a.repo+"|"+e]?xl(4,6,a.statues)[a.repo+"|"+e]:"")),gn(3),ko("innerHTML",xl(6,8,e),Gr)}var i,r,n,s,o}function nx(e,t){if(1&e&&(To(0),bo(1,rx,7,13,"a",10),kl(2,"async"),Ro()),2&e){const e=Bo();gn(1),ko("ngForOf",xl(2,1,e.drivers))}}function sx(e,t){1&e&&(Eo(0,"p",15),ha(1,"No Drivers"),Ao())}let ox=(()=>{class e{constructor(e){this._build=e,this._search_filter=new pv(""),this.repos=this._build.repositories,this.drivers=Wg([this._build.driver_list,this._search_filter]).pipe(M(e=>{const[t,i]=e;return t.filter(e=>e.toLowerCase().includes(i.toLowerCase()))})),this.statues=this._build.test_statuses,this.setRepo=e=>this._build.setRepository(e),this.setDriver=e=>this._build.setDriver(e),this.setFilter=e=>this._search_filter.next(e),this.search_str=""}get repo(){return this._build.getRepository()}}return e.\u0275fac=function(t){return new(t||e)(Co(Ew))},e.\u0275cmp=pt({type:e,selectors:[["sidebar"]],decls:13,vars:9,consts:[["appearance","outline",1,"m-2"],[3,"ngModel","ngModelChange"],[3,"value",4,"ngFor","ngForOf"],["appearance","outline",1,"mb-2","mx-2"],["matPrefix","",1,"material-icons"],["matInput","","placeholder","Filter drivers...",3,"ngModel","ngModelChange"],[1,"overflow-y-auto","flex-1","border-t","border-gray-300"],[4,"ngIf","ngIfElse"],["empty_state",""],[3,"value"],["mat-button","","class","w-full border-gray-200","routerLinkActive","active",3,"routerLink","click",4,"ngFor","ngForOf"],["mat-button","","routerLinkActive","active",1,"w-full","border-gray-200",3,"routerLink","click"],[1,"flex","items-center","my-2"],["name","dot"],[3,"innerHTML"],[1,"p-2","w-full","text-center"]],template:function(e,t){if(1&e&&(Eo(0,"mat-form-field",0),Eo(1,"mat-select",1),Io("ngModelChange",(function(e){return t.setRepo(e)})),bo(2,tx,2,2,"mat-option",2),kl(3,"async"),Ao(),Ao(),Eo(4,"mat-form-field",3),Eo(5,"i",4),ha(6,"search"),Ao(),Eo(7,"input",5),Io("ngModelChange",(function(e){return t.search_str=e}))("ngModelChange",(function(e){return t.setFilter(e)})),Ao(),Ao(),Eo(8,"div",6),bo(9,nx,3,3,"ng-container",7),kl(10,"async"),Ao(),bo(11,sx,2,0,"ng-template",null,8,Gl)),2&e){const e=wo(12);var i;gn(1),ko("ngModel",t.repo),gn(1),ko("ngForOf",xl(3,5,t.repos)),gn(5),ko("ngModel",t.search_str),gn(2),ko("ngIf",null==(i=xl(10,7,t.drivers))?null:i.length)("ngIfElse",e)}},directives:[oC,$S,bC,bS,gh,Jw,Qk,pC,yh,Lg,Vg,ew,iw],pipes:[Rh,ex],styles:['[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n max-width: 20rem;\n }\n\n mat-form-field[_ngcontent-%COMP%] {\n height: 3.5rem;\n }\n\n p[_ngcontent-%COMP%] {\n margin: 1rem 0;\n }\n\n [name="dot"][_ngcontent-%COMP%] {\n background-color: #ffb300;\n }\n\n [name="dot"].failed[_ngcontent-%COMP%] {\n background-color: #d50000;\n }\n\n [name="dot"].passed[_ngcontent-%COMP%] {\n background-color: #43a047;\n }\n\n a[_ngcontent-%COMP%] {\n border-bottom: 1px solid #edf2f7;\n border-radius: 0;\n }\n\n a.active[_ngcontent-%COMP%] {\n background-color: #c92366;\n color: #fff;\n }']}),e})(),ax=(()=>{class e{constructor(e,t,i){this._snackbar=e,this._cache=t,this._build=i,this.show_sidebar=this._build.sidebar}ngOnInit(){Fk=this._snackbar,function(e,t=(()=>null),i=3e5){e.isEnabled&&(Nk&&Nk.unsubscribe(),jk&&jk.unsubscribe(),Bk&&clearInterval(Bk),Nk=e.available.subscribe(e=>{Uk("CACHE",`Update available: ${"current version is "+e.current.hash} ${"available version is "+e.available.hash}`),function(){this._cache.isEnabled&&(Uk("CACHE","Activating changes to the cache..."),this._cache.activateUpdate().then(()=>{var e,t;e="Newer version of the application is available",t=()=>location.reload(!0),console.info(e),function(e,t,i="OK",r,n={type:"icon",class:"material-icons",content:"info"}){if(!Fk)throw new Error("Snackbar service hasn't been initialised");const s=Fk.open(t,i,{panelClass:[e],duration:5e3});i&&(r=r||(()=>s.dismiss()),s.onAction().subscribe(()=>r()))}("info",e,"Refresh",t)}))}()}),jk=e.activated.subscribe(()=>{Uk("CACHE","Updates activated. Reloading..."),t("Newer version of the application is available",()=>location.reload(!0))}),Bk=setInterval(()=>{Uk("CACHE","Checking for updates..."),this._cache.checkForUpdate()},i))}(this._cache)}}return e.\u0275fac=function(t){return new(t||e)(Co(fv),Co(mp),Co(Ew))},e.\u0275cmp=pt({type:e,selectors:[["app-root"]],decls:7,vars:4,consts:[[1,"absolute","inset-0","overflow-hidden","flex","flex-col"],[1,"z-20"],[1,"flex","flex-1","w-full",2,"height","50%"],[1,"h-full","shadow","z-10","overflow-hidden"],["name","content",1,"h-full","flex-1","w-1/2","bg-gray-200","z-0"]],template:function(e,t){1&e&&(Eo(0,"div",0),Oo(1,"topbar-header",1),Eo(2,"div",2),Eo(3,"sidebar",3),kl(4,"async"),Ao(),Eo(5,"div",4),Oo(6,"router-outlet"),Ao(),Ao(),Ao()),2&e&&(gn(3),Xo("show",xl(4,2,t.show_sidebar)))},directives:[qk,ox,rw],pipes:[Rh],styles:[".formatted-driver-name,.formatted-driver-name .icon{display:flex;align-items:center}.formatted-driver-name .icon{justify-content:center;height:1.2em;width:1.2em;font-size:1.5em}.xterm-helper-textarea{opacity:0}",".mat-form-field .mat-select-value,.mat-form-field input{position:relative;top:-4px}.dark-mode a[button],.dark-mode button.mat-button,a[button],button.mat-button{background-color:#c92366;border:1px solid #c92366;color:#fff;min-height:3em}.dark-mode a[button].inverse,.dark-mode button.mat-button.inverse,a[button].inverse,button.mat-button.inverse{background-color:#fff;color:#c92366}.dark-mode a[button].success,.dark-mode button.mat-button.success,a[button].success,button.mat-button.success{background-color:#43a047;border-color:#43a047}.dark-mode a[button].clear,.dark-mode button.mat-button.clear,a[button].clear,button.mat-button.clear{background:none;border:none;color:rgba(0,0,0,.85)}.dark-mode a[button].error,.dark-mode button.mat-button.error,a[button].error,button.mat-button.error{background:none;color:#e53935;border-color:#e53935}.dark-mode a[button][disabled],.dark-mode button.mat-button[disabled],a[button][disabled],button.mat-button[disabled]{background-color:#ccc;border-color:rgba(0,0,0,.1);pointer-events:none}.mat-spinner circle{stroke:#c92366}.xterm{padding:1rem}sidebar{width:0;transition:width .2s}sidebar.show{width:20rem}","label{font-size:.75rem;font-weight:600}"],encapsulation:2}),e})();const lx={provide:new Be("mat-autocomplete-scroll-strategy"),deps:[Em],useFactory:function(e){return()=>e.scrollStrategies.reposition()}};let cx=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[lx],imports:[[Pm,Dg,rg,Lh],q_,Dg,rg]}),e})();const hx=[aC,Jk,cx,qg,KS,ok,Ak],ux=[ES,AS];let dx=(()=>{class e{}return e.\u0275mod=vt({type:e}),e.\u0275inj=de({factory:function(t){return new(t||e)},imports:[[Lh,fw,...hx,...ux],aC,Jk,cx,qg,KS,ok,Ak,ES,AS]}),e})(),fx=(()=>{class e{}return e.\u0275mod=vt({type:e,bootstrap:[ax]}),e.\u0275inj=de({factory:function(t){return new(t||e)},providers:[],imports:[[cu,Hk,Df,uv,i_,dx,Sp.register("ngsw-worker.js",{enabled:!0})]]}),e})();(function(){if(Sr)throw new Error("Cannot enable prod mode after platform setup.");Cr=!1})(),au().bootstrapModule(fx).catch(e=>console.error(e))},zn8P:function(e,t){function i(e){return Promise.resolve().then((function(){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}))}i.keys=function(){return[]},i.resolve=i,e.exports=i,i.id="zn8P"}},[[0,0]]]); \ No newline at end of file diff --git a/www/manifest.webmanifest b/www/manifest.webmanifest new file mode 100644 index 00000000000..f1a06c80b03 --- /dev/null +++ b/www/manifest.webmanifest @@ -0,0 +1,59 @@ +{ + "name": "driver-spec-runner", + "short_name": "driver-spec-runner", + "theme_color": "#1976d2", + "background_color": "#fafafa", + "display": "standalone", + "scope": "./", + "start_url": "./", + "icons": [ + { + "src": "assets/icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-128x128.png", + "sizes": "128x128", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-384x384.png", + "sizes": "384x384", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable any" + } + ] +} diff --git a/www/ngsw-worker.js b/www/ngsw-worker.js new file mode 100644 index 00000000000..7bab3303433 --- /dev/null +++ b/www/ngsw-worker.js @@ -0,0 +1,2819 @@ +(function () { + 'use strict'; + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + /** + * Adapts the service worker to its runtime environment. + * + * Mostly, this is used to mock out identifiers which are otherwise read + * from the global scope. + */ + class Adapter { + constructor(scopeUrl) { + this.scopeUrl = scopeUrl; + const parsedScopeUrl = this.parseUrl(this.scopeUrl); + // Determine the origin from the registration scope. This is used to differentiate between + // relative and absolute URLs. + this.origin = parsedScopeUrl.origin; + // Suffixing `ngsw` with the baseHref to avoid clash of cache names for SWs with different + // scopes on the same domain. + this.cacheNamePrefix = 'ngsw:' + parsedScopeUrl.path; + } + /** + * Wrapper around the `Request` constructor. + */ + newRequest(input, init) { + return new Request(input, init); + } + /** + * Wrapper around the `Response` constructor. + */ + newResponse(body, init) { + return new Response(body, init); + } + /** + * Wrapper around the `Headers` constructor. + */ + newHeaders(headers) { + return new Headers(headers); + } + /** + * Test if a given object is an instance of `Client`. + */ + isClient(source) { + return (source instanceof Client); + } + /** + * Read the current UNIX time in milliseconds. + */ + get time() { + return Date.now(); + } + /** + * Get a normalized representation of a URL such as those found in the ServiceWorker's `ngsw.json` + * configuration. + * + * More specifically: + * 1. Resolve the URL relative to the ServiceWorker's scope. + * 2. If the URL is relative to the ServiceWorker's own origin, then only return the path part. + * Otherwise, return the full URL. + * + * @param url The raw request URL. + * @return A normalized representation of the URL. + */ + normalizeUrl(url) { + // Check the URL's origin against the ServiceWorker's. + const parsed = this.parseUrl(url, this.scopeUrl); + return (parsed.origin === this.origin ? parsed.path : url); + } + /** + * Parse a URL into its different parts, such as `origin`, `path` and `search`. + */ + parseUrl(url, relativeTo) { + // Workaround a Safari bug, see + // https://github.com/angular/angular/issues/31061#issuecomment-503637978 + const parsed = !relativeTo ? new URL(url) : new URL(url, relativeTo); + return { origin: parsed.origin, path: parsed.pathname, search: parsed.search }; + } + /** + * Wait for a given amount of time before completing a Promise. + */ + timeout(ms) { + return new Promise(resolve => { + setTimeout(() => resolve(), ms); + }); + } + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + /** + * An error returned in rejected promises if the given key is not found in the table. + */ + class NotFound { + constructor(table, key) { + this.table = table; + this.key = key; + } + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + /** + * An implementation of a `Database` that uses the `CacheStorage` API to serialize + * state within mock `Response` objects. + */ + class CacheDatabase { + constructor(scope, adapter) { + this.scope = scope; + this.adapter = adapter; + this.tables = new Map(); + } + 'delete'(name) { + if (this.tables.has(name)) { + this.tables.delete(name); + } + return this.scope.caches.delete(`${this.adapter.cacheNamePrefix}:db:${name}`); + } + list() { + return this.scope.caches.keys().then(keys => keys.filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:db:`))); + } + open(name, cacheQueryOptions) { + if (!this.tables.has(name)) { + const table = this.scope.caches.open(`${this.adapter.cacheNamePrefix}:db:${name}`) + .then(cache => new CacheTable(name, cache, this.adapter, cacheQueryOptions)); + this.tables.set(name, table); + } + return this.tables.get(name); + } + } + /** + * A `Table` backed by a `Cache`. + */ + class CacheTable { + constructor(table, cache, adapter, cacheQueryOptions) { + this.table = table; + this.cache = cache; + this.adapter = adapter; + this.cacheQueryOptions = cacheQueryOptions; + } + request(key) { + return this.adapter.newRequest('/' + key); + } + 'delete'(key) { + return this.cache.delete(this.request(key), this.cacheQueryOptions); + } + keys() { + return this.cache.keys().then(requests => requests.map(req => req.url.substr(1))); + } + read(key) { + return this.cache.match(this.request(key), this.cacheQueryOptions).then(res => { + if (res === undefined) { + return Promise.reject(new NotFound(this.table, key)); + } + return res.json(); + }); + } + write(key, value) { + return this.cache.put(this.request(key), this.adapter.newResponse(JSON.stringify(value))); + } + } + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { + step(generator.next(value)); + } + catch (e) { + reject(e); + } } + function rejected(value) { try { + step(generator["throw"](value)); + } + catch (e) { + reject(e); + } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + var UpdateCacheStatus = /*@__PURE__*/ (function (UpdateCacheStatus) { + UpdateCacheStatus[UpdateCacheStatus["NOT_CACHED"] = 0] = "NOT_CACHED"; + UpdateCacheStatus[UpdateCacheStatus["CACHED_BUT_UNUSED"] = 1] = "CACHED_BUT_UNUSED"; + UpdateCacheStatus[UpdateCacheStatus["CACHED"] = 2] = "CACHED"; + return UpdateCacheStatus; + })({}); + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + class SwCriticalError extends Error { + constructor() { + super(...arguments); + this.isCritical = true; + } + } + function errorToString(error) { + if (error instanceof Error) { + return `${error.message}\n${error.stack}`; + } + else { + return `${error}`; + } + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + /** + * Compute the SHA1 of the given string + * + * see http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf + * + * WARNING: this function has not been designed not tested with security in mind. + * DO NOT USE IT IN A SECURITY SENSITIVE CONTEXT. + * + * Borrowed from @angular/compiler/src/i18n/digest.ts + */ + function sha1(str) { + const utf8 = str; + const words32 = stringToWords32(utf8, Endian.Big); + return _sha1(words32, utf8.length * 8); + } + function sha1Binary(buffer) { + const words32 = arrayBufferToWords32(buffer, Endian.Big); + return _sha1(words32, buffer.byteLength * 8); + } + function _sha1(words32, len) { + const w = []; + let [a, b, c, d, e] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]; + words32[len >> 5] |= 0x80 << (24 - len % 32); + words32[((len + 64 >> 9) << 4) + 15] = len; + for (let i = 0; i < words32.length; i += 16) { + const [h0, h1, h2, h3, h4] = [a, b, c, d, e]; + for (let j = 0; j < 80; j++) { + if (j < 16) { + w[j] = words32[i + j]; + } + else { + w[j] = rol32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1); + } + const [f, k] = fk(j, b, c, d); + const temp = [rol32(a, 5), f, e, k, w[j]].reduce(add32); + [e, d, c, b, a] = [d, c, rol32(b, 30), a, temp]; + } + [a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)]; + } + return byteStringToHexString(words32ToByteString([a, b, c, d, e])); + } + function add32(a, b) { + return add32to64(a, b)[1]; + } + function add32to64(a, b) { + const low = (a & 0xffff) + (b & 0xffff); + const high = (a >>> 16) + (b >>> 16) + (low >>> 16); + return [high >>> 16, (high << 16) | (low & 0xffff)]; + } + // Rotate a 32b number left `count` position + function rol32(a, count) { + return (a << count) | (a >>> (32 - count)); + } + var Endian = /*@__PURE__*/ (function (Endian) { + Endian[Endian["Little"] = 0] = "Little"; + Endian[Endian["Big"] = 1] = "Big"; + return Endian; + })({}); + function fk(index, b, c, d) { + if (index < 20) { + return [(b & c) | (~b & d), 0x5a827999]; + } + if (index < 40) { + return [b ^ c ^ d, 0x6ed9eba1]; + } + if (index < 60) { + return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc]; + } + return [b ^ c ^ d, 0xca62c1d6]; + } + function stringToWords32(str, endian) { + const size = (str.length + 3) >>> 2; + const words32 = []; + for (let i = 0; i < size; i++) { + words32[i] = wordAt(str, i * 4, endian); + } + return words32; + } + function arrayBufferToWords32(buffer, endian) { + const size = (buffer.byteLength + 3) >>> 2; + const words32 = []; + const view = new Uint8Array(buffer); + for (let i = 0; i < size; i++) { + words32[i] = wordAt(view, i * 4, endian); + } + return words32; + } + function byteAt(str, index) { + if (typeof str === 'string') { + return index >= str.length ? 0 : str.charCodeAt(index) & 0xff; + } + else { + return index >= str.byteLength ? 0 : str[index] & 0xff; + } + } + function wordAt(str, index, endian) { + let word = 0; + if (endian === Endian.Big) { + for (let i = 0; i < 4; i++) { + word += byteAt(str, index + i) << (24 - 8 * i); + } + } + else { + for (let i = 0; i < 4; i++) { + word += byteAt(str, index + i) << 8 * i; + } + } + return word; + } + function words32ToByteString(words32) { + return words32.reduce((str, word) => str + word32ToByteString(word), ''); + } + function word32ToByteString(word) { + let str = ''; + for (let i = 0; i < 4; i++) { + str += String.fromCharCode((word >>> 8 * (3 - i)) & 0xff); + } + return str; + } + function byteStringToHexString(str) { + let hex = ''; + for (let i = 0; i < str.length; i++) { + const b = byteAt(str, i); + hex += (b >>> 4).toString(16) + (b & 0x0f).toString(16); + } + return hex.toLowerCase(); + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + /** + * A group of assets that are cached in a `Cache` and managed by a given policy. + * + * Concrete classes derive from this base and specify the exact caching policy. + */ + class AssetGroup { + constructor(scope, adapter, idle, config, hashes, db, prefix) { + this.scope = scope; + this.adapter = adapter; + this.idle = idle; + this.config = config; + this.hashes = hashes; + this.db = db; + this.prefix = prefix; + /** + * A deduplication cache, to make sure the SW never makes two network requests + * for the same resource at once. Managed by `fetchAndCacheOnce`. + */ + this.inFlightRequests = new Map(); + /** + * Normalized resource URLs. + */ + this.urls = []; + /** + * Regular expression patterns. + */ + this.patterns = []; + this.name = config.name; + // Normalize the config's URLs to take the ServiceWorker's scope into account. + this.urls = config.urls.map(url => adapter.normalizeUrl(url)); + // Patterns in the config are regular expressions disguised as strings. Breathe life into them. + this.patterns = config.patterns.map(pattern => new RegExp(pattern)); + // This is the primary cache, which holds all of the cached requests for this group. If a + // resource + // isn't in this cache, it hasn't been fetched yet. + this.cache = scope.caches.open(`${this.prefix}:${config.name}:cache`); + // This is the metadata table, which holds specific information for each cached URL, such as + // the timestamp of when it was added to the cache. + this.metadata = this.db.open(`${this.prefix}:${config.name}:meta`, config.cacheQueryOptions); + } + cacheStatus(url) { + return __awaiter(this, void 0, void 0, function* () { + const cache = yield this.cache; + const meta = yield this.metadata; + const req = this.adapter.newRequest(url); + const res = yield cache.match(req, this.config.cacheQueryOptions); + if (res === undefined) { + return UpdateCacheStatus.NOT_CACHED; + } + try { + const data = yield meta.read(req.url); + if (!data.used) { + return UpdateCacheStatus.CACHED_BUT_UNUSED; + } + } + catch (_) { + // Error on the side of safety and assume cached. + } + return UpdateCacheStatus.CACHED; + }); + } + /** + * Clean up all the cached data for this group. + */ + cleanup() { + return __awaiter(this, void 0, void 0, function* () { + yield this.scope.caches.delete(`${this.prefix}:${this.config.name}:cache`); + yield this.db.delete(`${this.prefix}:${this.config.name}:meta`); + }); + } + /** + * Process a request for a given resource and return it, or return null if it's not available. + */ + handleFetch(req, ctx) { + return __awaiter(this, void 0, void 0, function* () { + const url = this.adapter.normalizeUrl(req.url); + // Either the request matches one of the known resource URLs, one of the patterns for + // dynamically matched URLs, or neither. Determine which is the case for this request in + // order to decide how to handle it. + if (this.urls.indexOf(url) !== -1 || this.patterns.some(pattern => pattern.test(url))) { + // This URL matches a known resource. Either it's been cached already or it's missing, in + // which case it needs to be loaded from the network. + // Open the cache to check whether this resource is present. + const cache = yield this.cache; + // Look for a cached response. If one exists, it can be used to resolve the fetch + // operation. + const cachedResponse = yield cache.match(req, this.config.cacheQueryOptions); + if (cachedResponse !== undefined) { + // A response has already been cached (which presumably matches the hash for this + // resource). Check whether it's safe to serve this resource from cache. + if (this.hashes.has(url)) { + // This resource has a hash, and thus is versioned by the manifest. It's safe to return + // the response. + return cachedResponse; + } + else { + // This resource has no hash, and yet exists in the cache. Check how old this request is + // to make sure it's still usable. + if (yield this.needToRevalidate(req, cachedResponse)) { + this.idle.schedule(`revalidate(${this.prefix}, ${this.config.name}): ${req.url}`, () => __awaiter(this, void 0, void 0, function* () { + yield this.fetchAndCacheOnce(req); + })); + } + // In either case (revalidation or not), the cached response must be good. + return cachedResponse; + } + } + // No already-cached response exists, so attempt a fetch/cache operation. The original request + // may specify things like credential inclusion, but for assets these are not honored in order + // to avoid issues with opaque responses. The SW requests the data itself. + const res = yield this.fetchAndCacheOnce(this.adapter.newRequest(req.url)); + // If this is successful, the response needs to be cloned as it might be used to respond to + // multiple fetch operations at the same time. + return res.clone(); + } + else { + return null; + } + }); + } + /** + * Some resources are cached without a hash, meaning that their expiration is controlled + * by HTTP caching headers. Check whether the given request/response pair is still valid + * per the caching headers. + */ + needToRevalidate(req, res) { + return __awaiter(this, void 0, void 0, function* () { + // Three different strategies apply here: + // 1) The request has a Cache-Control header, and thus expiration needs to be based on its age. + // 2) The request has an Expires header, and expiration is based on the current timestamp. + // 3) The request has no applicable caching headers, and must be revalidated. + if (res.headers.has('Cache-Control')) { + // Figure out if there is a max-age directive in the Cache-Control header. + const cacheControl = res.headers.get('Cache-Control'); + const cacheDirectives = cacheControl + // Directives are comma-separated within the Cache-Control header value. + .split(',') + // Make sure each directive doesn't have extraneous whitespace. + .map(v => v.trim()) + // Some directives have values (like maxage and s-maxage) + .map(v => v.split('=')); + // Lowercase all the directive names. + cacheDirectives.forEach(v => v[0] = v[0].toLowerCase()); + // Find the max-age directive, if one exists. + const maxAgeDirective = cacheDirectives.find(v => v[0] === 'max-age'); + const cacheAge = maxAgeDirective ? maxAgeDirective[1] : undefined; + if (!cacheAge) { + // No usable TTL defined. Must assume that the response is stale. + return true; + } + try { + const maxAge = 1000 * parseInt(cacheAge); + // Determine the origin time of this request. If the SW has metadata on the request (which + // it + // should), it will have the time the request was added to the cache. If it doesn't for some + // reason, the request may have a Date header which will serve the same purpose. + let ts; + try { + // Check the metadata table. If a timestamp is there, use it. + const metaTable = yield this.metadata; + ts = (yield metaTable.read(req.url)).ts; + } + catch (_a) { + // Otherwise, look for a Date header. + const date = res.headers.get('Date'); + if (date === null) { + // Unable to determine when this response was created. Assume that it's stale, and + // revalidate it. + return true; + } + ts = Date.parse(date); + } + const age = this.adapter.time - ts; + return age < 0 || age > maxAge; + } + catch (_b) { + // Assume stale. + return true; + } + } + else if (res.headers.has('Expires')) { + // Determine if the expiration time has passed. + const expiresStr = res.headers.get('Expires'); + try { + // The request needs to be revalidated if the current time is later than the expiration + // time, if it parses correctly. + return this.adapter.time > Date.parse(expiresStr); + } + catch (_c) { + // The expiration date failed to parse, so revalidate as a precaution. + return true; + } + } + else { + // No way to evaluate staleness, so assume the response is already stale. + return true; + } + }); + } + /** + * Fetch the complete state of a cached resource, or return null if it's not found. + */ + fetchFromCacheOnly(url) { + return __awaiter(this, void 0, void 0, function* () { + const cache = yield this.cache; + const metaTable = yield this.metadata; + // Lookup the response in the cache. + const request = this.adapter.newRequest(url); + const response = yield cache.match(request, this.config.cacheQueryOptions); + if (response === undefined) { + // It's not found, return null. + return null; + } + // Next, lookup the cached metadata. + let metadata = undefined; + try { + metadata = yield metaTable.read(request.url); + } + catch (_a) { + // Do nothing, not found. This shouldn't happen, but it can be handled. + } + // Return both the response and any available metadata. + return { response, metadata }; + }); + } + /** + * Lookup all resources currently stored in the cache which have no associated hash. + */ + unhashedResources() { + return __awaiter(this, void 0, void 0, function* () { + const cache = yield this.cache; + // Start with the set of all cached requests. + return (yield cache.keys()) + // Normalize their URLs. + .map(request => this.adapter.normalizeUrl(request.url)) + // Exclude the URLs which have hashes. + .filter(url => !this.hashes.has(url)); + }); + } + /** + * Fetch the given resource from the network, and cache it if able. + */ + fetchAndCacheOnce(req, used = true) { + return __awaiter(this, void 0, void 0, function* () { + // The `inFlightRequests` map holds information about which caching operations are currently + // underway for known resources. If this request appears there, another "thread" is already + // in the process of caching it, and this work should not be duplicated. + if (this.inFlightRequests.has(req.url)) { + // There is a caching operation already in progress for this request. Wait for it to + // complete, and hopefully it will have yielded a useful response. + return this.inFlightRequests.get(req.url); + } + // No other caching operation is being attempted for this resource, so it will be owned here. + // Go to the network and get the correct version. + const fetchOp = this.fetchFromNetwork(req); + // Save this operation in `inFlightRequests` so any other "thread" attempting to cache it + // will block on this chain instead of duplicating effort. + this.inFlightRequests.set(req.url, fetchOp); + // Make sure this attempt is cleaned up properly on failure. + try { + // Wait for a response. If this fails, the request will remain in `inFlightRequests` + // indefinitely. + const res = yield fetchOp; + // It's very important that only successful responses are cached. Unsuccessful responses + // should never be cached as this can completely break applications. + if (!res.ok) { + throw new Error(`Response not Ok (fetchAndCacheOnce): request for ${req.url} returned response ${res.status} ${res.statusText}`); + } + try { + // This response is safe to cache (as long as it's cloned). Wait until the cache operation + // is complete. + const cache = yield this.scope.caches.open(`${this.prefix}:${this.config.name}:cache`); + yield cache.put(req, res.clone()); + // If the request is not hashed, update its metadata, especially the timestamp. This is + // needed for future determination of whether this cached response is stale or not. + if (!this.hashes.has(this.adapter.normalizeUrl(req.url))) { + // Metadata is tracked for requests that are unhashed. + const meta = { ts: this.adapter.time, used }; + const metaTable = yield this.metadata; + yield metaTable.write(req.url, meta); + } + return res; + } + catch (err) { + // Among other cases, this can happen when the user clears all data through the DevTools, + // but the SW is still running and serving another tab. In that case, trying to write to the + // caches throws an `Entry was not found` error. + // If this happens the SW can no longer work correctly. This situation is unrecoverable. + throw new SwCriticalError(`Failed to update the caches for request to '${req.url}' (fetchAndCacheOnce): ${errorToString(err)}`); + } + } + finally { + // Finally, it can be removed from `inFlightRequests`. This might result in a double-remove + // if some other chain was already making this request too, but that won't hurt anything. + this.inFlightRequests.delete(req.url); + } + }); + } + fetchFromNetwork(req, redirectLimit = 3) { + return __awaiter(this, void 0, void 0, function* () { + // Make a cache-busted request for the resource. + const res = yield this.cacheBustedFetchFromNetwork(req); + // Check for redirected responses, and follow the redirects. + if (res['redirected'] && !!res.url) { + // If the redirect limit is exhausted, fail with an error. + if (redirectLimit === 0) { + throw new SwCriticalError(`Response hit redirect limit (fetchFromNetwork): request redirected too many times, next is ${res.url}`); + } + // Unwrap the redirect directly. + return this.fetchFromNetwork(this.adapter.newRequest(res.url), redirectLimit - 1); + } + return res; + }); + } + /** + * Load a particular asset from the network, accounting for hash validation. + */ + cacheBustedFetchFromNetwork(req) { + return __awaiter(this, void 0, void 0, function* () { + const url = this.adapter.normalizeUrl(req.url); + // If a hash is available for this resource, then compare the fetched version with the + // canonical hash. Otherwise, the network version will have to be trusted. + if (this.hashes.has(url)) { + // It turns out this resource does have a hash. Look it up. Unless the fetched version + // matches this hash, it's invalid and the whole manifest may need to be thrown out. + const canonicalHash = this.hashes.get(url); + // Ideally, the resource would be requested with cache-busting to guarantee the SW gets + // the freshest version. However, doing this would eliminate any chance of the response + // being in the HTTP cache. Given that the browser has recently actively loaded the page, + // it's likely that many of the responses the SW needs to cache are in the HTTP cache and + // are fresh enough to use. In the future, this could be done by setting cacheMode to + // *only* check the browser cache for a cached version of the resource, when cacheMode is + // fully supported. For now, the resource is fetched directly, without cache-busting, and + // if the hash test fails a cache-busted request is tried before concluding that the + // resource isn't correct. This gives the benefit of acceleration via the HTTP cache + // without the risk of stale data, at the expense of a duplicate request in the event of + // a stale response. + // Fetch the resource from the network (possibly hitting the HTTP cache). + const networkResult = yield this.safeFetch(req); + // Decide whether a cache-busted request is necessary. It might be for two independent + // reasons: either the non-cache-busted request failed (hopefully transiently) or if the + // hash of the content retrieved does not match the canonical hash from the manifest. It's + // only valid to access the content of the first response if the request was successful. + let makeCacheBustedRequest = networkResult.ok; + if (makeCacheBustedRequest) { + // The request was successful. A cache-busted request is only necessary if the hashes + // don't match. Compare them, making sure to clone the response so it can be used later + // if it proves to be valid. + const fetchedHash = sha1Binary(yield networkResult.clone().arrayBuffer()); + makeCacheBustedRequest = (fetchedHash !== canonicalHash); + } + // Make a cache busted request to the network, if necessary. + if (makeCacheBustedRequest) { + // Hash failure, the version that was retrieved under the default URL did not have the + // hash expected. This could be because the HTTP cache got in the way and returned stale + // data, or because the version on the server really doesn't match. A cache-busting + // request will differentiate these two situations. + // TODO: handle case where the URL has parameters already (unlikely for assets). + const cacheBustReq = this.adapter.newRequest(this.cacheBust(req.url)); + const cacheBustedResult = yield this.safeFetch(cacheBustReq); + // If the response was unsuccessful, there's nothing more that can be done. + if (!cacheBustedResult.ok) { + throw new SwCriticalError(`Response not Ok (cacheBustedFetchFromNetwork): cache busted request for ${req.url} returned response ${cacheBustedResult.status} ${cacheBustedResult.statusText}`); + } + // Hash the contents. + const cacheBustedHash = sha1Binary(yield cacheBustedResult.clone().arrayBuffer()); + // If the cache-busted version doesn't match, then the manifest is not an accurate + // representation of the server's current set of files, and the SW should give up. + if (canonicalHash !== cacheBustedHash) { + throw new SwCriticalError(`Hash mismatch (cacheBustedFetchFromNetwork): ${req.url}: expected ${canonicalHash}, got ${cacheBustedHash} (after cache busting)`); + } + // If it does match, then use the cache-busted result. + return cacheBustedResult; + } + // Excellent, the version from the network matched on the first try, with no need for + // cache-busting. Use it. + return networkResult; + } + else { + // This URL doesn't exist in our hash database, so it must be requested directly. + return this.safeFetch(req); + } + }); + } + /** + * Possibly update a resource, if it's expired and needs to be updated. A no-op otherwise. + */ + maybeUpdate(updateFrom, req, cache) { + return __awaiter(this, void 0, void 0, function* () { + const url = this.adapter.normalizeUrl(req.url); + const meta = yield this.metadata; + // Check if this resource is hashed and already exists in the cache of a prior version. + if (this.hashes.has(url)) { + const hash = this.hashes.get(url); + // Check the caches of prior versions, using the hash to ensure the correct version of + // the resource is loaded. + const res = yield updateFrom.lookupResourceWithHash(url, hash); + // If a previously cached version was available, copy it over to this cache. + if (res !== null) { + // Copy to this cache. + yield cache.put(req, res); + yield meta.write(req.url, { ts: this.adapter.time, used: false }); + // No need to do anything further with this resource, it's now cached properly. + return true; + } + } + // No up-to-date version of this resource could be found. + return false; + }); + } + /** + * Construct a cache-busting URL for a given URL. + */ + cacheBust(url) { + return url + (url.indexOf('?') === -1 ? '?' : '&') + 'ngsw-cache-bust=' + Math.random(); + } + safeFetch(req) { + return __awaiter(this, void 0, void 0, function* () { + try { + return yield this.scope.fetch(req); + } + catch (_a) { + return this.adapter.newResponse('', { + status: 504, + statusText: 'Gateway Timeout', + }); + } + }); + } + } + /** + * An `AssetGroup` that prefetches all of its resources during initialization. + */ + class PrefetchAssetGroup extends AssetGroup { + initializeFully(updateFrom) { + return __awaiter(this, void 0, void 0, function* () { + // Open the cache which actually holds requests. + const cache = yield this.cache; + // Cache all known resources serially. As this reduce proceeds, each Promise waits + // on the last before starting the fetch/cache operation for the next request. Any + // errors cause fall-through to the final Promise which rejects. + yield this.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () { + // Wait on all previous operations to complete. + yield previous; + // Construct the Request for this url. + const req = this.adapter.newRequest(url); + // First, check the cache to see if there is already a copy of this resource. + const alreadyCached = (yield cache.match(req, this.config.cacheQueryOptions)) !== undefined; + // If the resource is in the cache already, it can be skipped. + if (alreadyCached) { + return; + } + // If an update source is available. + if (updateFrom !== undefined && (yield this.maybeUpdate(updateFrom, req, cache))) { + return; + } + // Otherwise, go to the network and hopefully cache the response (if successful). + yield this.fetchAndCacheOnce(req, false); + }), Promise.resolve()); + // Handle updating of unknown (unhashed) resources. This is only possible if there's + // a source to update from. + if (updateFrom !== undefined) { + const metaTable = yield this.metadata; + // Select all of the previously cached resources. These are cached unhashed resources + // from previous versions of the app, in any asset group. + yield (yield updateFrom.previouslyCachedResources()) + // First, narrow down the set of resources to those which are handled by this group. + // Either it's a known URL, or it matches a given pattern. + .filter(url => this.urls.indexOf(url) !== -1 || this.patterns.some(pattern => pattern.test(url))) + // Finally, process each resource in turn. + .reduce((previous, url) => __awaiter(this, void 0, void 0, function* () { + yield previous; + const req = this.adapter.newRequest(url); + // It's possible that the resource in question is already cached. If so, + // continue to the next one. + const alreadyCached = ((yield cache.match(req, this.config.cacheQueryOptions)) !== undefined); + if (alreadyCached) { + return; + } + // Get the most recent old version of the resource. + const res = yield updateFrom.lookupResourceWithoutHash(url); + if (res === null || res.metadata === undefined) { + // Unexpected, but not harmful. + return; + } + // Write it into the cache. It may already be expired, but it can still serve + // traffic until it's updated (stale-while-revalidate approach). + yield cache.put(req, res.response); + yield metaTable.write(req.url, Object.assign(Object.assign({}, res.metadata), { used: false })); + }), Promise.resolve()); + } + }); + } + } + class LazyAssetGroup extends AssetGroup { + initializeFully(updateFrom) { + return __awaiter(this, void 0, void 0, function* () { + // No action necessary if no update source is available - resources managed in this group + // are all lazily loaded, so there's nothing to initialize. + if (updateFrom === undefined) { + return; + } + // Open the cache which actually holds requests. + const cache = yield this.cache; + // Loop through the listed resources, caching any which are available. + yield this.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () { + // Wait on all previous operations to complete. + yield previous; + // Construct the Request for this url. + const req = this.adapter.newRequest(url); + // First, check the cache to see if there is already a copy of this resource. + const alreadyCached = (yield cache.match(req, this.config.cacheQueryOptions)) !== undefined; + // If the resource is in the cache already, it can be skipped. + if (alreadyCached) { + return; + } + const updated = yield this.maybeUpdate(updateFrom, req, cache); + if (this.config.updateMode === 'prefetch' && !updated) { + // If the resource was not updated, either it was not cached before or + // the previously cached version didn't match the updated hash. In that + // case, prefetch update mode dictates that the resource will be updated, + // except if it was not previously utilized. Check the status of the + // cached resource to see. + const cacheStatus = yield updateFrom.recentCacheStatus(url); + // If the resource is not cached, or was cached but unused, then it will be + // loaded lazily. + if (cacheStatus !== UpdateCacheStatus.CACHED) { + return; + } + // Update from the network. + yield this.fetchAndCacheOnce(req, false); + } + }), Promise.resolve()); + }); + } + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + /** + * Manages an instance of `LruState` and moves URLs to the head of the + * chain when requested. + */ + class LruList { + constructor(state) { + if (state === undefined) { + state = { + head: null, + tail: null, + map: {}, + count: 0, + }; + } + this.state = state; + } + /** + * The current count of URLs in the list. + */ + get size() { + return this.state.count; + } + /** + * Remove the tail. + */ + pop() { + // If there is no tail, return null. + if (this.state.tail === null) { + return null; + } + const url = this.state.tail; + this.remove(url); + // This URL has been successfully evicted. + return url; + } + remove(url) { + const node = this.state.map[url]; + if (node === undefined) { + return false; + } + // Special case if removing the current head. + if (this.state.head === url) { + // The node is the current head. Special case the removal. + if (node.next === null) { + // This is the only node. Reset the cache to be empty. + this.state.head = null; + this.state.tail = null; + this.state.map = {}; + this.state.count = 0; + return true; + } + // There is at least one other node. Make the next node the new head. + const next = this.state.map[node.next]; + next.previous = null; + this.state.head = next.url; + node.next = null; + delete this.state.map[url]; + this.state.count--; + return true; + } + // The node is not the head, so it has a previous. It may or may not be the tail. + // If it is not, then it has a next. First, grab the previous node. + const previous = this.state.map[node.previous]; + // Fix the forward pointer to skip over node and go directly to node.next. + previous.next = node.next; + // node.next may or may not be set. If it is, fix the back pointer to skip over node. + // If it's not set, then this node happened to be the tail, and the tail needs to be + // updated to point to the previous node (removing the tail). + if (node.next !== null) { + // There is a next node, fix its back pointer to skip this node. + this.state.map[node.next].previous = node.previous; + } + else { + // There is no next node - the accessed node must be the tail. Move the tail pointer. + this.state.tail = node.previous; + } + node.next = null; + node.previous = null; + delete this.state.map[url]; + // Count the removal. + this.state.count--; + return true; + } + accessed(url) { + // When a URL is accessed, its node needs to be moved to the head of the chain. + // This is accomplished in two steps: + // + // 1) remove the node from its position within the chain. + // 2) insert the node as the new head. + // + // Sometimes, a URL is accessed which has not been seen before. In this case, step 1 can + // be skipped completely (which will grow the chain by one). Of course, if the node is + // already the head, this whole operation can be skipped. + if (this.state.head === url) { + // The URL is already in the head position, accessing it is a no-op. + return; + } + // Look up the node in the map, and construct a new entry if it's + const node = this.state.map[url] || { url, next: null, previous: null }; + // Step 1: remove the node from its position within the chain, if it is in the chain. + if (this.state.map[url] !== undefined) { + this.remove(url); + } + // Step 2: insert the node at the head of the chain. + // First, check if there's an existing head node. If there is, it has previous: null. + // Its previous pointer should be set to the node we're inserting. + if (this.state.head !== null) { + this.state.map[this.state.head].previous = url; + } + // The next pointer of the node being inserted gets set to the old head, before the head + // pointer is updated to this node. + node.next = this.state.head; + // The new head is the new node. + this.state.head = url; + // If there is no tail, then this is the first node, and is both the head and the tail. + if (this.state.tail === null) { + this.state.tail = url; + } + // Set the node in the map of nodes (if the URL has been seen before, this is a no-op) + // and count the insertion. + this.state.map[url] = node; + this.state.count++; + } + } + /** + * A group of cached resources determined by a set of URL patterns which follow a LRU policy + * for caching. + */ + class DataGroup { + constructor(scope, adapter, config, db, debugHandler, prefix) { + this.scope = scope; + this.adapter = adapter; + this.config = config; + this.db = db; + this.debugHandler = debugHandler; + this.prefix = prefix; + /** + * Tracks the LRU state of resources in this cache. + */ + this._lru = null; + this.patterns = this.config.patterns.map(pattern => new RegExp(pattern)); + this.cache = this.scope.caches.open(`${this.prefix}:dynamic:${this.config.name}:cache`); + this.lruTable = this.db.open(`${this.prefix}:dynamic:${this.config.name}:lru`, this.config.cacheQueryOptions); + this.ageTable = this.db.open(`${this.prefix}:dynamic:${this.config.name}:age`, this.config.cacheQueryOptions); + } + /** + * Lazily initialize/load the LRU chain. + */ + lru() { + return __awaiter(this, void 0, void 0, function* () { + if (this._lru === null) { + const table = yield this.lruTable; + try { + this._lru = new LruList(yield table.read('lru')); + } + catch (_a) { + this._lru = new LruList(); + } + } + return this._lru; + }); + } + /** + * Sync the LRU chain to non-volatile storage. + */ + syncLru() { + return __awaiter(this, void 0, void 0, function* () { + if (this._lru === null) { + return; + } + const table = yield this.lruTable; + try { + return table.write('lru', this._lru.state); + } + catch (err) { + // Writing lru cache table failed. This could be a result of a full storage. + // Continue serving clients as usual. + this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).syncLru()`); + // TODO: Better detect/handle full storage; e.g. using + // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage). + } + }); + } + /** + * Process a fetch event and return a `Response` if the resource is covered by this group, + * or `null` otherwise. + */ + handleFetch(req, ctx) { + return __awaiter(this, void 0, void 0, function* () { + // Do nothing + if (!this.patterns.some(pattern => pattern.test(req.url))) { + return null; + } + // Lazily initialize the LRU cache. + const lru = yield this.lru(); + // The URL matches this cache. First, check whether this is a mutating request or not. + switch (req.method) { + case 'OPTIONS': + // Don't try to cache this - it's non-mutating, but is part of a mutating request. + // Most likely SWs don't even see this, but this guard is here just in case. + return null; + case 'GET': + case 'HEAD': + // Handle the request with whatever strategy was selected. + switch (this.config.strategy) { + case 'freshness': + return this.handleFetchWithFreshness(req, ctx, lru); + case 'performance': + return this.handleFetchWithPerformance(req, ctx, lru); + default: + throw new Error(`Unknown strategy: ${this.config.strategy}`); + } + default: + // This was a mutating request. Assume the cache for this URL is no longer valid. + const wasCached = lru.remove(req.url); + // If there was a cached entry, remove it. + if (wasCached) { + yield this.clearCacheForUrl(req.url); + } + // Sync the LRU chain to non-volatile storage. + yield this.syncLru(); + // Finally, fall back on the network. + return this.safeFetch(req); + } + }); + } + handleFetchWithPerformance(req, ctx, lru) { + return __awaiter(this, void 0, void 0, function* () { + let res = null; + // Check the cache first. If the resource exists there (and is not expired), the cached + // version can be used. + const fromCache = yield this.loadFromCache(req, lru); + if (fromCache !== null) { + res = fromCache.res; + // Check the age of the resource. + if (this.config.refreshAheadMs !== undefined && fromCache.age >= this.config.refreshAheadMs) { + ctx.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru)); + } + } + if (res !== null) { + return res; + } + // No match from the cache. Go to the network. Note that this is not an 'await' + // call, networkFetch is the actual Promise. This is due to timeout handling. + const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req); + res = yield timeoutFetch; + // Since fetch() will always return a response, undefined indicates a timeout. + if (res === undefined) { + // The request timed out. Return a Gateway Timeout error. + res = this.adapter.newResponse(null, { status: 504, statusText: 'Gateway Timeout' }); + // Cache the network response eventually. + ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru)); + } + else { + // The request completed in time, so cache it inline with the response flow. + yield this.safeCacheResponse(req, res, lru); + } + return res; + }); + } + handleFetchWithFreshness(req, ctx, lru) { + return __awaiter(this, void 0, void 0, function* () { + // Start with a network fetch. + const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req); + let res; + // If that fetch errors, treat it as a timed out request. + try { + res = yield timeoutFetch; + } + catch (_a) { + res = undefined; + } + // If the network fetch times out or errors, fall back on the cache. + if (res === undefined) { + ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true)); + // Ignore the age, the network response will be cached anyway due to the + // behavior of freshness. + const fromCache = yield this.loadFromCache(req, lru); + res = (fromCache !== null) ? fromCache.res : null; + } + else { + yield this.safeCacheResponse(req, res, lru, true); + } + // Either the network fetch didn't time out, or the cache yielded a usable response. + // In either case, use it. + if (res !== null) { + return res; + } + // No response in the cache. No choice but to fall back on the full network fetch. + return networkFetch; + }); + } + networkFetchWithTimeout(req) { + // If there is a timeout configured, race a timeout Promise with the network fetch. + // Otherwise, just fetch from the network directly. + if (this.config.timeoutMs !== undefined) { + const networkFetch = this.scope.fetch(req); + const safeNetworkFetch = (() => __awaiter(this, void 0, void 0, function* () { + try { + return yield networkFetch; + } + catch (_a) { + return this.adapter.newResponse(null, { + status: 504, + statusText: 'Gateway Timeout', + }); + } + }))(); + const networkFetchUndefinedError = (() => __awaiter(this, void 0, void 0, function* () { + try { + return yield networkFetch; + } + catch (_b) { + return undefined; + } + }))(); + // Construct a Promise for the timeout. + const timeout = this.adapter.timeout(this.config.timeoutMs); + // Race that with the network fetch. This will either be a Response, or `undefined` + // in the event that the request errored or timed out. + return [Promise.race([networkFetchUndefinedError, timeout]), safeNetworkFetch]; + } + else { + const networkFetch = this.safeFetch(req); + // Do a plain fetch. + return [networkFetch, networkFetch]; + } + } + safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) { + return __awaiter(this, void 0, void 0, function* () { + try { + const res = yield resOrPromise; + try { + yield this.cacheResponse(req, res, lru, okToCacheOpaque); + } + catch (err) { + // Saving the API response failed. This could be a result of a full storage. + // Since this data is cached lazily and temporarily, continue serving clients as usual. + this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`); + // TODO: Better detect/handle full storage; e.g. using + // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage). + } + } + catch (_a) { + // Request failed + // TODO: Handle this error somehow? + } + }); + } + loadFromCache(req, lru) { + return __awaiter(this, void 0, void 0, function* () { + // Look for a response in the cache. If one exists, return it. + const cache = yield this.cache; + let res = yield cache.match(req, this.config.cacheQueryOptions); + if (res !== undefined) { + // A response was found in the cache, but its age is not yet known. Look it up. + try { + const ageTable = yield this.ageTable; + const age = this.adapter.time - (yield ageTable.read(req.url)).age; + // If the response is young enough, use it. + if (age <= this.config.maxAge) { + // Successful match from the cache. Use the response, after marking it as having + // been accessed. + lru.accessed(req.url); + return { res, age }; + } + // Otherwise, or if there was an error, assume the response is expired, and evict it. + } + catch (_a) { + // Some error getting the age for the response. Assume it's expired. + } + lru.remove(req.url); + yield this.clearCacheForUrl(req.url); + // TODO: avoid duplicate in event of network timeout, maybe. + yield this.syncLru(); + } + return null; + }); + } + /** + * Operation for caching the response from the server. This has to happen all + * at once, so that the cache and LRU tracking remain in sync. If the network request + * completes before the timeout, this logic will be run inline with the response flow. + * If the request times out on the server, an error will be returned but the real network + * request will still be running in the background, to be cached when it completes. + */ + cacheResponse(req, res, lru, okToCacheOpaque = false) { + return __awaiter(this, void 0, void 0, function* () { + // Only cache successful responses. + if (!(res.ok || (okToCacheOpaque && res.type === 'opaque'))) { + return; + } + // If caching this response would make the cache exceed its maximum size, evict something + // first. + if (lru.size >= this.config.maxSize) { + // The cache is too big, evict something. + const evictedUrl = lru.pop(); + if (evictedUrl !== null) { + yield this.clearCacheForUrl(evictedUrl); + } + } + // TODO: evaluate for possible race conditions during flaky network periods. + // Mark this resource as having been accessed recently. This ensures it won't be evicted + // until enough other resources are requested that it falls off the end of the LRU chain. + lru.accessed(req.url); + // Store the response in the cache (cloning because the browser will consume + // the body during the caching operation). + yield (yield this.cache).put(req, res.clone()); + // Store the age of the cache. + const ageTable = yield this.ageTable; + yield ageTable.write(req.url, { age: this.adapter.time }); + // Sync the LRU chain to non-volatile storage. + yield this.syncLru(); + }); + } + /** + * Delete all of the saved state which this group uses to track resources. + */ + cleanup() { + return __awaiter(this, void 0, void 0, function* () { + // Remove both the cache and the database entries which track LRU stats. + yield Promise.all([ + this.scope.caches.delete(`${this.prefix}:dynamic:${this.config.name}:cache`), + this.db.delete(`${this.prefix}:dynamic:${this.config.name}:age`), + this.db.delete(`${this.prefix}:dynamic:${this.config.name}:lru`), + ]); + }); + } + /** + * Clear the state of the cache for a particular resource. + * + * This doesn't remove the resource from the LRU table, that is assumed to have + * been done already. This clears the GET and HEAD versions of the request from + * the cache itself, as well as the metadata stored in the age table. + */ + clearCacheForUrl(url) { + return __awaiter(this, void 0, void 0, function* () { + const [cache, ageTable] = yield Promise.all([this.cache, this.ageTable]); + yield Promise.all([ + cache.delete(this.adapter.newRequest(url, { method: 'GET' }), this.config.cacheQueryOptions), + cache.delete(this.adapter.newRequest(url, { method: 'HEAD' }), this.config.cacheQueryOptions), + ageTable.delete(url), + ]); + }); + } + safeFetch(req) { + return __awaiter(this, void 0, void 0, function* () { + try { + return this.scope.fetch(req); + } + catch (_a) { + return this.adapter.newResponse(null, { + status: 504, + statusText: 'Gateway Timeout', + }); + } + }); + } + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + const BACKWARDS_COMPATIBILITY_NAVIGATION_URLS = [ + { positive: true, regex: '^/.*$' }, + { positive: false, regex: '^/.*\\.[^/]*$' }, + { positive: false, regex: '^/.*__' }, + ]; + /** + * A specific version of the application, identified by a unique manifest + * as determined by its hash. + * + * Each `AppVersion` can be thought of as a published version of the app + * that can be installed as an update to any previously installed versions. + */ + class AppVersion { + constructor(scope, adapter, database, idle, debugHandler, manifest, manifestHash) { + this.scope = scope; + this.adapter = adapter; + this.database = database; + this.idle = idle; + this.debugHandler = debugHandler; + this.manifest = manifest; + this.manifestHash = manifestHash; + /** + * A Map of absolute URL paths (`/foo.txt`) to the known hash of their contents (if available). + */ + this.hashTable = new Map(); + /** + * The normalized URL to the file that serves as the index page to satisfy navigation requests. + * Usually this is `/index.html`. + */ + this.indexUrl = this.adapter.normalizeUrl(this.manifest.index); + /** + * Tracks whether the manifest has encountered any inconsistencies. + */ + this._okay = true; + // The hashTable within the manifest is an Object - convert it to a Map for easier lookups. + Object.keys(this.manifest.hashTable).forEach(url => { + this.hashTable.set(adapter.normalizeUrl(url), this.manifest.hashTable[url]); + }); + // Process each `AssetGroup` declared in the manifest. Each declared group gets an `AssetGroup` + // instance + // created for it, of a type that depends on the configuration mode. + this.assetGroups = (manifest.assetGroups || []).map(config => { + // Every asset group has a cache that's prefixed by the manifest hash and the name of the + // group. + const prefix = `${adapter.cacheNamePrefix}:${this.manifestHash}:assets`; + // Check the caching mode, which determines when resources will be fetched/updated. + switch (config.installMode) { + case 'prefetch': + return new PrefetchAssetGroup(this.scope, this.adapter, this.idle, config, this.hashTable, this.database, prefix); + case 'lazy': + return new LazyAssetGroup(this.scope, this.adapter, this.idle, config, this.hashTable, this.database, prefix); + } + }); + // Process each `DataGroup` declared in the manifest. + this.dataGroups = + (manifest.dataGroups || []) + .map(config => new DataGroup(this.scope, this.adapter, config, this.database, this.debugHandler, `${adapter.cacheNamePrefix}:${config.version}:data`)); + // This keeps backwards compatibility with app versions without navigation urls. + // Fix: https://github.com/angular/angular/issues/27209 + manifest.navigationUrls = manifest.navigationUrls || BACKWARDS_COMPATIBILITY_NAVIGATION_URLS; + // Create `include`/`exclude` RegExps for the `navigationUrls` declared in the manifest. + const includeUrls = manifest.navigationUrls.filter(spec => spec.positive); + const excludeUrls = manifest.navigationUrls.filter(spec => !spec.positive); + this.navigationUrls = { + include: includeUrls.map(spec => new RegExp(spec.regex)), + exclude: excludeUrls.map(spec => new RegExp(spec.regex)), + }; + } + get okay() { + return this._okay; + } + /** + * Fully initialize this version of the application. If this Promise resolves successfully, all + * required + * data has been safely downloaded. + */ + initializeFully(updateFrom) { + return __awaiter(this, void 0, void 0, function* () { + try { + // Fully initialize each asset group, in series. Starts with an empty Promise, + // and waits for the previous groups to have been initialized before initializing + // the next one in turn. + yield this.assetGroups.reduce((previous, group) => __awaiter(this, void 0, void 0, function* () { + // Wait for the previous groups to complete initialization. If there is a + // failure, this will throw, and each subsequent group will throw, until the + // whole sequence fails. + yield previous; + // Initialize this group. + return group.initializeFully(updateFrom); + }), Promise.resolve()); + } + catch (err) { + this._okay = false; + throw err; + } + }); + } + handleFetch(req, context) { + return __awaiter(this, void 0, void 0, function* () { + // Check the request against each `AssetGroup` in sequence. If an `AssetGroup` can't handle the + // request, + // it will return `null`. Thus, the first non-null response is the SW's answer to the request. + // So reduce + // the group list, keeping track of a possible response. If there is one, it gets passed + // through, and if + // not the next group is consulted to produce a candidate response. + const asset = yield this.assetGroups.reduce((potentialResponse, group) => __awaiter(this, void 0, void 0, function* () { + // Wait on the previous potential response. If it's not null, it should just be passed + // through. + const resp = yield potentialResponse; + if (resp !== null) { + return resp; + } + // No response has been found yet. Maybe this group will have one. + return group.handleFetch(req, context); + }), Promise.resolve(null)); + // The result of the above is the asset response, if there is any, or null otherwise. Return the + // asset + // response if there was one. If not, check with the data caching groups. + if (asset !== null) { + return asset; + } + // Perform the same reduction operation as above, but this time processing + // the data caching groups. + const data = yield this.dataGroups.reduce((potentialResponse, group) => __awaiter(this, void 0, void 0, function* () { + const resp = yield potentialResponse; + if (resp !== null) { + return resp; + } + return group.handleFetch(req, context); + }), Promise.resolve(null)); + // If the data caching group returned a response, go with it. + if (data !== null) { + return data; + } + // Next, check if this is a navigation request for a route. Detect circular + // navigations by checking if the request URL is the same as the index URL. + if (this.adapter.normalizeUrl(req.url) !== this.indexUrl && this.isNavigationRequest(req)) { + // This was a navigation request. Re-enter `handleFetch` with a request for + // the URL. + return this.handleFetch(this.adapter.newRequest(this.indexUrl), context); + } + return null; + }); + } + /** + * Determine whether the request is a navigation request. + * Takes into account: Request mode, `Accept` header, `navigationUrls` patterns. + */ + isNavigationRequest(req) { + if (req.mode !== 'navigate') { + return false; + } + if (!this.acceptsTextHtml(req)) { + return false; + } + const urlPrefix = this.scope.registration.scope.replace(/\/$/, ''); + const url = req.url.startsWith(urlPrefix) ? req.url.substr(urlPrefix.length) : req.url; + const urlWithoutQueryOrHash = url.replace(/[?#].*$/, ''); + return this.navigationUrls.include.some(regex => regex.test(urlWithoutQueryOrHash)) && + !this.navigationUrls.exclude.some(regex => regex.test(urlWithoutQueryOrHash)); + } + /** + * Check this version for a given resource with a particular hash. + */ + lookupResourceWithHash(url, hash) { + return __awaiter(this, void 0, void 0, function* () { + // Verify that this version has the requested resource cached. If not, + // there's no point in trying. + if (!this.hashTable.has(url)) { + return null; + } + // Next, check whether the resource has the correct hash. If not, any cached + // response isn't usable. + if (this.hashTable.get(url) !== hash) { + return null; + } + const cacheState = yield this.lookupResourceWithoutHash(url); + return cacheState && cacheState.response; + }); + } + /** + * Check this version for a given resource regardless of its hash. + */ + lookupResourceWithoutHash(url) { + // Limit the search to asset groups, and only scan the cache, don't + // load resources from the network. + return this.assetGroups.reduce((potentialResponse, group) => __awaiter(this, void 0, void 0, function* () { + const resp = yield potentialResponse; + if (resp !== null) { + return resp; + } + // fetchFromCacheOnly() avoids any network fetches, and returns the + // full set of cache data, not just the Response. + return group.fetchFromCacheOnly(url); + }), Promise.resolve(null)); + } + /** + * List all unhashed resources from all asset groups. + */ + previouslyCachedResources() { + return this.assetGroups.reduce((resources, group) => __awaiter(this, void 0, void 0, function* () { return (yield resources).concat(yield group.unhashedResources()); }), Promise.resolve([])); + } + recentCacheStatus(url) { + return __awaiter(this, void 0, void 0, function* () { + return this.assetGroups.reduce((current, group) => __awaiter(this, void 0, void 0, function* () { + const status = yield current; + if (status === UpdateCacheStatus.CACHED) { + return status; + } + const groupStatus = yield group.cacheStatus(url); + if (groupStatus === UpdateCacheStatus.NOT_CACHED) { + return status; + } + return groupStatus; + }), Promise.resolve(UpdateCacheStatus.NOT_CACHED)); + }); + } + /** + * Erase this application version, by cleaning up all the caches. + */ + cleanup() { + return __awaiter(this, void 0, void 0, function* () { + yield Promise.all(this.assetGroups.map(group => group.cleanup())); + yield Promise.all(this.dataGroups.map(group => group.cleanup())); + }); + } + /** + * Get the opaque application data which was provided with the manifest. + */ + get appData() { + return this.manifest.appData || null; + } + /** + * Check whether a request accepts `text/html` (based on the `Accept` header). + */ + acceptsTextHtml(req) { + const accept = req.headers.get('Accept'); + if (accept === null) { + return false; + } + const values = accept.split(','); + return values.some(value => value.trim().toLowerCase() === 'text/html'); + } + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + const DEBUG_LOG_BUFFER_SIZE = 100; + class DebugHandler { + constructor(driver, adapter) { + this.driver = driver; + this.adapter = adapter; + // There are two debug log message arrays. debugLogA records new debugging messages. + // Once it reaches DEBUG_LOG_BUFFER_SIZE, the array is moved to debugLogB and a new + // array is assigned to debugLogA. This ensures that insertion to the debug log is + // always O(1) no matter the number of logged messages, and that the total number + // of messages in the log never exceeds 2 * DEBUG_LOG_BUFFER_SIZE. + this.debugLogA = []; + this.debugLogB = []; + } + handleFetch(req) { + return __awaiter(this, void 0, void 0, function* () { + const [state, versions, idle] = yield Promise.all([ + this.driver.debugState(), + this.driver.debugVersions(), + this.driver.debugIdleState(), + ]); + const msgState = `NGSW Debug Info: + +Driver state: ${state.state} (${state.why}) +Latest manifest hash: ${state.latestHash || 'none'} +Last update check: ${this.since(state.lastUpdateCheck)}`; + const msgVersions = versions + .map(version => `=== Version ${version.hash} === + +Clients: ${version.clients.join(', ')}`) + .join('\n\n'); + const msgIdle = `=== Idle Task Queue === +Last update tick: ${this.since(idle.lastTrigger)} +Last update run: ${this.since(idle.lastRun)} +Task queue: +${idle.queue.map(v => ' * ' + v).join('\n')} + +Debug log: +${this.formatDebugLog(this.debugLogB)} +${this.formatDebugLog(this.debugLogA)} +`; + return this.adapter.newResponse(`${msgState} + +${msgVersions} + +${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }) }); + }); + } + since(time) { + if (time === null) { + return 'never'; + } + let age = this.adapter.time - time; + const days = Math.floor(age / 86400000); + age = age % 86400000; + const hours = Math.floor(age / 3600000); + age = age % 3600000; + const minutes = Math.floor(age / 60000); + age = age % 60000; + const seconds = Math.floor(age / 1000); + const millis = age % 1000; + return '' + (days > 0 ? `${days}d` : '') + (hours > 0 ? `${hours}h` : '') + + (minutes > 0 ? `${minutes}m` : '') + (seconds > 0 ? `${seconds}s` : '') + + (millis > 0 ? `${millis}u` : ''); + } + log(value, context = '') { + // Rotate the buffers if debugLogA has grown too large. + if (this.debugLogA.length === DEBUG_LOG_BUFFER_SIZE) { + this.debugLogB = this.debugLogA; + this.debugLogA = []; + } + // Convert errors to string for logging. + if (typeof value !== 'string') { + value = this.errorToString(value); + } + // Log the message. + this.debugLogA.push({ value, time: this.adapter.time, context }); + } + errorToString(err) { + return `${err.name}(${err.message}, ${err.stack})`; + } + formatDebugLog(log) { + return log.map(entry => `[${this.since(entry.time)}] ${entry.value} ${entry.context}`) + .join('\n'); + } + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + class IdleScheduler { + constructor(adapter, threshold, debug) { + this.adapter = adapter; + this.threshold = threshold; + this.debug = debug; + this.queue = []; + this.scheduled = null; + this.empty = Promise.resolve(); + this.emptyResolve = null; + this.lastTrigger = null; + this.lastRun = null; + } + trigger() { + return __awaiter(this, void 0, void 0, function* () { + this.lastTrigger = this.adapter.time; + if (this.queue.length === 0) { + return; + } + if (this.scheduled !== null) { + this.scheduled.cancel = true; + } + const scheduled = { + cancel: false, + }; + this.scheduled = scheduled; + yield this.adapter.timeout(this.threshold); + if (scheduled.cancel) { + return; + } + this.scheduled = null; + yield this.execute(); + }); + } + execute() { + return __awaiter(this, void 0, void 0, function* () { + this.lastRun = this.adapter.time; + while (this.queue.length > 0) { + const queue = this.queue; + this.queue = []; + yield queue.reduce((previous, task) => __awaiter(this, void 0, void 0, function* () { + yield previous; + try { + yield task.run(); + } + catch (err) { + this.debug.log(err, `while running idle task ${task.desc}`); + } + }), Promise.resolve()); + } + if (this.emptyResolve !== null) { + this.emptyResolve(); + this.emptyResolve = null; + } + this.empty = Promise.resolve(); + }); + } + schedule(desc, run) { + this.queue.push({ desc, run }); + if (this.emptyResolve === null) { + this.empty = new Promise(resolve => { + this.emptyResolve = resolve; + }); + } + } + get size() { + return this.queue.length; + } + get taskDescriptions() { + return this.queue.map(task => task.desc); + } + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + function hashManifest(manifest) { + return sha1(JSON.stringify(manifest)); + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + function isMsgCheckForUpdates(msg) { + return msg.action === 'CHECK_FOR_UPDATES'; + } + function isMsgActivateUpdate(msg) { + return msg.action === 'ACTIVATE_UPDATE'; + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + const IDLE_THRESHOLD = 5000; + const SUPPORTED_CONFIG_VERSION = 1; + const NOTIFICATION_OPTION_NAMES = [ + 'actions', 'badge', 'body', 'data', 'dir', 'icon', 'image', 'lang', 'renotify', + 'requireInteraction', 'silent', 'tag', 'timestamp', 'title', 'vibrate' + ]; + var DriverReadyState = /*@__PURE__*/ (function (DriverReadyState) { + // The SW is operating in a normal mode, responding to all traffic. + DriverReadyState[DriverReadyState["NORMAL"] = 0] = "NORMAL"; + // The SW does not have a clean installation of the latest version of the app, but older + // cached versions are safe to use so long as they don't try to fetch new dependencies. + // This is a degraded state. + DriverReadyState[DriverReadyState["EXISTING_CLIENTS_ONLY"] = 1] = "EXISTING_CLIENTS_ONLY"; + // The SW has decided that caching is completely unreliable, and is forgoing request + // handling until the next restart. + DriverReadyState[DriverReadyState["SAFE_MODE"] = 2] = "SAFE_MODE"; + return DriverReadyState; + })({}); + class Driver { + constructor(scope, adapter, db) { + // Set up all the event handlers that the SW needs. + this.scope = scope; + this.adapter = adapter; + this.db = db; + /** + * Tracks the current readiness condition under which the SW is operating. This controls + * whether the SW attempts to respond to some or all requests. + */ + this.state = DriverReadyState.NORMAL; + this.stateMessage = '(nominal)'; + /** + * Tracks whether the SW is in an initialized state or not. Before initialization, + * it's not legal to respond to requests. + */ + this.initialized = null; + /** + * Maps client IDs to the manifest hash of the application version being used to serve + * them. If a client ID is not present here, it has not yet been assigned a version. + * + * If a ManifestHash appears here, it is also present in the `versions` map below. + */ + this.clientVersionMap = new Map(); + /** + * Maps manifest hashes to instances of `AppVersion` for those manifests. + */ + this.versions = new Map(); + /** + * The latest version fetched from the server. + * + * Valid after initialization has completed. + */ + this.latestHash = null; + this.lastUpdateCheck = null; + /** + * Whether there is a check for updates currently scheduled due to navigation. + */ + this.scheduledNavUpdateCheck = false; + /** + * Keep track of whether we have logged an invalid `only-if-cached` request. + * (See `.onFetch()` for details.) + */ + this.loggedInvalidOnlyIfCachedRequest = false; + this.ngswStatePath = this.adapter.parseUrl('ngsw/state', this.scope.registration.scope).path; + // The install event is triggered when the service worker is first installed. + this.scope.addEventListener('install', (event) => { + // SW code updates are separate from application updates, so code updates are + // almost as straightforward as restarting the SW. Because of this, it's always + // safe to skip waiting until application tabs are closed, and activate the new + // SW version immediately. + event.waitUntil(this.scope.skipWaiting()); + }); + // The activate event is triggered when this version of the service worker is + // first activated. + this.scope.addEventListener('activate', (event) => { + event.waitUntil((() => __awaiter(this, void 0, void 0, function* () { + // As above, it's safe to take over from existing clients immediately, since the new SW + // version will continue to serve the old application. + yield this.scope.clients.claim(); + // Once all clients have been taken over, we can delete caches used by old versions of + // `@angular/service-worker`, which are no longer needed. This can happen in the background. + this.idle.schedule('activate: cleanup-old-sw-caches', () => __awaiter(this, void 0, void 0, function* () { + try { + yield this.cleanupOldSwCaches(); + } + catch (err) { + // Nothing to do - cleanup failed. Just log it. + this.debugger.log(err, 'cleanupOldSwCaches @ activate: cleanup-old-sw-caches'); + } + })); + }))()); + // Rather than wait for the first fetch event, which may not arrive until + // the next time the application is loaded, the SW takes advantage of the + // activation event to schedule initialization. However, if this were run + // in the context of the 'activate' event, waitUntil() here would cause fetch + // events to block until initialization completed. Thus, the SW does a + // postMessage() to itself, to schedule a new event loop iteration with an + // entirely separate event context. The SW will be kept alive by waitUntil() + // within that separate context while initialization proceeds, while at the + // same time the activation event is allowed to resolve and traffic starts + // being served. + if (this.scope.registration.active !== null) { + this.scope.registration.active.postMessage({ action: 'INITIALIZE' }); + } + }); + // Handle the fetch, message, and push events. + this.scope.addEventListener('fetch', (event) => this.onFetch(event)); + this.scope.addEventListener('message', (event) => this.onMessage(event)); + this.scope.addEventListener('push', (event) => this.onPush(event)); + this.scope.addEventListener('notificationclick', (event) => this.onClick(event)); + // The debugger generates debug pages in response to debugging requests. + this.debugger = new DebugHandler(this, this.adapter); + // The IdleScheduler will execute idle tasks after a given delay. + this.idle = new IdleScheduler(this.adapter, IDLE_THRESHOLD, this.debugger); + } + /** + * The handler for fetch events. + * + * This is the transition point between the synchronous event handler and the + * asynchronous execution that eventually resolves for respondWith() and waitUntil(). + */ + onFetch(event) { + const req = event.request; + const scopeUrl = this.scope.registration.scope; + const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl); + if (req.headers.has('ngsw-bypass') || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) { + return; + } + // The only thing that is served unconditionally is the debug page. + if (requestUrlObj.path === this.ngswStatePath) { + // Allow the debugger to handle the request, but don't affect SW state in any other way. + event.respondWith(this.debugger.handleFetch(req)); + return; + } + // If the SW is in a broken state where it's not safe to handle requests at all, + // returning causes the request to fall back on the network. This is preferred over + // `respondWith(fetch(req))` because the latter still shows in DevTools that the + // request was handled by the SW. + if (this.state === DriverReadyState.SAFE_MODE) { + // Even though the worker is in safe mode, idle tasks still need to happen so + // things like update checks, etc. can take place. + event.waitUntil(this.idle.trigger()); + return; + } + // Although "passive mixed content" (like images) only produces a warning without a + // ServiceWorker, fetching it via a ServiceWorker results in an error. Let such requests be + // handled by the browser, since handling with the ServiceWorker would fail anyway. + // See https://github.com/angular/angular/issues/23012#issuecomment-376430187 for more details. + if (requestUrlObj.origin.startsWith('http:') && scopeUrl.startsWith('https:')) { + // Still, log the incident for debugging purposes. + this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`); + return; + } + // When opening DevTools in Chrome, a request is made for the current URL (and possibly related + // resources, e.g. scripts) with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request + // will eventually fail, because `only-if-cached` is only allowed to be used with + // `mode: 'same-origin'`. + // This is likely a bug in Chrome DevTools. Avoid handling such requests. + // (See also https://github.com/angular/angular/issues/22362.) + // TODO(gkalpak): Remove once no longer necessary (i.e. fixed in Chrome DevTools). + if (req.cache === 'only-if-cached' && req.mode !== 'same-origin') { + // Log the incident only the first time it happens, to avoid spamming the logs. + if (!this.loggedInvalidOnlyIfCachedRequest) { + this.loggedInvalidOnlyIfCachedRequest = true; + this.debugger.log(`Ignoring invalid request: 'only-if-cached' can be set only with 'same-origin' mode`, `Driver.fetch(${req.url}, cache: ${req.cache}, mode: ${req.mode})`); + } + return; + } + // Past this point, the SW commits to handling the request itself. This could still + // fail (and result in `state` being set to `SAFE_MODE`), but even in that case the + // SW will still deliver a response. + event.respondWith(this.handleFetch(event)); + } + /** + * The handler for message events. + */ + onMessage(event) { + // Ignore message events when the SW is in safe mode, for now. + if (this.state === DriverReadyState.SAFE_MODE) { + return; + } + // If the message doesn't have the expected signature, ignore it. + const data = event.data; + if (!data || !data.action) { + return; + } + event.waitUntil((() => __awaiter(this, void 0, void 0, function* () { + // Initialization is the only event which is sent directly from the SW to itself, and thus + // `event.source` is not a `Client`. Handle it here, before the check for `Client` sources. + if (data.action === 'INITIALIZE') { + return this.ensureInitialized(event); + } + // Only messages from true clients are accepted past this point. + // This is essentially a typecast. + if (!this.adapter.isClient(event.source)) { + return; + } + // Handle the message and keep the SW alive until it's handled. + yield this.ensureInitialized(event); + yield this.handleMessage(data, event.source); + }))()); + } + onPush(msg) { + // Push notifications without data have no effect. + if (!msg.data) { + return; + } + // Handle the push and keep the SW alive until it's handled. + msg.waitUntil(this.handlePush(msg.data.json())); + } + onClick(event) { + // Handle the click event and keep the SW alive until it's handled. + event.waitUntil(this.handleClick(event.notification, event.action)); + } + ensureInitialized(event) { + return __awaiter(this, void 0, void 0, function* () { + // Since the SW may have just been started, it may or may not have been initialized already. + // `this.initialized` will be `null` if initialization has not yet been attempted, or will be a + // `Promise` which will resolve (successfully or unsuccessfully) if it has. + if (this.initialized !== null) { + return this.initialized; + } + // Initialization has not yet been attempted, so attempt it. This should only ever happen once + // per SW instantiation. + try { + this.initialized = this.initialize(); + yield this.initialized; + } + catch (error) { + // If initialization fails, the SW needs to enter a safe state, where it declines to respond + // to network requests. + this.state = DriverReadyState.SAFE_MODE; + this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`; + throw error; + } + finally { + // Regardless if initialization succeeded, background tasks still need to happen. + event.waitUntil(this.idle.trigger()); + } + }); + } + handleMessage(msg, from) { + return __awaiter(this, void 0, void 0, function* () { + if (isMsgCheckForUpdates(msg)) { + const action = (() => __awaiter(this, void 0, void 0, function* () { + yield this.checkForUpdate(); + }))(); + yield this.reportStatus(from, action, msg.statusNonce); + } + else if (isMsgActivateUpdate(msg)) { + yield this.reportStatus(from, this.updateClient(from), msg.statusNonce); + } + }); + } + handlePush(data) { + return __awaiter(this, void 0, void 0, function* () { + yield this.broadcast({ + type: 'PUSH', + data, + }); + if (!data.notification || !data.notification.title) { + return; + } + const desc = data.notification; + let options = {}; + NOTIFICATION_OPTION_NAMES.filter(name => desc.hasOwnProperty(name)) + .forEach(name => options[name] = desc[name]); + yield this.scope.registration.showNotification(desc['title'], options); + }); + } + handleClick(notification, action) { + return __awaiter(this, void 0, void 0, function* () { + notification.close(); + const options = {}; + // The filter uses `name in notification` because the properties are on the prototype so + // hasOwnProperty does not work here + NOTIFICATION_OPTION_NAMES.filter(name => name in notification) + .forEach(name => options[name] = notification[name]); + yield this.broadcast({ + type: 'NOTIFICATION_CLICK', + data: { action, notification: options }, + }); + }); + } + reportStatus(client, promise, nonce) { + return __awaiter(this, void 0, void 0, function* () { + const response = { type: 'STATUS', nonce, status: true }; + try { + yield promise; + client.postMessage(response); + } + catch (e) { + client.postMessage(Object.assign(Object.assign({}, response), { status: false, error: e.toString() })); + } + }); + } + updateClient(client) { + return __awaiter(this, void 0, void 0, function* () { + // Figure out which version the client is on. If it's not on the latest, + // it needs to be moved. + const existing = this.clientVersionMap.get(client.id); + if (existing === this.latestHash) { + // Nothing to do, this client is already on the latest version. + return; + } + // Switch the client over. + let previous = undefined; + // Look up the application data associated with the existing version. If there + // isn't any, fall back on using the hash. + if (existing !== undefined) { + const existingVersion = this.versions.get(existing); + previous = this.mergeHashWithAppData(existingVersion.manifest, existing); + } + // Set the current version used by the client, and sync the mapping to disk. + this.clientVersionMap.set(client.id, this.latestHash); + yield this.sync(); + // Notify the client about this activation. + const current = this.versions.get(this.latestHash); + const notice = { + type: 'UPDATE_ACTIVATED', + previous, + current: this.mergeHashWithAppData(current.manifest, this.latestHash), + }; + client.postMessage(notice); + }); + } + handleFetch(event) { + return __awaiter(this, void 0, void 0, function* () { + try { + // Ensure the SW instance has been initialized. + yield this.ensureInitialized(event); + } + catch (_a) { + // Since the SW is already committed to responding to the currently active request, + // respond with a network fetch. + return this.safeFetch(event.request); + } + // On navigation requests, check for new updates. + if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) { + this.scheduledNavUpdateCheck = true; + this.idle.schedule('check-updates-on-navigation', () => __awaiter(this, void 0, void 0, function* () { + this.scheduledNavUpdateCheck = false; + yield this.checkForUpdate(); + })); + } + // Decide which version of the app to use to serve this request. This is asynchronous as in + // some cases, a record will need to be written to disk about the assignment that is made. + const appVersion = yield this.assignVersion(event); + // Bail out + if (appVersion === null) { + event.waitUntil(this.idle.trigger()); + return this.safeFetch(event.request); + } + let res = null; + try { + // Handle the request. First try the AppVersion. If that doesn't work, fall back on the + // network. + res = yield appVersion.handleFetch(event.request, event); + } + catch (err) { + if (err.isCritical) { + // Something went wrong with the activation of this version. + yield this.versionFailed(appVersion, err); + event.waitUntil(this.idle.trigger()); + return this.safeFetch(event.request); + } + throw err; + } + // The AppVersion will only return null if the manifest doesn't specify what to do about this + // request. In that case, just fall back on the network. + if (res === null) { + event.waitUntil(this.idle.trigger()); + return this.safeFetch(event.request); + } + // Trigger the idle scheduling system. The Promise returned by trigger() will resolve after + // a specific amount of time has passed. If trigger() hasn't been called again by then (e.g. + // on a subsequent request), the idle task queue will be drained and the Promise won't resolve + // until that operation is complete as well. + event.waitUntil(this.idle.trigger()); + // The AppVersion returned a usable response, so return it. + return res; + }); + } + /** + * Attempt to quickly reach a state where it's safe to serve responses. + */ + initialize() { + return __awaiter(this, void 0, void 0, function* () { + // On initialization, all of the serialized state is read out of the 'control' + // table. This includes: + // - map of hashes to manifests of currently loaded application versions + // - map of client IDs to their pinned versions + // - record of the most recently fetched manifest hash + // + // If these values don't exist in the DB, then this is the either the first time + // the SW has run or the DB state has been wiped or is inconsistent. In that case, + // load a fresh copy of the manifest and reset the state from scratch. + // Open up the DB table. + const table = yield this.db.open('control'); + // Attempt to load the needed state from the DB. If this fails, the catch {} block + // will populate these variables with freshly constructed values. + let manifests, assignments, latest; + try { + // Read them from the DB simultaneously. + [manifests, assignments, latest] = yield Promise.all([ + table.read('manifests'), + table.read('assignments'), + table.read('latest'), + ]); + // Make sure latest manifest is correctly installed. If not (e.g. corrupted data), + // it could stay locked in EXISTING_CLIENTS_ONLY or SAFE_MODE state. + if (!this.versions.has(latest.latest) && !manifests.hasOwnProperty(latest.latest)) { + this.debugger.log(`Missing manifest for latest version hash ${latest.latest}`, 'initialize: read from DB'); + throw new Error(`Missing manifest for latest hash ${latest.latest}`); + } + // Successfully loaded from saved state. This implies a manifest exists, so + // the update check needs to happen in the background. + this.idle.schedule('init post-load (update, cleanup)', () => __awaiter(this, void 0, void 0, function* () { + yield this.checkForUpdate(); + try { + yield this.cleanupCaches(); + } + catch (err) { + // Nothing to do - cleanup failed. Just log it. + this.debugger.log(err, 'cleanupCaches @ init post-load'); + } + })); + } + catch (_) { + // Something went wrong. Try to start over by fetching a new manifest from the + // server and building up an empty initial state. + const manifest = yield this.fetchLatestManifest(); + const hash = hashManifest(manifest); + manifests = {}; + manifests[hash] = manifest; + assignments = {}; + latest = { latest: hash }; + // Save the initial state to the DB. + yield Promise.all([ + table.write('manifests', manifests), + table.write('assignments', assignments), + table.write('latest', latest), + ]); + } + // At this point, either the state has been loaded successfully, or fresh state + // with a new copy of the manifest has been produced. At this point, the `Driver` + // can have its internals hydrated from the state. + // Initialize the `versions` map by setting each hash to a new `AppVersion` instance + // for that manifest. + Object.keys(manifests).forEach((hash) => { + const manifest = manifests[hash]; + // If the manifest is newly initialized, an AppVersion may have already been + // created for it. + if (!this.versions.has(hash)) { + this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash)); + } + }); + // Map each client ID to its associated hash. Along the way, verify that the hash + // is still valid for that client ID. It should not be possible for a client to + // still be associated with a hash that was since removed from the state. + Object.keys(assignments).forEach((clientId) => { + const hash = assignments[clientId]; + if (this.versions.has(hash)) { + this.clientVersionMap.set(clientId, hash); + } + else { + this.clientVersionMap.set(clientId, latest.latest); + this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`); + } + }); + // Set the latest version. + this.latestHash = latest.latest; + // Finally, assert that the latest version is in fact loaded. + if (!this.versions.has(latest.latest)) { + throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`); + } + // Finally, wait for the scheduling of initialization of all versions in the + // manifest. Ordinarily this just schedules the initializations to happen during + // the next idle period, but in development mode this might actually wait for the + // full initialization. + // If any of these initializations fail, versionFailed() will be called either + // synchronously or asynchronously to handle the failure and re-map clients. + yield Promise.all(Object.keys(manifests).map((hash) => __awaiter(this, void 0, void 0, function* () { + try { + // Attempt to schedule or initialize this version. If this operation is + // successful, then initialization either succeeded or was scheduled. If + // it fails, then full initialization was attempted and failed. + yield this.scheduleInitialization(this.versions.get(hash)); + } + catch (err) { + this.debugger.log(err, `initialize: schedule init of ${hash}`); + return false; + } + }))); + }); + } + lookupVersionByHash(hash, debugName = 'lookupVersionByHash') { + // The version should exist, but check just in case. + if (!this.versions.has(hash)) { + throw new Error(`Invariant violated (${debugName}): want AppVersion for ${hash} but not loaded`); + } + return this.versions.get(hash); + } + /** + * Decide which version of the manifest to use for the event. + */ + assignVersion(event) { + return __awaiter(this, void 0, void 0, function* () { + // First, check whether the event has a (non empty) client ID. If it does, the version may + // already be associated. + const clientId = event.clientId; + if (clientId) { + // Check if there is an assigned client id. + if (this.clientVersionMap.has(clientId)) { + // There is an assignment for this client already. + const hash = this.clientVersionMap.get(clientId); + let appVersion = this.lookupVersionByHash(hash, 'assignVersion'); + // Ordinarily, this client would be served from its assigned version. But, if this + // request is a navigation request, this client can be updated to the latest + // version immediately. + if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash && + appVersion.isNavigationRequest(event.request)) { + // Update this client to the latest version immediately. + if (this.latestHash === null) { + throw new Error(`Invariant violated (assignVersion): latestHash was null`); + } + const client = yield this.scope.clients.get(clientId); + yield this.updateClient(client); + appVersion = this.lookupVersionByHash(this.latestHash, 'assignVersion'); + } + // TODO: make sure the version is valid. + return appVersion; + } + else { + // This is the first time this client ID has been seen. Whether the SW is in a + // state to handle new clients depends on the current readiness state, so check + // that first. + if (this.state !== DriverReadyState.NORMAL) { + // It's not safe to serve new clients in the current state. It's possible that + // this is an existing client which has not been mapped yet (see below) but + // even if that is the case, it's invalid to make an assignment to a known + // invalid version, even if that assignment was previously implicit. Return + // undefined here to let the caller know that no assignment is possible at + // this time. + return null; + } + // It's safe to handle this request. Two cases apply. Either: + // 1) the browser assigned a client ID at the time of the navigation request, and + // this is truly the first time seeing this client, or + // 2) a navigation request came previously from the same client, but with no client + // ID attached. Browsers do this to avoid creating a client under the origin in + // the event the navigation request is just redirected. + // + // In case 1, the latest version can safely be used. + // In case 2, the latest version can be used, with the assumption that the previous + // navigation request was answered under the same version. This assumption relies + // on the fact that it's unlikely an update will come in between the navigation + // request and requests for subsequent resources on that page. + // First validate the current state. + if (this.latestHash === null) { + throw new Error(`Invariant violated (assignVersion): latestHash was null`); + } + // Pin this client ID to the current latest version, indefinitely. + this.clientVersionMap.set(clientId, this.latestHash); + yield this.sync(); + // Return the latest `AppVersion`. + return this.lookupVersionByHash(this.latestHash, 'assignVersion'); + } + } + else { + // No client ID was associated with the request. This must be a navigation request + // for a new client. First check that the SW is accepting new clients. + if (this.state !== DriverReadyState.NORMAL) { + return null; + } + // Serve it with the latest version, and assume that the client will actually get + // associated with that version on the next request. + // First validate the current state. + if (this.latestHash === null) { + throw new Error(`Invariant violated (assignVersion): latestHash was null`); + } + // Return the latest `AppVersion`. + return this.lookupVersionByHash(this.latestHash, 'assignVersion'); + } + }); + } + fetchLatestManifest(ignoreOfflineError = false) { + return __awaiter(this, void 0, void 0, function* () { + const res = yield this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random())); + if (!res.ok) { + if (res.status === 404) { + yield this.deleteAllCaches(); + yield this.scope.registration.unregister(); + } + else if ((res.status === 503 || res.status === 504) && ignoreOfflineError) { + return null; + } + throw new Error(`Manifest fetch failed! (status: ${res.status})`); + } + this.lastUpdateCheck = this.adapter.time; + return res.json(); + }); + } + deleteAllCaches() { + return __awaiter(this, void 0, void 0, function* () { + yield (yield this.scope.caches.keys()) + // The Chrome debugger is not able to render the syntax properly when the + // code contains backticks. This is a known issue in Chrome and they have an + // open [issue](https://bugs.chromium.org/p/chromium/issues/detail?id=659515) for that. + // As a work-around for the time being, we can use \\ ` at the end of the line. + .filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:`)) // ` + .reduce((previous, key) => __awaiter(this, void 0, void 0, function* () { + yield Promise.all([ + previous, + this.scope.caches.delete(key), + ]); + }), Promise.resolve()); + }); + } + /** + * Schedule the SW's attempt to reach a fully prefetched state for the given AppVersion + * when the SW is not busy and has connectivity. This returns a Promise which must be + * awaited, as under some conditions the AppVersion might be initialized immediately. + */ + scheduleInitialization(appVersion) { + return __awaiter(this, void 0, void 0, function* () { + const initialize = () => __awaiter(this, void 0, void 0, function* () { + try { + yield appVersion.initializeFully(); + } + catch (err) { + this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`); + yield this.versionFailed(appVersion, err); + } + }); + // TODO: better logic for detecting localhost. + if (this.scope.registration.scope.indexOf('://localhost') > -1) { + return initialize(); + } + this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize); + }); + } + versionFailed(appVersion, err) { + return __awaiter(this, void 0, void 0, function* () { + // This particular AppVersion is broken. First, find the manifest hash. + const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion); + if (broken === undefined) { + // This version is no longer in use anyway, so nobody cares. + return; + } + const brokenHash = broken[0]; + const affectedClients = Array.from(this.clientVersionMap.entries()) + .filter(([clientId, hash]) => hash === brokenHash) + .map(([clientId]) => clientId); + // TODO: notify affected apps. + // The action taken depends on whether the broken manifest is the active (latest) or not. + // If so, the SW cannot accept new clients, but can continue to service old ones. + if (this.latestHash === brokenHash) { + // The latest manifest is broken. This means that new clients are at the mercy of the + // network, but caches continue to be valid for previous versions. This is + // unfortunate but unavoidable. + this.state = DriverReadyState.EXISTING_CLIENTS_ONLY; + this.stateMessage = `Degraded due to: ${errorToString(err)}`; + // Cancel the binding for the affected clients. + affectedClients.forEach(clientId => this.clientVersionMap.delete(clientId)); + } + else { + // The latest version is viable, but this older version isn't. The only + // possible remedy is to stop serving the older version and go to the network. + // Put the affected clients on the latest version. + affectedClients.forEach(clientId => this.clientVersionMap.set(clientId, this.latestHash)); + } + try { + yield this.sync(); + } + catch (err2) { + // We are already in a bad state. No need to make things worse. + // Just log the error and move on. + this.debugger.log(err2, `Driver.versionFailed(${err.message || err})`); + } + }); + } + setupUpdate(manifest, hash) { + return __awaiter(this, void 0, void 0, function* () { + const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash); + // Firstly, check if the manifest version is correct. + if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) { + yield this.deleteAllCaches(); + yield this.scope.registration.unregister(); + throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`); + } + // Cause the new version to become fully initialized. If this fails, then the + // version will not be available for use. + yield newVersion.initializeFully(this); + // Install this as an active version of the app. + this.versions.set(hash, newVersion); + // Future new clients will use this hash as the latest version. + this.latestHash = hash; + // If we are in `EXISTING_CLIENTS_ONLY` mode (meaning we didn't have a clean copy of the last + // latest version), we can now recover to `NORMAL` mode and start accepting new clients. + if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) { + this.state = DriverReadyState.NORMAL; + this.stateMessage = '(nominal)'; + } + yield this.sync(); + yield this.notifyClientsAboutUpdate(newVersion); + }); + } + checkForUpdate() { + return __awaiter(this, void 0, void 0, function* () { + let hash = '(unknown)'; + try { + const manifest = yield this.fetchLatestManifest(true); + if (manifest === null) { + // Client or server offline. Unable to check for updates at this time. + // Continue to service clients (existing and new). + this.debugger.log('Check for update aborted. (Client or server offline.)'); + return false; + } + hash = hashManifest(manifest); + // Check whether this is really an update. + if (this.versions.has(hash)) { + return false; + } + yield this.setupUpdate(manifest, hash); + return true; + } + catch (err) { + this.debugger.log(err, `Error occurred while updating to manifest ${hash}`); + this.state = DriverReadyState.EXISTING_CLIENTS_ONLY; + this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`; + return false; + } + }); + } + /** + * Synchronize the existing state to the underlying database. + */ + sync() { + return __awaiter(this, void 0, void 0, function* () { + // Open up the DB table. + const table = yield this.db.open('control'); + // Construct a serializable map of hashes to manifests. + const manifests = {}; + this.versions.forEach((version, hash) => { + manifests[hash] = version.manifest; + }); + // Construct a serializable map of client ids to version hashes. + const assignments = {}; + this.clientVersionMap.forEach((hash, clientId) => { + assignments[clientId] = hash; + }); + // Record the latest entry. Since this is a sync which is necessarily happening after + // initialization, latestHash should always be valid. + const latest = { + latest: this.latestHash, + }; + // Synchronize all of these. + yield Promise.all([ + table.write('manifests', manifests), + table.write('assignments', assignments), + table.write('latest', latest), + ]); + }); + } + cleanupCaches() { + return __awaiter(this, void 0, void 0, function* () { + // Query for all currently active clients, and list the client ids. This may skip + // some clients in the browser back-forward cache, but not much can be done about + // that. + const activeClients = (yield this.scope.clients.matchAll()).map(client => client.id); + // A simple list of client ids that the SW has kept track of. Subtracting + // activeClients from this list will result in the set of client ids which are + // being tracked but are no longer used in the browser, and thus can be cleaned up. + const knownClients = Array.from(this.clientVersionMap.keys()); + // Remove clients in the clientVersionMap that are no longer active. + knownClients.filter(id => activeClients.indexOf(id) === -1) + .forEach(id => this.clientVersionMap.delete(id)); + // Next, determine the set of versions which are still used. All others can be + // removed. + const usedVersions = new Set(); + this.clientVersionMap.forEach((version, _) => usedVersions.add(version)); + // Collect all obsolete versions by filtering out used versions from the set of all versions. + const obsoleteVersions = Array.from(this.versions.keys()) + .filter(version => !usedVersions.has(version) && version !== this.latestHash); + // Remove all the versions which are no longer used. + yield obsoleteVersions.reduce((previous, version) => __awaiter(this, void 0, void 0, function* () { + // Wait for the other cleanup operations to complete. + yield previous; + // Try to get past the failure of one particular version to clean up (this + // shouldn't happen, but handle it just in case). + try { + // Get ahold of the AppVersion for this particular hash. + const instance = this.versions.get(version); + // Delete it from the canonical map. + this.versions.delete(version); + // Clean it up. + yield instance.cleanup(); + } + catch (err) { + // Oh well? Not much that can be done here. These caches will be removed when + // the SW revs its format version, which happens from time to time. + this.debugger.log(err, `cleanupCaches - cleanup ${version}`); + } + }), Promise.resolve()); + // Commit all the changes to the saved state. + yield this.sync(); + }); + } + /** + * Delete caches that were used by older versions of `@angular/service-worker` to avoid running + * into storage quota limitations imposed by browsers. + * (Since at this point the SW has claimed all clients, it is safe to remove those caches.) + */ + cleanupOldSwCaches() { + return __awaiter(this, void 0, void 0, function* () { + const cacheNames = yield this.scope.caches.keys(); + const oldSwCacheNames = cacheNames.filter(name => /^ngsw:(?!\/)/.test(name)); + yield Promise.all(oldSwCacheNames.map(name => this.scope.caches.delete(name))); + }); + } + /** + * Determine if a specific version of the given resource is cached anywhere within the SW, + * and fetch it if so. + */ + lookupResourceWithHash(url, hash) { + return Array + // Scan through the set of all cached versions, valid or otherwise. It's safe to do such + // lookups even for invalid versions as the cached version of a resource will have the + // same hash regardless. + .from(this.versions.values()) + // Reduce the set of versions to a single potential result. At any point along the + // reduction, if a response has already been identified, then pass it through, as no + // future operation could change the response. If no response has been found yet, keep + // checking versions until one is or until all versions have been exhausted. + .reduce((prev, version) => __awaiter(this, void 0, void 0, function* () { + // First, check the previous result. If a non-null result has been found already, just + // return it. + if ((yield prev) !== null) { + return prev; + } + // No result has been found yet. Try the next `AppVersion`. + return version.lookupResourceWithHash(url, hash); + }), Promise.resolve(null)); + } + lookupResourceWithoutHash(url) { + return __awaiter(this, void 0, void 0, function* () { + yield this.initialized; + const version = this.versions.get(this.latestHash); + return version ? version.lookupResourceWithoutHash(url) : null; + }); + } + previouslyCachedResources() { + return __awaiter(this, void 0, void 0, function* () { + yield this.initialized; + const version = this.versions.get(this.latestHash); + return version ? version.previouslyCachedResources() : []; + }); + } + recentCacheStatus(url) { + return __awaiter(this, void 0, void 0, function* () { + const version = this.versions.get(this.latestHash); + return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED; + }); + } + mergeHashWithAppData(manifest, hash) { + return { + hash, + appData: manifest.appData, + }; + } + notifyClientsAboutUpdate(next) { + return __awaiter(this, void 0, void 0, function* () { + yield this.initialized; + const clients = yield this.scope.clients.matchAll(); + yield clients.reduce((previous, client) => __awaiter(this, void 0, void 0, function* () { + yield previous; + // Firstly, determine which version this client is on. + const version = this.clientVersionMap.get(client.id); + if (version === undefined) { + // Unmapped client - assume it's the latest. + return; + } + if (version === this.latestHash) { + // Client is already on the latest version, no need for a notification. + return; + } + const current = this.versions.get(version); + // Send a notice. + const notice = { + type: 'UPDATE_AVAILABLE', + current: this.mergeHashWithAppData(current.manifest, version), + available: this.mergeHashWithAppData(next.manifest, this.latestHash), + }; + client.postMessage(notice); + }), Promise.resolve()); + }); + } + broadcast(msg) { + return __awaiter(this, void 0, void 0, function* () { + const clients = yield this.scope.clients.matchAll(); + clients.forEach(client => { + client.postMessage(msg); + }); + }); + } + debugState() { + return __awaiter(this, void 0, void 0, function* () { + return { + state: DriverReadyState[this.state], + why: this.stateMessage, + latestHash: this.latestHash, + lastUpdateCheck: this.lastUpdateCheck, + }; + }); + } + debugVersions() { + return __awaiter(this, void 0, void 0, function* () { + // Build list of versions. + return Array.from(this.versions.keys()).map(hash => { + const version = this.versions.get(hash); + const clients = Array.from(this.clientVersionMap.entries()) + .filter(([clientId, version]) => version === hash) + .map(([clientId, version]) => clientId); + return { + hash, + manifest: version.manifest, + clients, + status: '', + }; + }); + }); + } + debugIdleState() { + return __awaiter(this, void 0, void 0, function* () { + return { + queue: this.idle.taskDescriptions, + lastTrigger: this.idle.lastTrigger, + lastRun: this.idle.lastRun, + }; + }); + } + safeFetch(req) { + return __awaiter(this, void 0, void 0, function* () { + try { + return yield this.scope.fetch(req); + } + catch (err) { + this.debugger.log(err, `Driver.fetch(${req.url})`); + return this.adapter.newResponse(null, { + status: 504, + statusText: 'Gateway Timeout', + }); + } + }); + } + } + + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + const scope = self; + const adapter = new Adapter(scope.registration.scope); + const driver = new Driver(scope, adapter, new CacheDatabase(scope, adapter)); + +}()); diff --git a/www/ngsw.json b/www/ngsw.json new file mode 100644 index 00000000000..1ca0ecffbc3 --- /dev/null +++ b/www/ngsw.json @@ -0,0 +1,84 @@ +{ + "configVersion": 1, + "timestamp": 1605140587052, + "index": "/index.html", + "assetGroups": [ + { + "name": "app", + "installMode": "prefetch", + "updateMode": "prefetch", + "cacheQueryOptions": { + "ignoreVary": true + }, + "urls": [ + "/favicon.ico", + "/index.html", + "/main.a75b981096ad2a451c1d.js", + "/manifest.webmanifest", + "/polyfills.a4021de53358bb0fec14.js", + "/runtime.e227d1a0e31cbccbf8ec.js", + "/styles.55ddde32a7bc7cfb8ba7.css" + ], + "patterns": [] + }, + { + "name": "assets", + "installMode": "lazy", + "updateMode": "prefetch", + "cacheQueryOptions": { + "ignoreVary": true + }, + "urls": [ + "/assets/icons/icon-128x128.png", + "/assets/icons/icon-144x144.png", + "/assets/icons/icon-152x152.png", + "/assets/icons/icon-192x192.png", + "/assets/icons/icon-384x384.png", + "/assets/icons/icon-512x512.png", + "/assets/icons/icon-72x72.png", + "/assets/icons/icon-96x96.png", + "/assets/logo-dark.svg", + "/assets/logo-light.svg" + ], + "patterns": [] + } + ], + "dataGroups": [], + "hashTable": { + "/assets/icons/icon-128x128.png": "dae3b6ed49bdaf4327b92531d4b5b4a5d30c7532", + "/assets/icons/icon-144x144.png": "b0bd89982e08f9bd2b642928f5391915b74799a7", + "/assets/icons/icon-152x152.png": "7479a9477815dfd9668d60f8b3b2fba709b91310", + "/assets/icons/icon-192x192.png": "1abd80d431a237a853ce38147d8c63752f10933b", + "/assets/icons/icon-384x384.png": "329749cd6393768d3131ed6304c136b1ca05f2fd", + "/assets/icons/icon-512x512.png": "559d9c4318b45a1f2b10596bbb4c960fe521dbcc", + "/assets/icons/icon-72x72.png": "c457e56089a36952cd67156f9996bc4ce54a5ed9", + "/assets/icons/icon-96x96.png": "3914125a4b445bf111c5627875fc190f560daa41", + "/assets/logo-dark.svg": "3c5dbbd620a781a0d1e3680cb697982dd44a1613", + "/assets/logo-light.svg": "29908fafefe60c105f06b2f8d69f7c1eed436c17", + "/favicon.ico": "22f6a4a3bcaafafb0254e0f2fa4ceb89e505e8b2", + "/index.html": "e31575afb78008c0976a5c88feff34cf9e0329c7", + "/main.a75b981096ad2a451c1d.js": "7a9f93c040259b3bc0ded13f5a80c126a53f1461", + "/manifest.webmanifest": "e3c62aa5d0d6a32fa90da1196d0928f85a7b2470", + "/polyfills.a4021de53358bb0fec14.js": "b47435a9ef2a4829f2f9dfd35599bde3614981b1", + "/runtime.e227d1a0e31cbccbf8ec.js": "a9aafcf49f49145093fc831efd9b8e2f6c71bb9c", + "/styles.55ddde32a7bc7cfb8ba7.css": "6b632c1488075f9ff6d2eb1d244de8b72725ad4f" + }, + "navigationUrls": [ + { + "positive": true, + "regex": "^\\/.*$" + }, + { + "positive": false, + "regex": "^\\/(?:.+\\/)?[^/]*\\.[^/]*$" + }, + { + "positive": false, + "regex": "^\\/(?:.+\\/)?[^/]*__[^/]*$" + }, + { + "positive": false, + "regex": "^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$" + } + ] +} \ No newline at end of file diff --git a/www/polyfills.a4021de53358bb0fec14.js b/www/polyfills.a4021de53358bb0fec14.js new file mode 100644 index 00000000000..4919f68abb5 --- /dev/null +++ b/www/polyfills.a4021de53358bb0fec14.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[2],{1:function(e,t,n){e.exports=n("hN/g")},"hN/g":function(e,t,n){"use strict";n.r(t),n("pDpN")},pDpN:function(e,t,n){var o,r;void 0===(r="function"==typeof(o=function(){"use strict";!function(e){const t=e.performance;function n(e){t&&t.mark&&t.mark(e)}function o(e,n){t&&t.measure&&t.measure(e,n)}n("Zone");const r=e.__Zone_symbol_prefix||"__zone_symbol__";function s(e){return r+e}const a=!0===e[s("forceDuplicateZoneCheck")];if(e.Zone){if(a||"function"!=typeof e.Zone.__symbol__)throw new Error("Zone already loaded.");return e.Zone}class i{constructor(e,t){this._parent=e,this._name=t?t.name||"unnamed":"",this._properties=t&&t.properties||{},this._zoneDelegate=new l(this,this._parent&&this._parent._zoneDelegate,t)}static assertZonePatched(){if(e.Promise!==C.ZoneAwarePromise)throw new Error("Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.\nMost likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)")}static get root(){let e=i.current;for(;e.parent;)e=e.parent;return e}static get current(){return z.zone}static get currentTask(){return j}static __load_patch(t,r){if(C.hasOwnProperty(t)){if(a)throw Error("Already loaded patch: "+t)}else if(!e["__Zone_disable_"+t]){const s="Zone:"+t;n(s),C[t]=r(e,i,O),o(s,s)}}get parent(){return this._parent}get name(){return this._name}get(e){const t=this.getZoneWith(e);if(t)return t._properties[e]}getZoneWith(e){let t=this;for(;t;){if(t._properties.hasOwnProperty(e))return t;t=t._parent}return null}fork(e){if(!e)throw new Error("ZoneSpec required!");return this._zoneDelegate.fork(this,e)}wrap(e,t){if("function"!=typeof e)throw new Error("Expecting function got: "+e);const n=this._zoneDelegate.intercept(this,e,t),o=this;return function(){return o.runGuarded(n,this,arguments,t)}}run(e,t,n,o){z={parent:z,zone:this};try{return this._zoneDelegate.invoke(this,e,t,n,o)}finally{z=z.parent}}runGuarded(e,t=null,n,o){z={parent:z,zone:this};try{try{return this._zoneDelegate.invoke(this,e,t,n,o)}catch(r){if(this._zoneDelegate.handleError(this,r))throw r}}finally{z=z.parent}}runTask(e,t,n){if(e.zone!=this)throw new Error("A task can only be run in the zone of creation! (Creation: "+(e.zone||y).name+"; Execution: "+this.name+")");if(e.state===v&&(e.type===P||e.type===D))return;const o=e.state!=E;o&&e._transitionTo(E,T),e.runCount++;const r=j;j=e,z={parent:z,zone:this};try{e.type==D&&e.data&&!e.data.isPeriodic&&(e.cancelFn=void 0);try{return this._zoneDelegate.invokeTask(this,e,t,n)}catch(s){if(this._zoneDelegate.handleError(this,s))throw s}}finally{e.state!==v&&e.state!==Z&&(e.type==P||e.data&&e.data.isPeriodic?o&&e._transitionTo(T,E):(e.runCount=0,this._updateTaskCount(e,-1),o&&e._transitionTo(v,E,v))),z=z.parent,j=r}}scheduleTask(e){if(e.zone&&e.zone!==this){let t=this;for(;t;){if(t===e.zone)throw Error(`can not reschedule task to ${this.name} which is descendants of the original zone ${e.zone.name}`);t=t.parent}}e._transitionTo(b,v);const t=[];e._zoneDelegates=t,e._zone=this;try{e=this._zoneDelegate.scheduleTask(this,e)}catch(n){throw e._transitionTo(Z,b,v),this._zoneDelegate.handleError(this,n),n}return e._zoneDelegates===t&&this._updateTaskCount(e,1),e.state==b&&e._transitionTo(T,b),e}scheduleMicroTask(e,t,n,o){return this.scheduleTask(new u(S,e,t,n,o,void 0))}scheduleMacroTask(e,t,n,o,r){return this.scheduleTask(new u(D,e,t,n,o,r))}scheduleEventTask(e,t,n,o,r){return this.scheduleTask(new u(P,e,t,n,o,r))}cancelTask(e){if(e.zone!=this)throw new Error("A task can only be cancelled in the zone of creation! (Creation: "+(e.zone||y).name+"; Execution: "+this.name+")");e._transitionTo(w,T,E);try{this._zoneDelegate.cancelTask(this,e)}catch(t){throw e._transitionTo(Z,w),this._zoneDelegate.handleError(this,t),t}return this._updateTaskCount(e,-1),e._transitionTo(v,w),e.runCount=0,e}_updateTaskCount(e,t){const n=e._zoneDelegates;-1==t&&(e._zoneDelegates=null);for(let o=0;oe.hasTask(n,o),onScheduleTask:(e,t,n,o)=>e.scheduleTask(n,o),onInvokeTask:(e,t,n,o,r,s)=>e.invokeTask(n,o,r,s),onCancelTask:(e,t,n,o)=>e.cancelTask(n,o)};class l{constructor(e,t,n){this._taskCounts={microTask:0,macroTask:0,eventTask:0},this.zone=e,this._parentDelegate=t,this._forkZS=n&&(n&&n.onFork?n:t._forkZS),this._forkDlgt=n&&(n.onFork?t:t._forkDlgt),this._forkCurrZone=n&&(n.onFork?this.zone:t._forkCurrZone),this._interceptZS=n&&(n.onIntercept?n:t._interceptZS),this._interceptDlgt=n&&(n.onIntercept?t:t._interceptDlgt),this._interceptCurrZone=n&&(n.onIntercept?this.zone:t._interceptCurrZone),this._invokeZS=n&&(n.onInvoke?n:t._invokeZS),this._invokeDlgt=n&&(n.onInvoke?t:t._invokeDlgt),this._invokeCurrZone=n&&(n.onInvoke?this.zone:t._invokeCurrZone),this._handleErrorZS=n&&(n.onHandleError?n:t._handleErrorZS),this._handleErrorDlgt=n&&(n.onHandleError?t:t._handleErrorDlgt),this._handleErrorCurrZone=n&&(n.onHandleError?this.zone:t._handleErrorCurrZone),this._scheduleTaskZS=n&&(n.onScheduleTask?n:t._scheduleTaskZS),this._scheduleTaskDlgt=n&&(n.onScheduleTask?t:t._scheduleTaskDlgt),this._scheduleTaskCurrZone=n&&(n.onScheduleTask?this.zone:t._scheduleTaskCurrZone),this._invokeTaskZS=n&&(n.onInvokeTask?n:t._invokeTaskZS),this._invokeTaskDlgt=n&&(n.onInvokeTask?t:t._invokeTaskDlgt),this._invokeTaskCurrZone=n&&(n.onInvokeTask?this.zone:t._invokeTaskCurrZone),this._cancelTaskZS=n&&(n.onCancelTask?n:t._cancelTaskZS),this._cancelTaskDlgt=n&&(n.onCancelTask?t:t._cancelTaskDlgt),this._cancelTaskCurrZone=n&&(n.onCancelTask?this.zone:t._cancelTaskCurrZone),this._hasTaskZS=null,this._hasTaskDlgt=null,this._hasTaskDlgtOwner=null,this._hasTaskCurrZone=null;const o=n&&n.onHasTask;(o||t&&t._hasTaskZS)&&(this._hasTaskZS=o?n:c,this._hasTaskDlgt=t,this._hasTaskDlgtOwner=this,this._hasTaskCurrZone=e,n.onScheduleTask||(this._scheduleTaskZS=c,this._scheduleTaskDlgt=t,this._scheduleTaskCurrZone=this.zone),n.onInvokeTask||(this._invokeTaskZS=c,this._invokeTaskDlgt=t,this._invokeTaskCurrZone=this.zone),n.onCancelTask||(this._cancelTaskZS=c,this._cancelTaskDlgt=t,this._cancelTaskCurrZone=this.zone))}fork(e,t){return this._forkZS?this._forkZS.onFork(this._forkDlgt,this.zone,e,t):new i(e,t)}intercept(e,t,n){return this._interceptZS?this._interceptZS.onIntercept(this._interceptDlgt,this._interceptCurrZone,e,t,n):t}invoke(e,t,n,o,r){return this._invokeZS?this._invokeZS.onInvoke(this._invokeDlgt,this._invokeCurrZone,e,t,n,o,r):t.apply(n,o)}handleError(e,t){return!this._handleErrorZS||this._handleErrorZS.onHandleError(this._handleErrorDlgt,this._handleErrorCurrZone,e,t)}scheduleTask(e,t){let n=t;if(this._scheduleTaskZS)this._hasTaskZS&&n._zoneDelegates.push(this._hasTaskDlgtOwner),n=this._scheduleTaskZS.onScheduleTask(this._scheduleTaskDlgt,this._scheduleTaskCurrZone,e,t),n||(n=t);else if(t.scheduleFn)t.scheduleFn(t);else{if(t.type!=S)throw new Error("Task is missing scheduleFn.");k(t)}return n}invokeTask(e,t,n,o){return this._invokeTaskZS?this._invokeTaskZS.onInvokeTask(this._invokeTaskDlgt,this._invokeTaskCurrZone,e,t,n,o):t.callback.apply(n,o)}cancelTask(e,t){let n;if(this._cancelTaskZS)n=this._cancelTaskZS.onCancelTask(this._cancelTaskDlgt,this._cancelTaskCurrZone,e,t);else{if(!t.cancelFn)throw Error("Task is not cancelable");n=t.cancelFn(t)}return n}hasTask(e,t){try{this._hasTaskZS&&this._hasTaskZS.onHasTask(this._hasTaskDlgt,this._hasTaskCurrZone,e,t)}catch(n){this.handleError(e,n)}}_updateTaskCount(e,t){const n=this._taskCounts,o=n[e],r=n[e]=o+t;if(r<0)throw new Error("More tasks executed then were scheduled.");0!=o&&0!=r||this.hasTask(this.zone,{microTask:n.microTask>0,macroTask:n.macroTask>0,eventTask:n.eventTask>0,change:e})}}class u{constructor(t,n,o,r,s,a){if(this._zone=null,this.runCount=0,this._zoneDelegates=null,this._state="notScheduled",this.type=t,this.source=n,this.data=r,this.scheduleFn=s,this.cancelFn=a,!o)throw new Error("callback is not defined");this.callback=o;const i=this;this.invoke=t===P&&r&&r.useG?u.invokeTask:function(){return u.invokeTask.call(e,i,this,arguments)}}static invokeTask(e,t,n){e||(e=this),I++;try{return e.runCount++,e.zone.runTask(e,t,n)}finally{1==I&&m(),I--}}get zone(){return this._zone}get state(){return this._state}cancelScheduleRequest(){this._transitionTo(v,b)}_transitionTo(e,t,n){if(this._state!==t&&this._state!==n)throw new Error(`${this.type} '${this.source}': can not transition to '${e}', expecting state '${t}'${n?" or '"+n+"'":""}, was '${this._state}'.`);this._state=e,e==v&&(this._zoneDelegates=null)}toString(){return this.data&&void 0!==this.data.handleId?this.data.handleId.toString():Object.prototype.toString.call(this)}toJSON(){return{type:this.type,state:this.state,source:this.source,zone:this.zone.name,runCount:this.runCount}}}const h=s("setTimeout"),p=s("Promise"),f=s("then");let d,g=[],_=!1;function k(t){if(0===I&&0===g.length)if(d||e[p]&&(d=e[p].resolve(0)),d){let e=d[f];e||(e=d.then),e.call(d,m)}else e[h](m,0);t&&g.push(t)}function m(){if(!_){for(_=!0;g.length;){const t=g;g=[];for(let n=0;nz,onUnhandledError:N,microtaskDrainDone:N,scheduleMicroTask:k,showUncaughtError:()=>!i[s("ignoreConsoleErrorUncaughtError")],patchEventTarget:()=>[],patchOnProperties:N,patchMethod:()=>N,bindArguments:()=>[],patchThen:()=>N,patchMacroTask:()=>N,setNativePromise:e=>{e&&"function"==typeof e.resolve&&(d=e.resolve(0))},patchEventPrototype:()=>N,isIEOrEdge:()=>!1,getGlobalObjects:()=>{},ObjectDefineProperty:()=>N,ObjectGetOwnPropertyDescriptor:()=>{},ObjectCreate:()=>{},ArraySlice:()=>[],patchClass:()=>N,wrapWithCurrentZone:()=>N,filterProperties:()=>[],attachOriginToPatched:()=>N,_redefineProperty:()=>N,patchCallbacks:()=>N};let z={parent:null,zone:new i(null,null)},j=null,I=0;function N(){}o("Zone","Zone"),e.Zone=i}("undefined"!=typeof window&&window||"undefined"!=typeof self&&self||global),Zone.__load_patch("ZoneAwarePromise",(e,t,n)=>{const o=Object.getOwnPropertyDescriptor,r=Object.defineProperty,s=n.symbol,a=[],i=!0===e[s("DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION")],c=s("Promise"),l=s("then");n.onUnhandledError=e=>{if(n.showUncaughtError()){const t=e&&e.rejection;t?console.error("Unhandled Promise rejection:",t instanceof Error?t.message:t,"; Zone:",e.zone.name,"; Task:",e.task&&e.task.source,"; Value:",t,t instanceof Error?t.stack:void 0):console.error(e)}},n.microtaskDrainDone=()=>{for(;a.length;){const t=a.shift();try{t.zone.runGuarded(()=>{throw t})}catch(e){h(e)}}};const u=s("unhandledPromiseRejectionHandler");function h(e){n.onUnhandledError(e);try{const n=t[u];"function"==typeof n&&n.call(this,e)}catch(o){}}function p(e){return e&&e.then}function f(e){return e}function d(e){return D.reject(e)}const g=s("state"),_=s("value"),k=s("finally"),m=s("parentPromiseValue"),y=s("parentPromiseState");function v(e,t){return n=>{try{T(e,t,n)}catch(o){T(e,!1,o)}}}const b=s("currentTaskTrace");function T(e,o,s){const c=function(){let e=!1;return function(t){return function(){e||(e=!0,t.apply(null,arguments))}}}();if(e===s)throw new TypeError("Promise resolved with itself");if(null===e[g]){let h=null;try{"object"!=typeof s&&"function"!=typeof s||(h=s&&s.then)}catch(u){return c(()=>{T(e,!1,u)})(),e}if(!1!==o&&s instanceof D&&s.hasOwnProperty(g)&&s.hasOwnProperty(_)&&null!==s[g])w(s),T(e,s[g],s[_]);else if(!1!==o&&"function"==typeof h)try{h.call(s,c(v(e,o)),c(v(e,!1)))}catch(u){c(()=>{T(e,!1,u)})()}else{e[g]=o;const c=e[_];if(e[_]=s,e[k]===k&&!0===o&&(e[g]=e[y],e[_]=e[m]),!1===o&&s instanceof Error){const e=t.currentTask&&t.currentTask.data&&t.currentTask.data.__creationTrace__;e&&r(s,b,{configurable:!0,enumerable:!1,writable:!0,value:e})}for(let t=0;t{try{const o=e[_],r=!!n&&k===n[k];r&&(n[m]=o,n[y]=s);const i=t.run(a,void 0,r&&a!==d&&a!==f?[]:[o]);T(n,!0,i)}catch(o){T(n,!1,o)}},n)}const S=function(){};class D{static toString(){return"function ZoneAwarePromise() { [native code] }"}static resolve(e){return T(new this(null),!0,e)}static reject(e){return T(new this(null),!1,e)}static race(e){let t,n,o=new this((e,o)=>{t=e,n=o});function r(e){t(e)}function s(e){n(e)}for(let a of e)p(a)||(a=this.resolve(a)),a.then(r,s);return o}static all(e){return D.allWithCallback(e)}static allSettled(e){return(this&&this.prototype instanceof D?this:D).allWithCallback(e,{thenCallback:e=>({status:"fulfilled",value:e}),errorCallback:e=>({status:"rejected",reason:e})})}static allWithCallback(e,t){let n,o,r=new this((e,t)=>{n=e,o=t}),s=2,a=0;const i=[];for(let l of e){p(l)||(l=this.resolve(l));const e=a;try{l.then(o=>{i[e]=t?t.thenCallback(o):o,s--,0===s&&n(i)},r=>{t?(i[e]=t.errorCallback(r),s--,0===s&&n(i)):o(r)})}catch(c){o(c)}s++,a++}return s-=2,0===s&&n(i),r}constructor(e){const t=this;if(!(t instanceof D))throw new Error("Must be an instanceof Promise.");t[g]=null,t[_]=[];try{e&&e(v(t,!0),v(t,!1))}catch(n){T(t,!1,n)}}get[Symbol.toStringTag](){return"Promise"}get[Symbol.species](){return D}then(e,n){let o=this.constructor[Symbol.species];o&&"function"==typeof o||(o=this.constructor||D);const r=new o(S),s=t.current;return null==this[g]?this[_].push(s,r,e,n):Z(this,s,r,e,n),r}catch(e){return this.then(null,e)}finally(e){let n=this.constructor[Symbol.species];n&&"function"==typeof n||(n=D);const o=new n(S);o[k]=k;const r=t.current;return null==this[g]?this[_].push(r,o,e,e):Z(this,r,o,e,e),o}}D.resolve=D.resolve,D.reject=D.reject,D.race=D.race,D.all=D.all;const P=e[c]=e.Promise,C=t.__symbol__("ZoneAwarePromise");let O=o(e,"Promise");O&&!O.configurable||(O&&delete O.writable,O&&delete O.value,O||(O={configurable:!0,enumerable:!0}),O.get=function(){return e[C]?e[C]:e[c]},O.set=function(t){t===D?e[C]=t:(e[c]=t,t.prototype[l]||j(t),n.setNativePromise(t))},r(e,"Promise",O)),e.Promise=D;const z=s("thenPatched");function j(e){const t=e.prototype,n=o(t,"then");if(n&&(!1===n.writable||!n.configurable))return;const r=t.then;t[l]=r,e.prototype.then=function(e,t){return new D((e,t)=>{r.call(this,e,t)}).then(e,t)},e[z]=!0}if(n.patchThen=j,P){j(P);const t=e.fetch;"function"==typeof t&&(e[n.symbol("fetch")]=t,e.fetch=(I=t,function(){let e=I.apply(this,arguments);if(e instanceof D)return e;let t=e.constructor;return t[z]||j(t),e}))}var I;return Promise[t.__symbol__("uncaughtPromiseErrors")]=a,D});const e=Object.getOwnPropertyDescriptor,t=Object.defineProperty,n=Object.getPrototypeOf,o=Object.create,r=Array.prototype.slice,s=Zone.__symbol__("addEventListener"),a=Zone.__symbol__("removeEventListener"),i=Zone.__symbol__("");function c(e,t){return Zone.current.wrap(e,t)}function l(e,t,n,o,r){return Zone.current.scheduleMacroTask(e,t,n,o,r)}const u=Zone.__symbol__,h="undefined"!=typeof window,p=h?window:void 0,f=h&&p||"object"==typeof self&&self||global,d=[null];function g(e,t){for(let n=e.length-1;n>=0;n--)"function"==typeof e[n]&&(e[n]=c(e[n],t+"_"+n));return e}function _(e){return!e||!1!==e.writable&&!("function"==typeof e.get&&void 0===e.set)}const k="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,m=!("nw"in f)&&void 0!==f.process&&"[object process]"==={}.toString.call(f.process),y=!m&&!k&&!(!h||!p.HTMLElement),v=void 0!==f.process&&"[object process]"==={}.toString.call(f.process)&&!k&&!(!h||!p.HTMLElement),b={},T=function(e){if(!(e=e||f.event))return;let t=b[e.type];t||(t=b[e.type]=u("ON_PROPERTY"+e.type));const n=this||e.target||f,o=n[t];let r;if(y&&n===p&&"error"===e.type){const t=e;r=o&&o.call(this,t.message,t.filename,t.lineno,t.colno,t.error),!0===r&&e.preventDefault()}else r=o&&o.apply(this,arguments),null==r||r||e.preventDefault();return r};function E(n,o,r){let s=e(n,o);if(!s&&r&&e(r,o)&&(s={enumerable:!0,configurable:!0}),!s||!s.configurable)return;const a=u("on"+o+"patched");if(n.hasOwnProperty(a)&&n[a])return;delete s.writable,delete s.value;const i=s.get,c=s.set,l=o.substr(2);let h=b[l];h||(h=b[l]=u("ON_PROPERTY"+l)),s.set=function(e){let t=this;t||n!==f||(t=f),t&&(t[h]&&t.removeEventListener(l,T),c&&c.apply(t,d),"function"==typeof e?(t[h]=e,t.addEventListener(l,T,!1)):t[h]=null)},s.get=function(){let e=this;if(e||n!==f||(e=f),!e)return null;const t=e[h];if(t)return t;if(i){let t=i&&i.call(this);if(t)return s.set.call(this,t),"function"==typeof e.removeAttribute&&e.removeAttribute(o),t}return null},t(n,o,s),n[a]=!0}function w(e,t,n){if(t)for(let o=0;ofunction(t,o){const s=n(t,o);return s.cbIdx>=0&&"function"==typeof o[s.cbIdx]?l(s.name,o[s.cbIdx],s,r):e.apply(t,o)})}function C(e,t){e[u("OriginalDelegate")]=t}let O=!1,z=!1;function j(){try{const e=p.navigator.userAgent;if(-1!==e.indexOf("MSIE ")||-1!==e.indexOf("Trident/"))return!0}catch(e){}return!1}function I(){if(O)return z;O=!0;try{const e=p.navigator.userAgent;-1===e.indexOf("MSIE ")&&-1===e.indexOf("Trident/")&&-1===e.indexOf("Edge/")||(z=!0)}catch(e){}return z}Zone.__load_patch("toString",e=>{const t=Function.prototype.toString,n=u("OriginalDelegate"),o=u("Promise"),r=u("Error"),s=function(){if("function"==typeof this){const s=this[n];if(s)return"function"==typeof s?t.call(s):Object.prototype.toString.call(s);if(this===Promise){const n=e[o];if(n)return t.call(n)}if(this===Error){const n=e[r];if(n)return t.call(n)}}return t.call(this)};s[n]=t,Function.prototype.toString=s;const a=Object.prototype.toString;Object.prototype.toString=function(){return this instanceof Promise?"[object Promise]":a.call(this)}});let N=!1;if("undefined"!=typeof window)try{const e=Object.defineProperty({},"passive",{get:function(){N=!0}});window.addEventListener("test",e,e),window.removeEventListener("test",e,e)}catch(ie){N=!1}const R={useG:!0},x={},L={},M=new RegExp("^"+i+"(\\w+)(true|false)$"),A=u("propagationStopped");function H(e,t){const n=(t?t(e):e)+"false",o=(t?t(e):e)+"true",r=i+n,s=i+o;x[e]={},x[e].false=r,x[e].true=s}function F(e,t,o){const r=o&&o.add||"addEventListener",s=o&&o.rm||"removeEventListener",a=o&&o.listeners||"eventListeners",c=o&&o.rmAll||"removeAllListeners",l=u(r),h="."+r+":",p=function(e,t,n){if(e.isRemoved)return;const o=e.callback;"object"==typeof o&&o.handleEvent&&(e.callback=e=>o.handleEvent(e),e.originalDelegate=o),e.invoke(e,t,[n]);const r=e.options;r&&"object"==typeof r&&r.once&&t[s].call(t,n.type,e.originalDelegate?e.originalDelegate:e.callback,r)},f=function(t){if(!(t=t||e.event))return;const n=this||t.target||e,o=n[x[t.type].false];if(o)if(1===o.length)p(o[0],n,t);else{const e=o.slice();for(let o=0;ofunction(t,n){t[A]=!0,e&&e.apply(t,n)})}function q(e,t,n,o,r){const s=Zone.__symbol__(o);if(t[s])return;const a=t[s]=t[o];t[o]=function(s,i,c){return i&&i.prototype&&r.forEach((function(t){const r=`${n}.${o}::`+t,s=i.prototype;if(s.hasOwnProperty(t)){const n=e.ObjectGetOwnPropertyDescriptor(s,t);n&&n.value?(n.value=e.wrapWithCurrentZone(n.value,r),e._redefineProperty(i.prototype,t,n)):s[t]&&(s[t]=e.wrapWithCurrentZone(s[t],r))}else s[t]&&(s[t]=e.wrapWithCurrentZone(s[t],r))})),a.call(t,s,i,c)},e.attachOriginToPatched(t[o],a)}const W=["absolutedeviceorientation","afterinput","afterprint","appinstalled","beforeinstallprompt","beforeprint","beforeunload","devicelight","devicemotion","deviceorientation","deviceorientationabsolute","deviceproximity","hashchange","languagechange","message","mozbeforepaint","offline","online","paint","pageshow","pagehide","popstate","rejectionhandled","storage","unhandledrejection","unload","userproximity","vrdisplayconnected","vrdisplaydisconnected","vrdisplaypresentchange"],U=["encrypted","waitingforkey","msneedkey","mozinterruptbegin","mozinterruptend"],V=["load"],$=["blur","error","focus","load","resize","scroll","messageerror"],X=["bounce","finish","start"],J=["loadstart","progress","abort","error","load","progress","timeout","loadend","readystatechange"],Y=["upgradeneeded","complete","abort","success","error","blocked","versionchange","close"],K=["close","error","open","message"],Q=["error","message"],ee=["abort","animationcancel","animationend","animationiteration","auxclick","beforeinput","blur","cancel","canplay","canplaythrough","change","compositionstart","compositionupdate","compositionend","cuechange","click","close","contextmenu","curechange","dblclick","drag","dragend","dragenter","dragexit","dragleave","dragover","drop","durationchange","emptied","ended","error","focus","focusin","focusout","gotpointercapture","input","invalid","keydown","keypress","keyup","load","loadstart","loadeddata","loadedmetadata","lostpointercapture","mousedown","mouseenter","mouseleave","mousemove","mouseout","mouseover","mouseup","mousewheel","orientationchange","pause","play","playing","pointercancel","pointerdown","pointerenter","pointerleave","pointerlockchange","mozpointerlockchange","webkitpointerlockerchange","pointerlockerror","mozpointerlockerror","webkitpointerlockerror","pointermove","pointout","pointerover","pointerup","progress","ratechange","reset","resize","scroll","seeked","seeking","select","selectionchange","selectstart","show","sort","stalled","submit","suspend","timeupdate","volumechange","touchcancel","touchmove","touchstart","touchend","transitioncancel","transitionend","waiting","wheel"].concat(["webglcontextrestored","webglcontextlost","webglcontextcreationerror"],["autocomplete","autocompleteerror"],["toggle"],["afterscriptexecute","beforescriptexecute","DOMContentLoaded","freeze","fullscreenchange","mozfullscreenchange","webkitfullscreenchange","msfullscreenchange","fullscreenerror","mozfullscreenerror","webkitfullscreenerror","msfullscreenerror","readystatechange","visibilitychange","resume"],W,["beforecopy","beforecut","beforepaste","copy","cut","paste","dragstart","loadend","animationstart","search","transitionrun","transitionstart","webkitanimationend","webkitanimationiteration","webkitanimationstart","webkittransitionend"],["activate","afterupdate","ariarequest","beforeactivate","beforedeactivate","beforeeditfocus","beforeupdate","cellchange","controlselect","dataavailable","datasetchanged","datasetcomplete","errorupdate","filterchange","layoutcomplete","losecapture","move","moveend","movestart","propertychange","resizeend","resizestart","rowenter","rowexit","rowsdelete","rowsinserted","command","compassneedscalibration","deactivate","help","mscontentzoom","msmanipulationstatechanged","msgesturechange","msgesturedoubletap","msgestureend","msgesturehold","msgesturestart","msgesturetap","msgotpointercapture","msinertiastart","mslostpointercapture","mspointercancel","mspointerdown","mspointerenter","mspointerhover","mspointerleave","mspointermove","mspointerout","mspointerover","mspointerup","pointerout","mssitemodejumplistitemremoved","msthumbnailclick","stop","storagecommit"]);function te(e,t,n){if(!n||0===n.length)return t;const o=n.filter(t=>t.target===e);if(!o||0===o.length)return t;const r=o[0].ignoreProperties;return t.filter(e=>-1===r.indexOf(e))}function ne(e,t,n,o){e&&w(e,te(e,t,n),o)}function oe(e,t){if(m&&!v)return;if(Zone[e.symbol("patchEvents")])return;const o="undefined"!=typeof WebSocket,r=t.__Zone_ignore_on_properties;if(y){const e=window,t=j?[{target:e,ignoreProperties:["error"]}]:[];ne(e,ee.concat(["messageerror"]),r?r.concat(t):r,n(e)),ne(Document.prototype,ee,r),void 0!==e.SVGElement&&ne(e.SVGElement.prototype,ee,r),ne(Element.prototype,ee,r),ne(HTMLElement.prototype,ee,r),ne(HTMLMediaElement.prototype,U,r),ne(HTMLFrameSetElement.prototype,W.concat($),r),ne(HTMLBodyElement.prototype,W.concat($),r),ne(HTMLFrameElement.prototype,V,r),ne(HTMLIFrameElement.prototype,V,r);const o=e.HTMLMarqueeElement;o&&ne(o.prototype,X,r);const s=e.Worker;s&&ne(s.prototype,Q,r)}const s=t.XMLHttpRequest;s&&ne(s.prototype,J,r);const a=t.XMLHttpRequestEventTarget;a&&ne(a&&a.prototype,J,r),"undefined"!=typeof IDBIndex&&(ne(IDBIndex.prototype,Y,r),ne(IDBRequest.prototype,Y,r),ne(IDBOpenDBRequest.prototype,Y,r),ne(IDBDatabase.prototype,Y,r),ne(IDBTransaction.prototype,Y,r),ne(IDBCursor.prototype,Y,r)),o&&ne(WebSocket.prototype,K,r)}Zone.__load_patch("util",(n,s,a)=>{a.patchOnProperties=w,a.patchMethod=D,a.bindArguments=g,a.patchMacroTask=P;const l=s.__symbol__("BLACK_LISTED_EVENTS"),u=s.__symbol__("UNPATCHED_EVENTS");n[u]&&(n[l]=n[u]),n[l]&&(s[l]=s[u]=n[l]),a.patchEventPrototype=B,a.patchEventTarget=F,a.isIEOrEdge=I,a.ObjectDefineProperty=t,a.ObjectGetOwnPropertyDescriptor=e,a.ObjectCreate=o,a.ArraySlice=r,a.patchClass=S,a.wrapWithCurrentZone=c,a.filterProperties=te,a.attachOriginToPatched=C,a._redefineProperty=Object.defineProperty,a.patchCallbacks=q,a.getGlobalObjects=()=>({globalSources:L,zoneSymbolEventNames:x,eventNames:ee,isBrowser:y,isMix:v,isNode:m,TRUE_STR:"true",FALSE_STR:"false",ZONE_SYMBOL_PREFIX:i,ADD_EVENT_LISTENER_STR:"addEventListener",REMOVE_EVENT_LISTENER_STR:"removeEventListener"})});const re=u("zoneTask");function se(e,t,n,o){let r=null,s=null;n+=o;const a={};function i(t){const n=t.data;return n.args[0]=function(){try{t.invoke.apply(this,arguments)}finally{t.data&&t.data.isPeriodic||("number"==typeof n.handleId?delete a[n.handleId]:n.handleId&&(n.handleId[re]=null))}},n.handleId=r.apply(e,n.args),t}function c(e){return s(e.data.handleId)}r=D(e,t+=o,n=>function(r,s){if("function"==typeof s[0]){const e=l(t,s[0],{isPeriodic:"Interval"===o,delay:"Timeout"===o||"Interval"===o?s[1]||0:void 0,args:s},i,c);if(!e)return e;const n=e.data.handleId;return"number"==typeof n?a[n]=e:n&&(n[re]=e),n&&n.ref&&n.unref&&"function"==typeof n.ref&&"function"==typeof n.unref&&(e.ref=n.ref.bind(n),e.unref=n.unref.bind(n)),"number"==typeof n||n?n:e}return n.apply(e,s)}),s=D(e,n,t=>function(n,o){const r=o[0];let s;"number"==typeof r?s=a[r]:(s=r&&r[re],s||(s=r)),s&&"string"==typeof s.type?"notScheduled"!==s.state&&(s.cancelFn&&s.data.isPeriodic||0===s.runCount)&&("number"==typeof r?delete a[r]:r&&(r[re]=null),s.zone.cancelTask(s)):t.apply(e,o)})}function ae(e,t){if(Zone[t.symbol("patchEventTarget")])return;const{eventNames:n,zoneSymbolEventNames:o,TRUE_STR:r,FALSE_STR:s,ZONE_SYMBOL_PREFIX:a}=t.getGlobalObjects();for(let c=0;c{const t=e[Zone.__symbol__("legacyPatch")];t&&t()}),Zone.__load_patch("timers",e=>{se(e,"set","clear","Timeout"),se(e,"set","clear","Interval"),se(e,"set","clear","Immediate")}),Zone.__load_patch("requestAnimationFrame",e=>{se(e,"request","cancel","AnimationFrame"),se(e,"mozRequest","mozCancel","AnimationFrame"),se(e,"webkitRequest","webkitCancel","AnimationFrame")}),Zone.__load_patch("blocking",(e,t)=>{const n=["alert","prompt","confirm"];for(let o=0;ofunction(o,s){return t.current.run(n,e,s,r)})}),Zone.__load_patch("EventTarget",(e,t,n)=>{!function(e,t){t.patchEventPrototype(e,t)}(e,n),ae(e,n);const o=e.XMLHttpRequestEventTarget;o&&o.prototype&&n.patchEventTarget(e,[o.prototype]),S("MutationObserver"),S("WebKitMutationObserver"),S("IntersectionObserver"),S("FileReader")}),Zone.__load_patch("on_property",(e,t,n)=>{oe(n,e)}),Zone.__load_patch("customElements",(e,t,n)=>{!function(e,t){const{isBrowser:n,isMix:o}=t.getGlobalObjects();(n||o)&&e.customElements&&"customElements"in e&&t.patchCallbacks(t,e.customElements,"customElements","define",["connectedCallback","disconnectedCallback","adoptedCallback","attributeChangedCallback"])}(e,n)}),Zone.__load_patch("XHR",(e,t)=>{!function(e){const p=e.XMLHttpRequest;if(!p)return;const f=p.prototype;let d=f[s],g=f[a];if(!d){const t=e.XMLHttpRequestEventTarget;if(t){const e=t.prototype;d=e[s],g=e[a]}}function _(e){const o=e.data,c=o.target;c[i]=!1,c[h]=!1;const l=c[r];d||(d=c[s],g=c[a]),l&&g.call(c,"readystatechange",l);const u=c[r]=()=>{if(c.readyState===c.DONE)if(!o.aborted&&c[i]&&"scheduled"===e.state){const n=c[t.__symbol__("loadfalse")];if(n&&n.length>0){const r=e.invoke;e.invoke=function(){const n=c[t.__symbol__("loadfalse")];for(let t=0;tfunction(e,t){return e[o]=0==t[2],e[c]=t[1],y.apply(e,t)}),v=u("fetchTaskAborting"),b=u("fetchTaskScheduling"),T=D(f,"send",()=>function(e,n){if(!0===t.current[b])return T.apply(e,n);if(e[o])return T.apply(e,n);{const t={target:e,url:e[c],isPeriodic:!1,args:n,aborted:!1},o=l("XMLHttpRequest.send",k,t,_,m);e&&!0===e[h]&&!t.aborted&&"scheduled"===o.state&&o.invoke()}}),E=D(f,"abort",()=>function(e,o){const r=e[n];if(r&&"string"==typeof r.type){if(null==r.cancelFn||r.data&&r.data.aborted)return;r.zone.cancelTask(r)}else if(!0===t.current[v])return E.apply(e,o)})}(e);const n=u("xhrTask"),o=u("xhrSync"),r=u("xhrListener"),i=u("xhrScheduled"),c=u("xhrURL"),h=u("xhrErrorBeforeScheduled")}),Zone.__load_patch("geolocation",t=>{t.navigator&&t.navigator.geolocation&&function(t,n){const o=t.constructor.name;for(let r=0;r{const t=function(){return e.apply(this,g(arguments,o+"."+s))};return C(t,e),t})(a)}}}(t.navigator.geolocation,["getCurrentPosition","watchPosition"])}),Zone.__load_patch("PromiseRejectionEvent",(e,t)=>{function n(t){return function(n){G(e,t).forEach(o=>{const r=e.PromiseRejectionEvent;if(r){const e=new r(t,{promise:n.promise,reason:n.rejection});o.invoke(e)}})}}e.PromiseRejectionEvent&&(t[u("unhandledPromiseRejectionHandler")]=n("unhandledrejection"),t[u("rejectionHandledHandler")]=n("rejectionhandled"))})})?o.call(t,n,t,e):o)||(e.exports=r)}},[[1,0]]]); \ No newline at end of file diff --git a/www/runtime.e227d1a0e31cbccbf8ec.js b/www/runtime.e227d1a0e31cbccbf8ec.js new file mode 100644 index 00000000000..effa6aee759 --- /dev/null +++ b/www/runtime.e227d1a0e31cbccbf8ec.js @@ -0,0 +1 @@ +!function(e){function r(r){for(var n,l,f=r[0],i=r[1],p=r[2],c=0,s=[];c { + self.skipWaiting(); +}); + +self.addEventListener('activate', event => { + event.waitUntil(self.clients.claim()); + self.registration.unregister().then(() => { + console.log('NGSW Safety Worker - unregistered old service worker'); + }); +}); diff --git a/www/styles.55ddde32a7bc7cfb8ba7.css b/www/styles.55ddde32a7bc7cfb8ba7.css new file mode 100644 index 00000000000..37c11d75723 --- /dev/null +++ b/www/styles.55ddde32a7bc7cfb8ba7.css @@ -0,0 +1,4 @@ +.xterm{font-feature-settings:"liga" 0;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm{cursor:text}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:.5}.xterm-underline{text-decoration:underline}.mat-badge-content{font-weight:600;font-size:12px;font-family:Roboto,Helvetica Neue,sans-serif}.mat-badge-small .mat-badge-content{font-size:9px}.mat-badge-large .mat-badge-content{font-size:24px}.mat-h1,.mat-headline,.mat-typography h1{font:400 24px/32px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal;margin:0 0 16px}.mat-h2,.mat-title,.mat-typography h2{font:500 20px/32px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal;margin:0 0 16px}.mat-h3,.mat-subheading-2,.mat-typography h3{font:400 16px/28px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal;margin:0 0 16px}.mat-h4,.mat-subheading-1,.mat-typography h4{font:400 15px/24px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal;margin:0 0 16px}.mat-h5,.mat-typography h5{font:400 calc(14px * .83)/20px Roboto,Helvetica Neue,sans-serif;margin:0 0 12px}.mat-h6,.mat-typography h6{font:400 calc(14px * .67)/20px Roboto,Helvetica Neue,sans-serif;margin:0 0 12px}.mat-body-2,.mat-body-strong{font:500 14px/24px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal}.mat-body,.mat-body-1,.mat-typography{font:400 14px/20px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal}.mat-body-1 p,.mat-body p,.mat-typography p{margin:0 0 12px}.mat-caption,.mat-small{font:400 12px/20px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal}.mat-display-4,.mat-typography .mat-display-4{font:300 112px/112px Roboto,Helvetica Neue,sans-serif;letter-spacing:-.05em;margin:0 0 56px}.mat-display-3,.mat-typography .mat-display-3{font:400 56px/56px Roboto,Helvetica Neue,sans-serif;letter-spacing:-.02em;margin:0 0 64px}.mat-display-2,.mat-typography .mat-display-2{font:400 45px/48px Roboto,Helvetica Neue,sans-serif;letter-spacing:-.005em;margin:0 0 64px}.mat-display-1,.mat-typography .mat-display-1{font:400 34px/40px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal;margin:0 0 64px}.mat-bottom-sheet-container{font:400 14px/20px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal}.mat-button,.mat-fab,.mat-flat-button,.mat-icon-button,.mat-mini-fab,.mat-raised-button,.mat-stroked-button{font-family:Roboto,Helvetica Neue,sans-serif;font-size:14px;font-weight:500}.mat-button-toggle,.mat-card{font-family:Roboto,Helvetica Neue,sans-serif}.mat-card-title{font-size:24px;font-weight:500}.mat-card-header .mat-card-title{font-size:20px}.mat-card-content,.mat-card-subtitle{font-size:14px}.mat-checkbox{font-family:Roboto,Helvetica Neue,sans-serif}.mat-checkbox-layout .mat-checkbox-label{line-height:24px}.mat-chip{font-size:14px;font-weight:500}.mat-chip .mat-chip-remove.mat-icon,.mat-chip .mat-chip-trailing-icon.mat-icon{font-size:18px}.mat-table{font-family:Roboto,Helvetica Neue,sans-serif}.mat-header-cell{font-size:12px;font-weight:500}.mat-cell,.mat-footer-cell{font-size:14px}.mat-calendar{font-family:Roboto,Helvetica Neue,sans-serif}.mat-calendar-body{font-size:13px}.mat-calendar-body-label,.mat-calendar-period-button{font-size:14px;font-weight:500}.mat-calendar-table-header th{font-size:11px;font-weight:400}.mat-dialog-title{font:500 20px/32px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal}.mat-expansion-panel-header{font-family:Roboto,Helvetica Neue,sans-serif;font-size:15px;font-weight:400}.mat-expansion-panel-content{font:400 14px/20px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal}.mat-form-field{font-size:inherit;font-weight:400;line-height:1.125;font-family:Roboto,Helvetica Neue,sans-serif;letter-spacing:normal}.mat-form-field-wrapper{padding-bottom:1.34375em}.mat-form-field-prefix .mat-icon,.mat-form-field-suffix .mat-icon{font-size:150%;line-height:1.125}.mat-form-field-prefix .mat-icon-button,.mat-form-field-suffix .mat-icon-button{height:1.5em;width:1.5em}.mat-form-field-prefix .mat-icon-button .mat-icon,.mat-form-field-suffix .mat-icon-button .mat-icon{height:1.125em;line-height:1.125}.mat-form-field-infix{padding:.5em 0;border-top:.84375em solid transparent}.mat-form-field-can-float.mat-form-field-should-float .mat-form-field-label,.mat-form-field-can-float .mat-input-server:focus+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-1.34375em) scale(.75);width:133.3333333333%}.mat-form-field-can-float .mat-input-server[label]:not(:label-shown)+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-1.34374em) scale(.75);width:133.3333433333%}.mat-form-field-label-wrapper{top:-.84375em;padding-top:.84375em}.mat-form-field-label{top:1.34375em}.mat-form-field-underline{bottom:1.34375em}.mat-form-field-subscript-wrapper{font-size:75%;margin-top:.6666666667em;top:calc(100% - 1.7916666667em)}.mat-form-field-appearance-legacy .mat-form-field-wrapper{padding-bottom:1.25em}.mat-form-field-appearance-legacy .mat-form-field-infix{padding:.4375em 0}.mat-form-field-appearance-legacy.mat-form-field-can-float.mat-form-field-should-float .mat-form-field-label,.mat-form-field-appearance-legacy.mat-form-field-can-float .mat-input-server:focus+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-1.28125em) scale(.75) perspective(100px) translateZ(.001px);-ms-transform:translateY(-1.28125em) scale(.75);width:133.3333333333%}.mat-form-field-appearance-legacy.mat-form-field-can-float .mat-form-field-autofill-control:-webkit-autofill+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-1.28125em) scale(.75) perspective(100px) translateZ(.00101px);-ms-transform:translateY(-1.28124em) scale(.75);width:133.3333433333%}.mat-form-field-appearance-legacy.mat-form-field-can-float .mat-input-server[label]:not(:label-shown)+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-1.28125em) scale(.75) perspective(100px) translateZ(.00102px);-ms-transform:translateY(-1.28123em) scale(.75);width:133.3333533333%}.mat-form-field-appearance-legacy .mat-form-field-label{top:1.28125em}.mat-form-field-appearance-legacy .mat-form-field-underline{bottom:1.25em}.mat-form-field-appearance-legacy .mat-form-field-subscript-wrapper{margin-top:.5416666667em;top:calc(100% - 1.6666666667em)}@media print{.mat-form-field-appearance-legacy.mat-form-field-can-float.mat-form-field-should-float .mat-form-field-label,.mat-form-field-appearance-legacy.mat-form-field-can-float .mat-input-server:focus+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-1.28122em) scale(.75)}.mat-form-field-appearance-legacy.mat-form-field-can-float .mat-form-field-autofill-control:-webkit-autofill+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-1.28121em) scale(.75)}.mat-form-field-appearance-legacy.mat-form-field-can-float .mat-input-server[label]:not(:label-shown)+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-1.2812em) scale(.75)}}.mat-form-field-appearance-fill .mat-form-field-infix{padding:.25em 0 .75em}.mat-form-field-appearance-fill .mat-form-field-label{top:1.09375em;margin-top:-.5em}.mat-form-field-appearance-fill.mat-form-field-can-float.mat-form-field-should-float .mat-form-field-label,.mat-form-field-appearance-fill.mat-form-field-can-float .mat-input-server:focus+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-.59375em) scale(.75);width:133.3333333333%}.mat-form-field-appearance-fill.mat-form-field-can-float .mat-input-server[label]:not(:label-shown)+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-.59374em) scale(.75);width:133.3333433333%}.mat-form-field-appearance-outline .mat-form-field-infix{padding:1em 0}.mat-form-field-appearance-outline .mat-form-field-label{top:1.84375em;margin-top:-.25em}.mat-form-field-appearance-outline.mat-form-field-can-float.mat-form-field-should-float .mat-form-field-label,.mat-form-field-appearance-outline.mat-form-field-can-float .mat-input-server:focus+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-1.59375em) scale(.75);width:133.3333333333%}.mat-form-field-appearance-outline.mat-form-field-can-float .mat-input-server[label]:not(:label-shown)+.mat-form-field-label-wrapper .mat-form-field-label{transform:translateY(-1.59374em) scale(.75);width:133.3333433333%}.mat-grid-tile-footer,.mat-grid-tile-header{font-size:14px}.mat-grid-tile-footer .mat-line,.mat-grid-tile-header .mat-line{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;box-sizing:border-box}.mat-grid-tile-footer .mat-line:nth-child(n+2),.mat-grid-tile-header .mat-line:nth-child(n+2){font-size:12px}input.mat-input-element{margin-top:-.0625em}.mat-menu-item{font-family:Roboto,Helvetica Neue,sans-serif;font-size:14px;font-weight:400}.mat-paginator,.mat-paginator-page-size .mat-select-trigger{font-family:Roboto,Helvetica Neue,sans-serif;font-size:12px}.mat-radio-button,.mat-select{font-family:Roboto,Helvetica Neue,sans-serif}.mat-select-trigger{height:1.125em}.mat-slide-toggle-content,.mat-slider-thumb-label-text{font-family:Roboto,Helvetica Neue,sans-serif}.mat-slider-thumb-label-text{font-size:12px;font-weight:500}.mat-stepper-horizontal,.mat-stepper-vertical{font-family:Roboto,Helvetica Neue,sans-serif}.mat-step-label{font-size:14px;font-weight:400}.mat-step-sub-label-error{font-weight:400}.mat-step-label-error{font-size:14px}.mat-step-label-selected{font-size:14px;font-weight:500}.mat-tab-group,.mat-tab-label,.mat-tab-link{font-family:Roboto,Helvetica Neue,sans-serif}.mat-tab-label,.mat-tab-link{font-size:14px;font-weight:500}.mat-toolbar,.mat-toolbar h1,.mat-toolbar h2,.mat-toolbar h3,.mat-toolbar h4,.mat-toolbar h5,.mat-toolbar h6{font:500 20px/32px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal;margin:0}.mat-tooltip{font-family:Roboto,Helvetica Neue,sans-serif;font-size:10px;padding-top:6px;padding-bottom:6px}.mat-tooltip-handset{font-size:14px;padding-top:8px;padding-bottom:8px}.mat-list-item,.mat-list-option{font-family:Roboto,Helvetica Neue,sans-serif}.mat-list-base .mat-list-item{font-size:16px}.mat-list-base .mat-list-item .mat-line{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;box-sizing:border-box}.mat-list-base .mat-list-item .mat-line:nth-child(n+2){font-size:14px}.mat-list-base .mat-list-option{font-size:16px}.mat-list-base .mat-list-option .mat-line{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;box-sizing:border-box}.mat-list-base .mat-list-option .mat-line:nth-child(n+2){font-size:14px}.mat-list-base .mat-subheader{font-family:Roboto,Helvetica Neue,sans-serif;font-size:14px;font-weight:500}.mat-list-base[dense] .mat-list-item{font-size:12px}.mat-list-base[dense] .mat-list-item .mat-line{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;box-sizing:border-box}.mat-list-base[dense] .mat-list-item .mat-line:nth-child(n+2),.mat-list-base[dense] .mat-list-option{font-size:12px}.mat-list-base[dense] .mat-list-option .mat-line{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;box-sizing:border-box}.mat-list-base[dense] .mat-list-option .mat-line:nth-child(n+2){font-size:12px}.mat-list-base[dense] .mat-subheader{font-family:Roboto,Helvetica Neue,sans-serif;font-size:12px;font-weight:500}.mat-option{font-family:Roboto,Helvetica Neue,sans-serif;font-size:16px}.mat-optgroup-label{font:500 14px/24px Roboto,Helvetica Neue,sans-serif;letter-spacing:normal}.mat-simple-snackbar{font-family:Roboto,Helvetica Neue,sans-serif;font-size:14px}.mat-simple-snackbar-action{line-height:1;font-family:inherit;font-size:inherit;font-weight:500}.mat-tree{font-family:Roboto,Helvetica Neue,sans-serif}.mat-nested-tree-node,.mat-tree-node{font-weight:400;font-size:14px}.mat-ripple{overflow:hidden;position:relative}.mat-ripple:not(:empty){transform:translateZ(0)}.mat-ripple.mat-ripple-unbounded{overflow:visible}.mat-ripple-element{position:absolute;border-radius:50%;pointer-events:none;transition:opacity,transform 0ms cubic-bezier(0,0,.2,1);transform:scale(0)}.cdk-high-contrast-active .mat-ripple-element{display:none}.cdk-visually-hidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;outline:0;-webkit-appearance:none;-moz-appearance:none}.cdk-global-overlay-wrapper,.cdk-overlay-container{pointer-events:none;top:0;left:0;height:100%;width:100%}.cdk-overlay-container{position:fixed;z-index:1000}.cdk-overlay-container:empty{display:none}.cdk-global-overlay-wrapper,.cdk-overlay-pane{display:flex;position:absolute;z-index:1000}.cdk-overlay-pane{pointer-events:auto;box-sizing:border-box;max-width:100%;max-height:100%}.cdk-overlay-backdrop{position:absolute;top:0;bottom:0;left:0;right:0;z-index:1000;pointer-events:auto;-webkit-tap-highlight-color:transparent;transition:opacity .4s cubic-bezier(.25,.8,.25,1);opacity:0}.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:1}@media screen and (-ms-high-contrast:active){.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:.6}}.cdk-overlay-dark-backdrop{background:rgba(0,0,0,.32)}.cdk-overlay-transparent-backdrop,.cdk-overlay-transparent-backdrop.cdk-overlay-backdrop-showing{opacity:0}.cdk-overlay-connected-position-bounding-box{position:absolute;z-index:1000;display:flex;flex-direction:column;min-width:1px;min-height:1px}.cdk-global-scrollblock{position:fixed;width:100%;overflow-y:scroll}@keyframes cdk-text-field-autofill-start{ + /*!*/}@keyframes cdk-text-field-autofill-end{ + /*!*/}.cdk-text-field-autofill-monitored:-webkit-autofill{animation:cdk-text-field-autofill-start 0s 1ms}.cdk-text-field-autofill-monitored:not(:-webkit-autofill){animation:cdk-text-field-autofill-end 0s 1ms}textarea.cdk-textarea-autosize{resize:none}textarea.cdk-textarea-autosize-measuring{padding:2px 0!important;box-sizing:initial!important;height:auto!important;overflow:hidden!important}textarea.cdk-textarea-autosize-measuring-firefox{padding:2px 0!important;box-sizing:initial!important;height:0!important}.mat-focus-indicator,.mat-mdc-focus-indicator{position:relative}.mat-ripple-element{background-color:rgba(0,0,0,.1)}.mat-option{color:rgba(0,0,0,.87)}.mat-option.mat-active,.mat-option.mat-selected:not(.mat-option-multiple):not(.mat-option-disabled),.mat-option:focus:not(.mat-option-disabled),.mat-option:hover:not(.mat-option-disabled){background:rgba(0,0,0,.04)}.mat-option.mat-active{color:rgba(0,0,0,.87)}.mat-option.mat-option-disabled{color:rgba(0,0,0,.38)}.mat-primary .mat-option.mat-selected:not(.mat-option-disabled){color:#3f51b5}.mat-accent .mat-option.mat-selected:not(.mat-option-disabled){color:#ff4081}.mat-warn .mat-option.mat-selected:not(.mat-option-disabled){color:#f44336}.mat-optgroup-label{color:rgba(0,0,0,.54)}.mat-optgroup-disabled .mat-optgroup-label{color:rgba(0,0,0,.38)}.mat-pseudo-checkbox{color:rgba(0,0,0,.54)}.mat-pseudo-checkbox:after{color:#fafafa}.mat-pseudo-checkbox-disabled{color:#b0b0b0}.mat-primary .mat-pseudo-checkbox-checked,.mat-primary .mat-pseudo-checkbox-indeterminate{background:#3f51b5}.mat-accent .mat-pseudo-checkbox-checked,.mat-accent .mat-pseudo-checkbox-indeterminate,.mat-pseudo-checkbox-checked,.mat-pseudo-checkbox-indeterminate{background:#ff4081}.mat-warn .mat-pseudo-checkbox-checked,.mat-warn .mat-pseudo-checkbox-indeterminate{background:#f44336}.mat-pseudo-checkbox-checked.mat-pseudo-checkbox-disabled,.mat-pseudo-checkbox-indeterminate.mat-pseudo-checkbox-disabled{background:#b0b0b0}.mat-app-background{background-color:#fafafa;color:rgba(0,0,0,.87)}.mat-elevation-z0{box-shadow:0 0 0 0 rgba(0,0,0,.2),0 0 0 0 rgba(0,0,0,.14),0 0 0 0 rgba(0,0,0,.12)}.mat-elevation-z1{box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12)}.mat-elevation-z2{box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12)}.mat-elevation-z3{box-shadow:0 3px 3px -2px rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12)}.mat-elevation-z4{box-shadow:0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12)}.mat-elevation-z5{box-shadow:0 3px 5px -1px rgba(0,0,0,.2),0 5px 8px 0 rgba(0,0,0,.14),0 1px 14px 0 rgba(0,0,0,.12)}.mat-elevation-z6{box-shadow:0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12)}.mat-elevation-z7{box-shadow:0 4px 5px -2px rgba(0,0,0,.2),0 7px 10px 1px rgba(0,0,0,.14),0 2px 16px 1px rgba(0,0,0,.12)}.mat-elevation-z8{box-shadow:0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12)}.mat-elevation-z9{box-shadow:0 5px 6px -3px rgba(0,0,0,.2),0 9px 12px 1px rgba(0,0,0,.14),0 3px 16px 2px rgba(0,0,0,.12)}.mat-elevation-z10{box-shadow:0 6px 6px -3px rgba(0,0,0,.2),0 10px 14px 1px rgba(0,0,0,.14),0 4px 18px 3px rgba(0,0,0,.12)}.mat-elevation-z11{box-shadow:0 6px 7px -4px rgba(0,0,0,.2),0 11px 15px 1px rgba(0,0,0,.14),0 4px 20px 3px rgba(0,0,0,.12)}.mat-elevation-z12{box-shadow:0 7px 8px -4px rgba(0,0,0,.2),0 12px 17px 2px rgba(0,0,0,.14),0 5px 22px 4px rgba(0,0,0,.12)}.mat-elevation-z13{box-shadow:0 7px 8px -4px rgba(0,0,0,.2),0 13px 19px 2px rgba(0,0,0,.14),0 5px 24px 4px rgba(0,0,0,.12)}.mat-elevation-z14{box-shadow:0 7px 9px -4px rgba(0,0,0,.2),0 14px 21px 2px rgba(0,0,0,.14),0 5px 26px 4px rgba(0,0,0,.12)}.mat-elevation-z15{box-shadow:0 8px 9px -5px rgba(0,0,0,.2),0 15px 22px 2px rgba(0,0,0,.14),0 6px 28px 5px rgba(0,0,0,.12)}.mat-elevation-z16{box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12)}.mat-elevation-z17{box-shadow:0 8px 11px -5px rgba(0,0,0,.2),0 17px 26px 2px rgba(0,0,0,.14),0 6px 32px 5px rgba(0,0,0,.12)}.mat-elevation-z18{box-shadow:0 9px 11px -5px rgba(0,0,0,.2),0 18px 28px 2px rgba(0,0,0,.14),0 7px 34px 6px rgba(0,0,0,.12)}.mat-elevation-z19{box-shadow:0 9px 12px -6px rgba(0,0,0,.2),0 19px 29px 2px rgba(0,0,0,.14),0 7px 36px 6px rgba(0,0,0,.12)}.mat-elevation-z20{box-shadow:0 10px 13px -6px rgba(0,0,0,.2),0 20px 31px 3px rgba(0,0,0,.14),0 8px 38px 7px rgba(0,0,0,.12)}.mat-elevation-z21{box-shadow:0 10px 13px -6px rgba(0,0,0,.2),0 21px 33px 3px rgba(0,0,0,.14),0 8px 40px 7px rgba(0,0,0,.12)}.mat-elevation-z22{box-shadow:0 10px 14px -6px rgba(0,0,0,.2),0 22px 35px 3px rgba(0,0,0,.14),0 8px 42px 7px rgba(0,0,0,.12)}.mat-elevation-z23{box-shadow:0 11px 14px -7px rgba(0,0,0,.2),0 23px 36px 3px rgba(0,0,0,.14),0 9px 44px 8px rgba(0,0,0,.12)}.mat-elevation-z24{box-shadow:0 11px 15px -7px rgba(0,0,0,.2),0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12)}.mat-theme-loaded-marker{display:none}.mat-autocomplete-panel{background:#fff;color:rgba(0,0,0,.87)}.mat-autocomplete-panel:not([class*=mat-elevation-z]){box-shadow:0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12)}.mat-autocomplete-panel .mat-option.mat-selected:not(.mat-active):not(:hover){background:#fff}.mat-autocomplete-panel .mat-option.mat-selected:not(.mat-active):not(:hover):not(.mat-option-disabled){color:rgba(0,0,0,.87)}.mat-badge-content{color:#fff;background:#3f51b5}.cdk-high-contrast-active .mat-badge-content{outline:1px solid;border-radius:0}.mat-badge-accent .mat-badge-content{background:#ff4081;color:#fff}.mat-badge-warn .mat-badge-content{color:#fff;background:#f44336}.mat-badge{position:relative}.mat-badge-hidden .mat-badge-content{display:none}.mat-badge-disabled .mat-badge-content{background:#b9b9b9;color:rgba(0,0,0,.38)}.mat-badge-content{position:absolute;text-align:center;display:inline-block;border-radius:50%;transition:transform .2s ease-in-out;transform:scale(.6);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;pointer-events:none}.mat-badge-content._mat-animation-noopable,.ng-animate-disabled .mat-badge-content{transition:none}.mat-badge-content.mat-badge-active{transform:none}.mat-badge-small .mat-badge-content{width:16px;height:16px;line-height:16px}.mat-badge-small.mat-badge-above .mat-badge-content{top:-8px}.mat-badge-small.mat-badge-below .mat-badge-content{bottom:-8px}.mat-badge-small.mat-badge-before .mat-badge-content{left:-16px}[dir=rtl] .mat-badge-small.mat-badge-before .mat-badge-content{left:auto;right:-16px}.mat-badge-small.mat-badge-after .mat-badge-content{right:-16px}[dir=rtl] .mat-badge-small.mat-badge-after .mat-badge-content{right:auto;left:-16px}.mat-badge-small.mat-badge-overlap.mat-badge-before .mat-badge-content{left:-8px}[dir=rtl] .mat-badge-small.mat-badge-overlap.mat-badge-before .mat-badge-content{left:auto;right:-8px}.mat-badge-small.mat-badge-overlap.mat-badge-after .mat-badge-content{right:-8px}[dir=rtl] .mat-badge-small.mat-badge-overlap.mat-badge-after .mat-badge-content{right:auto;left:-8px}.mat-badge-medium .mat-badge-content{width:22px;height:22px;line-height:22px}.mat-badge-medium.mat-badge-above .mat-badge-content{top:-11px}.mat-badge-medium.mat-badge-below .mat-badge-content{bottom:-11px}.mat-badge-medium.mat-badge-before .mat-badge-content{left:-22px}[dir=rtl] .mat-badge-medium.mat-badge-before .mat-badge-content{left:auto;right:-22px}.mat-badge-medium.mat-badge-after .mat-badge-content{right:-22px}[dir=rtl] .mat-badge-medium.mat-badge-after .mat-badge-content{right:auto;left:-22px}.mat-badge-medium.mat-badge-overlap.mat-badge-before .mat-badge-content{left:-11px}[dir=rtl] .mat-badge-medium.mat-badge-overlap.mat-badge-before .mat-badge-content{left:auto;right:-11px}.mat-badge-medium.mat-badge-overlap.mat-badge-after .mat-badge-content{right:-11px}[dir=rtl] .mat-badge-medium.mat-badge-overlap.mat-badge-after .mat-badge-content{right:auto;left:-11px}.mat-badge-large .mat-badge-content{width:28px;height:28px;line-height:28px}.mat-badge-large.mat-badge-above .mat-badge-content{top:-14px}.mat-badge-large.mat-badge-below .mat-badge-content{bottom:-14px}.mat-badge-large.mat-badge-before .mat-badge-content{left:-28px}[dir=rtl] .mat-badge-large.mat-badge-before .mat-badge-content{left:auto;right:-28px}.mat-badge-large.mat-badge-after .mat-badge-content{right:-28px}[dir=rtl] .mat-badge-large.mat-badge-after .mat-badge-content{right:auto;left:-28px}.mat-badge-large.mat-badge-overlap.mat-badge-before .mat-badge-content{left:-14px}[dir=rtl] .mat-badge-large.mat-badge-overlap.mat-badge-before .mat-badge-content{left:auto;right:-14px}.mat-badge-large.mat-badge-overlap.mat-badge-after .mat-badge-content{right:-14px}[dir=rtl] .mat-badge-large.mat-badge-overlap.mat-badge-after .mat-badge-content{right:auto;left:-14px}.mat-bottom-sheet-container{box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12);background:#fff;color:rgba(0,0,0,.87)}.mat-button,.mat-icon-button,.mat-stroked-button{color:inherit;background:transparent}.mat-button.mat-primary,.mat-icon-button.mat-primary,.mat-stroked-button.mat-primary{color:#3f51b5}.mat-button.mat-accent,.mat-icon-button.mat-accent,.mat-stroked-button.mat-accent{color:#ff4081}.mat-button.mat-warn,.mat-icon-button.mat-warn,.mat-stroked-button.mat-warn{color:#f44336}.mat-button.mat-accent.mat-button-disabled,.mat-button.mat-button-disabled.mat-button-disabled,.mat-button.mat-primary.mat-button-disabled,.mat-button.mat-warn.mat-button-disabled,.mat-icon-button.mat-accent.mat-button-disabled,.mat-icon-button.mat-button-disabled.mat-button-disabled,.mat-icon-button.mat-primary.mat-button-disabled,.mat-icon-button.mat-warn.mat-button-disabled,.mat-stroked-button.mat-accent.mat-button-disabled,.mat-stroked-button.mat-button-disabled.mat-button-disabled,.mat-stroked-button.mat-primary.mat-button-disabled,.mat-stroked-button.mat-warn.mat-button-disabled{color:rgba(0,0,0,.26)}.mat-button.mat-primary .mat-button-focus-overlay,.mat-icon-button.mat-primary .mat-button-focus-overlay,.mat-stroked-button.mat-primary .mat-button-focus-overlay{background-color:#3f51b5}.mat-button.mat-accent .mat-button-focus-overlay,.mat-icon-button.mat-accent .mat-button-focus-overlay,.mat-stroked-button.mat-accent .mat-button-focus-overlay{background-color:#ff4081}.mat-button.mat-warn .mat-button-focus-overlay,.mat-icon-button.mat-warn .mat-button-focus-overlay,.mat-stroked-button.mat-warn .mat-button-focus-overlay{background-color:#f44336}.mat-button.mat-button-disabled .mat-button-focus-overlay,.mat-icon-button.mat-button-disabled .mat-button-focus-overlay,.mat-stroked-button.mat-button-disabled .mat-button-focus-overlay{background-color:initial}.mat-button .mat-ripple-element,.mat-icon-button .mat-ripple-element,.mat-stroked-button .mat-ripple-element{opacity:.1;background-color:currentColor}.mat-button-focus-overlay{background:#000}.mat-stroked-button:not(.mat-button-disabled){border-color:rgba(0,0,0,.12)}.mat-fab,.mat-flat-button,.mat-mini-fab,.mat-raised-button{color:rgba(0,0,0,.87);background-color:#fff}.mat-fab.mat-accent,.mat-fab.mat-primary,.mat-fab.mat-warn,.mat-flat-button.mat-accent,.mat-flat-button.mat-primary,.mat-flat-button.mat-warn,.mat-mini-fab.mat-accent,.mat-mini-fab.mat-primary,.mat-mini-fab.mat-warn,.mat-raised-button.mat-accent,.mat-raised-button.mat-primary,.mat-raised-button.mat-warn{color:#fff}.mat-fab.mat-accent.mat-button-disabled,.mat-fab.mat-button-disabled.mat-button-disabled,.mat-fab.mat-primary.mat-button-disabled,.mat-fab.mat-warn.mat-button-disabled,.mat-flat-button.mat-accent.mat-button-disabled,.mat-flat-button.mat-button-disabled.mat-button-disabled,.mat-flat-button.mat-primary.mat-button-disabled,.mat-flat-button.mat-warn.mat-button-disabled,.mat-mini-fab.mat-accent.mat-button-disabled,.mat-mini-fab.mat-button-disabled.mat-button-disabled,.mat-mini-fab.mat-primary.mat-button-disabled,.mat-mini-fab.mat-warn.mat-button-disabled,.mat-raised-button.mat-accent.mat-button-disabled,.mat-raised-button.mat-button-disabled.mat-button-disabled,.mat-raised-button.mat-primary.mat-button-disabled,.mat-raised-button.mat-warn.mat-button-disabled{color:rgba(0,0,0,.26)}.mat-fab.mat-primary,.mat-flat-button.mat-primary,.mat-mini-fab.mat-primary,.mat-raised-button.mat-primary{background-color:#3f51b5}.mat-fab.mat-accent,.mat-flat-button.mat-accent,.mat-mini-fab.mat-accent,.mat-raised-button.mat-accent{background-color:#ff4081}.mat-fab.mat-warn,.mat-flat-button.mat-warn,.mat-mini-fab.mat-warn,.mat-raised-button.mat-warn{background-color:#f44336}.mat-fab.mat-accent.mat-button-disabled,.mat-fab.mat-button-disabled.mat-button-disabled,.mat-fab.mat-primary.mat-button-disabled,.mat-fab.mat-warn.mat-button-disabled,.mat-flat-button.mat-accent.mat-button-disabled,.mat-flat-button.mat-button-disabled.mat-button-disabled,.mat-flat-button.mat-primary.mat-button-disabled,.mat-flat-button.mat-warn.mat-button-disabled,.mat-mini-fab.mat-accent.mat-button-disabled,.mat-mini-fab.mat-button-disabled.mat-button-disabled,.mat-mini-fab.mat-primary.mat-button-disabled,.mat-mini-fab.mat-warn.mat-button-disabled,.mat-raised-button.mat-accent.mat-button-disabled,.mat-raised-button.mat-button-disabled.mat-button-disabled,.mat-raised-button.mat-primary.mat-button-disabled,.mat-raised-button.mat-warn.mat-button-disabled{background-color:rgba(0,0,0,.12)}.mat-fab.mat-accent .mat-ripple-element,.mat-fab.mat-primary .mat-ripple-element,.mat-fab.mat-warn .mat-ripple-element,.mat-flat-button.mat-accent .mat-ripple-element,.mat-flat-button.mat-primary .mat-ripple-element,.mat-flat-button.mat-warn .mat-ripple-element,.mat-mini-fab.mat-accent .mat-ripple-element,.mat-mini-fab.mat-primary .mat-ripple-element,.mat-mini-fab.mat-warn .mat-ripple-element,.mat-raised-button.mat-accent .mat-ripple-element,.mat-raised-button.mat-primary .mat-ripple-element,.mat-raised-button.mat-warn .mat-ripple-element{background-color:hsla(0,0%,100%,.1)}.mat-flat-button:not([class*=mat-elevation-z]),.mat-stroked-button:not([class*=mat-elevation-z]){box-shadow:0 0 0 0 rgba(0,0,0,.2),0 0 0 0 rgba(0,0,0,.14),0 0 0 0 rgba(0,0,0,.12)}.mat-raised-button:not([class*=mat-elevation-z]){box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12)}.mat-raised-button:not(.mat-button-disabled):active:not([class*=mat-elevation-z]){box-shadow:0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12)}.mat-raised-button.mat-button-disabled:not([class*=mat-elevation-z]){box-shadow:0 0 0 0 rgba(0,0,0,.2),0 0 0 0 rgba(0,0,0,.14),0 0 0 0 rgba(0,0,0,.12)}.mat-fab:not([class*=mat-elevation-z]),.mat-mini-fab:not([class*=mat-elevation-z]){box-shadow:0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12)}.mat-fab:not(.mat-button-disabled):active:not([class*=mat-elevation-z]),.mat-mini-fab:not(.mat-button-disabled):active:not([class*=mat-elevation-z]){box-shadow:0 7px 8px -4px rgba(0,0,0,.2),0 12px 17px 2px rgba(0,0,0,.14),0 5px 22px 4px rgba(0,0,0,.12)}.mat-fab.mat-button-disabled:not([class*=mat-elevation-z]),.mat-mini-fab.mat-button-disabled:not([class*=mat-elevation-z]){box-shadow:0 0 0 0 rgba(0,0,0,.2),0 0 0 0 rgba(0,0,0,.14),0 0 0 0 rgba(0,0,0,.12)}.mat-button-toggle-group,.mat-button-toggle-standalone{box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12)}.mat-button-toggle-group-appearance-standard,.mat-button-toggle-standalone.mat-button-toggle-appearance-standard{box-shadow:none}.mat-button-toggle{color:rgba(0,0,0,.38)}.mat-button-toggle .mat-button-toggle-focus-overlay{background-color:rgba(0,0,0,.12)}.mat-button-toggle-appearance-standard{color:rgba(0,0,0,.87);background:#fff}.mat-button-toggle-appearance-standard .mat-button-toggle-focus-overlay{background-color:#000}.mat-button-toggle-group-appearance-standard .mat-button-toggle+.mat-button-toggle{border-left:1px solid rgba(0,0,0,.12)}[dir=rtl] .mat-button-toggle-group-appearance-standard .mat-button-toggle+.mat-button-toggle{border-left:none;border-right:1px solid rgba(0,0,0,.12)}.mat-button-toggle-group-appearance-standard.mat-button-toggle-vertical .mat-button-toggle+.mat-button-toggle{border-left:none;border-right:none;border-top:1px solid rgba(0,0,0,.12)}.mat-button-toggle-checked{background-color:#e0e0e0;color:rgba(0,0,0,.54)}.mat-button-toggle-checked.mat-button-toggle-appearance-standard{color:rgba(0,0,0,.87)}.mat-button-toggle-disabled{color:rgba(0,0,0,.26);background-color:#eee}.mat-button-toggle-disabled.mat-button-toggle-appearance-standard{background:#fff}.mat-button-toggle-disabled.mat-button-toggle-checked{background-color:#bdbdbd}.mat-button-toggle-group-appearance-standard,.mat-button-toggle-standalone.mat-button-toggle-appearance-standard{border:1px solid rgba(0,0,0,.12)}.mat-button-toggle-appearance-standard .mat-button-toggle-label-content{line-height:48px}.mat-card{background:#fff;color:rgba(0,0,0,.87)}.mat-card:not([class*=mat-elevation-z]){box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12)}.mat-card.mat-card-flat:not([class*=mat-elevation-z]){box-shadow:0 0 0 0 rgba(0,0,0,.2),0 0 0 0 rgba(0,0,0,.14),0 0 0 0 rgba(0,0,0,.12)}.mat-card-subtitle{color:rgba(0,0,0,.54)}.mat-checkbox-frame{border-color:rgba(0,0,0,.54)}.mat-checkbox-checkmark{fill:#fafafa}.mat-checkbox-checkmark-path{stroke:#fafafa!important}.mat-checkbox-mixedmark{background-color:#fafafa}.mat-checkbox-checked.mat-primary .mat-checkbox-background,.mat-checkbox-indeterminate.mat-primary .mat-checkbox-background{background-color:#3f51b5}.mat-checkbox-checked.mat-accent .mat-checkbox-background,.mat-checkbox-indeterminate.mat-accent .mat-checkbox-background{background-color:#ff4081}.mat-checkbox-checked.mat-warn .mat-checkbox-background,.mat-checkbox-indeterminate.mat-warn .mat-checkbox-background{background-color:#f44336}.mat-checkbox-disabled.mat-checkbox-checked .mat-checkbox-background,.mat-checkbox-disabled.mat-checkbox-indeterminate .mat-checkbox-background{background-color:#b0b0b0}.mat-checkbox-disabled:not(.mat-checkbox-checked) .mat-checkbox-frame{border-color:#b0b0b0}.mat-checkbox-disabled .mat-checkbox-label{color:rgba(0,0,0,.54)}.mat-checkbox .mat-ripple-element{background-color:#000}.mat-checkbox-checked:not(.mat-checkbox-disabled).mat-primary .mat-ripple-element,.mat-checkbox:active:not(.mat-checkbox-disabled).mat-primary .mat-ripple-element{background:#3f51b5}.mat-checkbox-checked:not(.mat-checkbox-disabled).mat-accent .mat-ripple-element,.mat-checkbox:active:not(.mat-checkbox-disabled).mat-accent .mat-ripple-element{background:#ff4081}.mat-checkbox-checked:not(.mat-checkbox-disabled).mat-warn .mat-ripple-element,.mat-checkbox:active:not(.mat-checkbox-disabled).mat-warn .mat-ripple-element{background:#f44336}.mat-chip.mat-standard-chip{background-color:#e0e0e0;color:rgba(0,0,0,.87)}.mat-chip.mat-standard-chip .mat-chip-remove{color:rgba(0,0,0,.87);opacity:.4}.mat-chip.mat-standard-chip:not(.mat-chip-disabled):active{box-shadow:0 3px 3px -2px rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12)}.mat-chip.mat-standard-chip:not(.mat-chip-disabled) .mat-chip-remove:hover{opacity:.54}.mat-chip.mat-standard-chip.mat-chip-disabled{opacity:.4}.mat-chip.mat-standard-chip:after{background:#000}.mat-chip.mat-standard-chip.mat-chip-selected.mat-primary{background-color:#3f51b5;color:#fff}.mat-chip.mat-standard-chip.mat-chip-selected.mat-primary .mat-chip-remove{color:#fff;opacity:.4}.mat-chip.mat-standard-chip.mat-chip-selected.mat-primary .mat-ripple-element{background-color:hsla(0,0%,100%,.1)}.mat-chip.mat-standard-chip.mat-chip-selected.mat-warn{background-color:#f44336;color:#fff}.mat-chip.mat-standard-chip.mat-chip-selected.mat-warn .mat-chip-remove{color:#fff;opacity:.4}.mat-chip.mat-standard-chip.mat-chip-selected.mat-warn .mat-ripple-element{background-color:hsla(0,0%,100%,.1)}.mat-chip.mat-standard-chip.mat-chip-selected.mat-accent{background-color:#ff4081;color:#fff}.mat-chip.mat-standard-chip.mat-chip-selected.mat-accent .mat-chip-remove{color:#fff;opacity:.4}.mat-chip.mat-standard-chip.mat-chip-selected.mat-accent .mat-ripple-element{background-color:hsla(0,0%,100%,.1)}.mat-table{background:#fff}.mat-table-sticky,.mat-table tbody,.mat-table tfoot,.mat-table thead,[mat-footer-row],[mat-header-row],[mat-row],mat-footer-row,mat-header-row,mat-row{background:inherit}mat-footer-row,mat-header-row,mat-row,td.mat-cell,td.mat-footer-cell,th.mat-header-cell{border-bottom-color:rgba(0,0,0,.12)}.mat-header-cell{color:rgba(0,0,0,.54)}.mat-cell,.mat-footer-cell{color:rgba(0,0,0,.87)}.mat-calendar-arrow{border-top-color:rgba(0,0,0,.54)}.mat-datepicker-content .mat-calendar-next-button,.mat-datepicker-content .mat-calendar-previous-button,.mat-datepicker-toggle{color:rgba(0,0,0,.54)}.mat-calendar-table-header{color:rgba(0,0,0,.38)}.mat-calendar-table-header-divider:after{background:rgba(0,0,0,.12)}.mat-calendar-body-label{color:rgba(0,0,0,.54)}.mat-calendar-body-cell-content,.mat-date-range-input-separator{color:rgba(0,0,0,.87);border-color:transparent}.mat-calendar-body-disabled>.mat-calendar-body-cell-content:not(.mat-calendar-body-selected):not(.mat-calendar-body-comparison-identical),.mat-form-field-disabled .mat-date-range-input-separator{color:rgba(0,0,0,.38)}.cdk-keyboard-focused .mat-calendar-body-active>.mat-calendar-body-cell-content:not(.mat-calendar-body-selected):not(.mat-calendar-body-comparison-identical),.cdk-program-focused .mat-calendar-body-active>.mat-calendar-body-cell-content:not(.mat-calendar-body-selected):not(.mat-calendar-body-comparison-identical),.mat-calendar-body-cell:not(.mat-calendar-body-disabled):hover>.mat-calendar-body-cell-content:not(.mat-calendar-body-selected):not(.mat-calendar-body-comparison-identical){background-color:rgba(0,0,0,.04)}.mat-calendar-body-in-preview{color:rgba(0,0,0,.24)}.mat-calendar-body-today:not(.mat-calendar-body-selected):not(.mat-calendar-body-comparison-identical){border-color:rgba(0,0,0,.38)}.mat-calendar-body-disabled>.mat-calendar-body-today:not(.mat-calendar-body-selected):not(.mat-calendar-body-comparison-identical){border-color:rgba(0,0,0,.18)}.mat-calendar-body-in-range:before{background:rgba(63,81,181,.2)}.mat-calendar-body-comparison-identical,.mat-calendar-body-in-comparison-range:before{background:rgba(249,171,0,.2)}.mat-calendar-body-comparison-bridge-start:before,[dir=rtl] .mat-calendar-body-comparison-bridge-end:before{background:linear-gradient(90deg,rgba(63,81,181,.2) 50%,rgba(249,171,0,.2) 0)}.mat-calendar-body-comparison-bridge-end:before,[dir=rtl] .mat-calendar-body-comparison-bridge-start:before{background:linear-gradient(270deg,rgba(63,81,181,.2) 50%,rgba(249,171,0,.2) 0)}.mat-calendar-body-in-comparison-range.mat-calendar-body-in-range:after,.mat-calendar-body-in-range>.mat-calendar-body-comparison-identical{background:#a8dab5}.mat-calendar-body-comparison-identical.mat-calendar-body-selected,.mat-calendar-body-in-comparison-range>.mat-calendar-body-selected{background:#46a35e}.mat-calendar-body-selected{background-color:#3f51b5;color:#fff}.mat-calendar-body-disabled>.mat-calendar-body-selected{background-color:rgba(63,81,181,.4)}.mat-calendar-body-today.mat-calendar-body-selected{box-shadow:inset 0 0 0 1px #fff}.mat-datepicker-content{box-shadow:0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12);background-color:#fff;color:rgba(0,0,0,.87)}.mat-datepicker-content.mat-accent .mat-calendar-body-in-range:before{background:rgba(255,64,129,.2)}.mat-datepicker-content.mat-accent .mat-calendar-body-comparison-identical,.mat-datepicker-content.mat-accent .mat-calendar-body-in-comparison-range:before{background:rgba(249,171,0,.2)}.mat-datepicker-content.mat-accent .mat-calendar-body-comparison-bridge-start:before,.mat-datepicker-content.mat-accent [dir=rtl] .mat-calendar-body-comparison-bridge-end:before{background:linear-gradient(90deg,rgba(255,64,129,.2) 50%,rgba(249,171,0,.2) 0)}.mat-datepicker-content.mat-accent .mat-calendar-body-comparison-bridge-end:before,.mat-datepicker-content.mat-accent [dir=rtl] .mat-calendar-body-comparison-bridge-start:before{background:linear-gradient(270deg,rgba(255,64,129,.2) 50%,rgba(249,171,0,.2) 0)}.mat-datepicker-content.mat-accent .mat-calendar-body-in-comparison-range.mat-calendar-body-in-range:after,.mat-datepicker-content.mat-accent .mat-calendar-body-in-range>.mat-calendar-body-comparison-identical{background:#a8dab5}.mat-datepicker-content.mat-accent .mat-calendar-body-comparison-identical.mat-calendar-body-selected,.mat-datepicker-content.mat-accent .mat-calendar-body-in-comparison-range>.mat-calendar-body-selected{background:#46a35e}.mat-datepicker-content.mat-accent .mat-calendar-body-selected{background-color:#ff4081;color:#fff}.mat-datepicker-content.mat-accent .mat-calendar-body-disabled>.mat-calendar-body-selected{background-color:rgba(255,64,129,.4)}.mat-datepicker-content.mat-accent .mat-calendar-body-today.mat-calendar-body-selected{box-shadow:inset 0 0 0 1px #fff}.mat-datepicker-content.mat-warn .mat-calendar-body-in-range:before{background:rgba(244,67,54,.2)}.mat-datepicker-content.mat-warn .mat-calendar-body-comparison-identical,.mat-datepicker-content.mat-warn .mat-calendar-body-in-comparison-range:before{background:rgba(249,171,0,.2)}.mat-datepicker-content.mat-warn .mat-calendar-body-comparison-bridge-start:before,.mat-datepicker-content.mat-warn [dir=rtl] .mat-calendar-body-comparison-bridge-end:before{background:linear-gradient(90deg,rgba(244,67,54,.2) 50%,rgba(249,171,0,.2) 0)}.mat-datepicker-content.mat-warn .mat-calendar-body-comparison-bridge-end:before,.mat-datepicker-content.mat-warn [dir=rtl] .mat-calendar-body-comparison-bridge-start:before{background:linear-gradient(270deg,rgba(244,67,54,.2) 50%,rgba(249,171,0,.2) 0)}.mat-datepicker-content.mat-warn .mat-calendar-body-in-comparison-range.mat-calendar-body-in-range:after,.mat-datepicker-content.mat-warn .mat-calendar-body-in-range>.mat-calendar-body-comparison-identical{background:#a8dab5}.mat-datepicker-content.mat-warn .mat-calendar-body-comparison-identical.mat-calendar-body-selected,.mat-datepicker-content.mat-warn .mat-calendar-body-in-comparison-range>.mat-calendar-body-selected{background:#46a35e}.mat-datepicker-content.mat-warn .mat-calendar-body-selected{background-color:#f44336;color:#fff}.mat-datepicker-content.mat-warn .mat-calendar-body-disabled>.mat-calendar-body-selected{background-color:rgba(244,67,54,.4)}.mat-datepicker-content.mat-warn .mat-calendar-body-today.mat-calendar-body-selected{box-shadow:inset 0 0 0 1px #fff}.mat-datepicker-content-touch{box-shadow:0 0 0 0 rgba(0,0,0,.2),0 0 0 0 rgba(0,0,0,.14),0 0 0 0 rgba(0,0,0,.12)}.mat-datepicker-toggle-active{color:#3f51b5}.mat-datepicker-toggle-active.mat-accent{color:#ff4081}.mat-datepicker-toggle-active.mat-warn{color:#f44336}.mat-date-range-input-inner[disabled]{color:rgba(0,0,0,.38)}.mat-dialog-container{box-shadow:0 11px 15px -7px rgba(0,0,0,.2),0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12);background:#fff;color:rgba(0,0,0,.87)}.mat-divider{border-top-color:rgba(0,0,0,.12)}.mat-divider-vertical{border-right-color:rgba(0,0,0,.12)}.mat-expansion-panel{background:#fff;color:rgba(0,0,0,.87)}.mat-expansion-panel:not([class*=mat-elevation-z]){box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12)}.mat-action-row{border-top-color:rgba(0,0,0,.12)}.mat-expansion-panel .mat-expansion-panel-header.cdk-keyboard-focused:not([aria-disabled=true]),.mat-expansion-panel .mat-expansion-panel-header.cdk-program-focused:not([aria-disabled=true]),.mat-expansion-panel:not(.mat-expanded) .mat-expansion-panel-header:hover:not([aria-disabled=true]){background:rgba(0,0,0,.04)}@media (hover:none){.mat-expansion-panel:not(.mat-expanded):not([aria-disabled=true]) .mat-expansion-panel-header:hover{background:#fff}}.mat-expansion-panel-header-title{color:rgba(0,0,0,.87)}.mat-expansion-indicator:after,.mat-expansion-panel-header-description{color:rgba(0,0,0,.54)}.mat-expansion-panel-header[aria-disabled=true]{color:rgba(0,0,0,.26)}.mat-expansion-panel-header[aria-disabled=true] .mat-expansion-panel-header-description,.mat-expansion-panel-header[aria-disabled=true] .mat-expansion-panel-header-title{color:inherit}.mat-expansion-panel-header{height:48px}.mat-expansion-panel-header.mat-expanded{height:64px}.mat-form-field-label,.mat-hint{color:rgba(0,0,0,.6)}.mat-form-field.mat-focused .mat-form-field-label{color:#3f51b5}.mat-form-field.mat-focused .mat-form-field-label.mat-accent{color:#ff4081}.mat-form-field.mat-focused .mat-form-field-label.mat-warn{color:#f44336}.mat-focused .mat-form-field-required-marker{color:#ff4081}.mat-form-field-ripple{background-color:rgba(0,0,0,.87)}.mat-form-field.mat-focused .mat-form-field-ripple{background-color:#3f51b5}.mat-form-field.mat-focused .mat-form-field-ripple.mat-accent{background-color:#ff4081}.mat-form-field.mat-focused .mat-form-field-ripple.mat-warn{background-color:#f44336}.mat-form-field-type-mat-native-select.mat-focused:not(.mat-form-field-invalid) .mat-form-field-infix:after{color:#3f51b5}.mat-form-field-type-mat-native-select.mat-focused:not(.mat-form-field-invalid).mat-accent .mat-form-field-infix:after{color:#ff4081}.mat-form-field-type-mat-native-select.mat-focused:not(.mat-form-field-invalid).mat-warn .mat-form-field-infix:after,.mat-form-field.mat-form-field-invalid .mat-form-field-label,.mat-form-field.mat-form-field-invalid .mat-form-field-label.mat-accent,.mat-form-field.mat-form-field-invalid .mat-form-field-label .mat-form-field-required-marker{color:#f44336}.mat-form-field.mat-form-field-invalid .mat-form-field-ripple,.mat-form-field.mat-form-field-invalid .mat-form-field-ripple.mat-accent{background-color:#f44336}.mat-error{color:#f44336}.mat-form-field-appearance-legacy .mat-form-field-label,.mat-form-field-appearance-legacy .mat-hint{color:rgba(0,0,0,.54)}.mat-form-field-appearance-legacy .mat-form-field-underline{background-color:rgba(0,0,0,.42)}.mat-form-field-appearance-legacy.mat-form-field-disabled .mat-form-field-underline{background-image:linear-gradient(90deg,rgba(0,0,0,.42) 0,rgba(0,0,0,.42) 33%,transparent 0);background-size:4px 100%;background-repeat:repeat-x}.mat-form-field-appearance-standard .mat-form-field-underline{background-color:rgba(0,0,0,.42)}.mat-form-field-appearance-standard.mat-form-field-disabled .mat-form-field-underline{background-image:linear-gradient(90deg,rgba(0,0,0,.42) 0,rgba(0,0,0,.42) 33%,transparent 0);background-size:4px 100%;background-repeat:repeat-x}.mat-form-field-appearance-fill .mat-form-field-flex{background-color:rgba(0,0,0,.04)}.mat-form-field-appearance-fill.mat-form-field-disabled .mat-form-field-flex{background-color:rgba(0,0,0,.02)}.mat-form-field-appearance-fill .mat-form-field-underline:before{background-color:rgba(0,0,0,.42)}.mat-form-field-appearance-fill.mat-form-field-disabled .mat-form-field-label{color:rgba(0,0,0,.38)}.mat-form-field-appearance-fill.mat-form-field-disabled .mat-form-field-underline:before{background-color:initial}.mat-form-field-appearance-outline .mat-form-field-outline{color:rgba(0,0,0,.12)}.mat-form-field-appearance-outline .mat-form-field-outline-thick{color:rgba(0,0,0,.87)}.mat-form-field-appearance-outline.mat-focused .mat-form-field-outline-thick{color:#3f51b5}.mat-form-field-appearance-outline.mat-focused.mat-accent .mat-form-field-outline-thick{color:#ff4081}.mat-form-field-appearance-outline.mat-focused.mat-warn .mat-form-field-outline-thick,.mat-form-field-appearance-outline.mat-form-field-invalid.mat-form-field-invalid .mat-form-field-outline-thick{color:#f44336}.mat-form-field-appearance-outline.mat-form-field-disabled .mat-form-field-label{color:rgba(0,0,0,.38)}.mat-form-field-appearance-outline.mat-form-field-disabled .mat-form-field-outline{color:rgba(0,0,0,.06)}.mat-icon.mat-primary{color:#3f51b5}.mat-icon.mat-accent{color:#ff4081}.mat-icon.mat-warn{color:#f44336}.mat-form-field-type-mat-native-select .mat-form-field-infix:after{color:rgba(0,0,0,.54)}.mat-form-field-type-mat-native-select.mat-form-field-disabled .mat-form-field-infix:after,.mat-input-element:disabled{color:rgba(0,0,0,.38)}.mat-input-element{caret-color:#3f51b5}.mat-input-element::placeholder{color:rgba(0,0,0,.42)}.mat-input-element::-moz-placeholder{color:rgba(0,0,0,.42)}.mat-input-element::-webkit-input-placeholder{color:rgba(0,0,0,.42)}.mat-input-element:-ms-input-placeholder{color:rgba(0,0,0,.42)}.mat-form-field.mat-accent .mat-input-element{caret-color:#ff4081}.mat-form-field-invalid .mat-input-element,.mat-form-field.mat-warn .mat-input-element{caret-color:#f44336}.mat-form-field-type-mat-native-select.mat-form-field-invalid .mat-form-field-infix:after{color:#f44336}.mat-list-base .mat-list-item,.mat-list-base .mat-list-option{color:rgba(0,0,0,.87)}.mat-list-base .mat-subheader{color:rgba(0,0,0,.54)}.mat-list-item-disabled{background-color:#eee}.mat-action-list .mat-list-item:focus,.mat-action-list .mat-list-item:hover,.mat-list-option:focus,.mat-list-option:hover,.mat-nav-list .mat-list-item:focus,.mat-nav-list .mat-list-item:hover{background:rgba(0,0,0,.04)}.mat-list-single-selected-option,.mat-list-single-selected-option:focus,.mat-list-single-selected-option:hover{background:rgba(0,0,0,.12)}.mat-menu-panel{background:#fff}.mat-menu-panel:not([class*=mat-elevation-z]){box-shadow:0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12)}.mat-menu-item{background:transparent;color:rgba(0,0,0,.87)}.mat-menu-item[disabled],.mat-menu-item[disabled]:after{color:rgba(0,0,0,.38)}.mat-menu-item-submenu-trigger:after,.mat-menu-item .mat-icon-no-color{color:rgba(0,0,0,.54)}.mat-menu-item-highlighted:not([disabled]),.mat-menu-item.cdk-keyboard-focused:not([disabled]),.mat-menu-item.cdk-program-focused:not([disabled]),.mat-menu-item:hover:not([disabled]){background:rgba(0,0,0,.04)}.mat-paginator{background:#fff}.mat-paginator,.mat-paginator-page-size .mat-select-trigger{color:rgba(0,0,0,.54)}.mat-paginator-decrement,.mat-paginator-increment{border-top:2px solid rgba(0,0,0,.54);border-right:2px solid rgba(0,0,0,.54)}.mat-paginator-first,.mat-paginator-last{border-top:2px solid rgba(0,0,0,.54)}.mat-icon-button[disabled] .mat-paginator-decrement,.mat-icon-button[disabled] .mat-paginator-first,.mat-icon-button[disabled] .mat-paginator-increment,.mat-icon-button[disabled] .mat-paginator-last{border-color:rgba(0,0,0,.38)}.mat-paginator-container{min-height:56px}.mat-progress-bar-background{fill:#c5cae9}.mat-progress-bar-buffer{background-color:#c5cae9}.mat-progress-bar-fill:after{background-color:#3f51b5}.mat-progress-bar.mat-accent .mat-progress-bar-background{fill:#ff80ab}.mat-progress-bar.mat-accent .mat-progress-bar-buffer{background-color:#ff80ab}.mat-progress-bar.mat-accent .mat-progress-bar-fill:after{background-color:#ff4081}.mat-progress-bar.mat-warn .mat-progress-bar-background{fill:#ffcdd2}.mat-progress-bar.mat-warn .mat-progress-bar-buffer{background-color:#ffcdd2}.mat-progress-bar.mat-warn .mat-progress-bar-fill:after{background-color:#f44336}.mat-progress-spinner circle,.mat-spinner circle{stroke:#3f51b5}.mat-progress-spinner.mat-accent circle,.mat-spinner.mat-accent circle{stroke:#ff4081}.mat-progress-spinner.mat-warn circle,.mat-spinner.mat-warn circle{stroke:#f44336}.mat-radio-outer-circle{border-color:rgba(0,0,0,.54)}.mat-radio-button.mat-primary.mat-radio-checked .mat-radio-outer-circle{border-color:#3f51b5}.mat-radio-button.mat-primary.mat-radio-checked .mat-radio-persistent-ripple,.mat-radio-button.mat-primary .mat-radio-inner-circle,.mat-radio-button.mat-primary .mat-radio-ripple .mat-ripple-element:not(.mat-radio-persistent-ripple),.mat-radio-button.mat-primary:active .mat-radio-persistent-ripple{background-color:#3f51b5}.mat-radio-button.mat-accent.mat-radio-checked .mat-radio-outer-circle{border-color:#ff4081}.mat-radio-button.mat-accent.mat-radio-checked .mat-radio-persistent-ripple,.mat-radio-button.mat-accent .mat-radio-inner-circle,.mat-radio-button.mat-accent .mat-radio-ripple .mat-ripple-element:not(.mat-radio-persistent-ripple),.mat-radio-button.mat-accent:active .mat-radio-persistent-ripple{background-color:#ff4081}.mat-radio-button.mat-warn.mat-radio-checked .mat-radio-outer-circle{border-color:#f44336}.mat-radio-button.mat-warn.mat-radio-checked .mat-radio-persistent-ripple,.mat-radio-button.mat-warn .mat-radio-inner-circle,.mat-radio-button.mat-warn .mat-radio-ripple .mat-ripple-element:not(.mat-radio-persistent-ripple),.mat-radio-button.mat-warn:active .mat-radio-persistent-ripple{background-color:#f44336}.mat-radio-button.mat-radio-disabled.mat-radio-checked .mat-radio-outer-circle,.mat-radio-button.mat-radio-disabled .mat-radio-outer-circle{border-color:rgba(0,0,0,.38)}.mat-radio-button.mat-radio-disabled .mat-radio-inner-circle,.mat-radio-button.mat-radio-disabled .mat-radio-ripple .mat-ripple-element{background-color:rgba(0,0,0,.38)}.mat-radio-button.mat-radio-disabled .mat-radio-label-content{color:rgba(0,0,0,.38)}.mat-radio-button .mat-ripple-element{background-color:#000}.mat-select-value{color:rgba(0,0,0,.87)}.mat-select-placeholder{color:rgba(0,0,0,.42)}.mat-select-disabled .mat-select-value{color:rgba(0,0,0,.38)}.mat-select-arrow{color:rgba(0,0,0,.54)}.mat-select-panel{background:#fff}.mat-select-panel:not([class*=mat-elevation-z]){box-shadow:0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12)}.mat-select-panel .mat-option.mat-selected:not(.mat-option-multiple){background:rgba(0,0,0,.12)}.mat-form-field.mat-focused.mat-primary .mat-select-arrow{color:#3f51b5}.mat-form-field.mat-focused.mat-accent .mat-select-arrow{color:#ff4081}.mat-form-field.mat-focused.mat-warn .mat-select-arrow,.mat-form-field .mat-select.mat-select-invalid .mat-select-arrow{color:#f44336}.mat-form-field .mat-select.mat-select-disabled .mat-select-arrow{color:rgba(0,0,0,.38)}.mat-drawer-container{background-color:#fafafa;color:rgba(0,0,0,.87)}.mat-drawer{color:rgba(0,0,0,.87)}.mat-drawer,.mat-drawer.mat-drawer-push{background-color:#fff}.mat-drawer:not(.mat-drawer-side){box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12)}.mat-drawer-side{border-right:1px solid rgba(0,0,0,.12)}.mat-drawer-side.mat-drawer-end,[dir=rtl] .mat-drawer-side{border-left:1px solid rgba(0,0,0,.12);border-right:none}[dir=rtl] .mat-drawer-side.mat-drawer-end{border-left:none;border-right:1px solid rgba(0,0,0,.12)}.mat-drawer-backdrop.mat-drawer-shown{background-color:rgba(0,0,0,.6)}.mat-slide-toggle.mat-checked .mat-slide-toggle-thumb{background-color:#ff4081}.mat-slide-toggle.mat-checked .mat-slide-toggle-bar{background-color:rgba(255,64,129,.54)}.mat-slide-toggle.mat-checked .mat-ripple-element{background-color:#ff4081}.mat-slide-toggle.mat-primary.mat-checked .mat-slide-toggle-thumb{background-color:#3f51b5}.mat-slide-toggle.mat-primary.mat-checked .mat-slide-toggle-bar{background-color:rgba(63,81,181,.54)}.mat-slide-toggle.mat-primary.mat-checked .mat-ripple-element{background-color:#3f51b5}.mat-slide-toggle.mat-warn.mat-checked .mat-slide-toggle-thumb{background-color:#f44336}.mat-slide-toggle.mat-warn.mat-checked .mat-slide-toggle-bar{background-color:rgba(244,67,54,.54)}.mat-slide-toggle.mat-warn.mat-checked .mat-ripple-element{background-color:#f44336}.mat-slide-toggle:not(.mat-checked) .mat-ripple-element{background-color:#000}.mat-slide-toggle-thumb{box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);background-color:#fafafa}.mat-slide-toggle-bar{background-color:rgba(0,0,0,.38)}.mat-slider-track-background{background-color:rgba(0,0,0,.26)}.mat-primary .mat-slider-thumb,.mat-primary .mat-slider-thumb-label,.mat-primary .mat-slider-track-fill{background-color:#3f51b5}.mat-primary .mat-slider-thumb-label-text{color:#fff}.mat-primary .mat-slider-focus-ring{background-color:rgba(63,81,181,.2)}.mat-accent .mat-slider-thumb,.mat-accent .mat-slider-thumb-label,.mat-accent .mat-slider-track-fill{background-color:#ff4081}.mat-accent .mat-slider-thumb-label-text{color:#fff}.mat-accent .mat-slider-focus-ring{background-color:rgba(255,64,129,.2)}.mat-warn .mat-slider-thumb,.mat-warn .mat-slider-thumb-label,.mat-warn .mat-slider-track-fill{background-color:#f44336}.mat-warn .mat-slider-thumb-label-text{color:#fff}.mat-warn .mat-slider-focus-ring{background-color:rgba(244,67,54,.2)}.cdk-focused .mat-slider-track-background,.mat-slider:hover .mat-slider-track-background{background-color:rgba(0,0,0,.38)}.mat-slider-disabled .mat-slider-thumb,.mat-slider-disabled .mat-slider-track-background,.mat-slider-disabled .mat-slider-track-fill,.mat-slider-disabled:hover .mat-slider-track-background{background-color:rgba(0,0,0,.26)}.mat-slider-min-value .mat-slider-focus-ring{background-color:rgba(0,0,0,.12)}.mat-slider-min-value.mat-slider-thumb-label-showing .mat-slider-thumb,.mat-slider-min-value.mat-slider-thumb-label-showing .mat-slider-thumb-label{background-color:rgba(0,0,0,.87)}.mat-slider-min-value.mat-slider-thumb-label-showing.cdk-focused .mat-slider-thumb,.mat-slider-min-value.mat-slider-thumb-label-showing.cdk-focused .mat-slider-thumb-label{background-color:rgba(0,0,0,.26)}.mat-slider-min-value:not(.mat-slider-thumb-label-showing) .mat-slider-thumb{border-color:rgba(0,0,0,.26);background-color:initial}.mat-slider-min-value:not(.mat-slider-thumb-label-showing).cdk-focused .mat-slider-thumb,.mat-slider-min-value:not(.mat-slider-thumb-label-showing):hover .mat-slider-thumb{border-color:rgba(0,0,0,.38)}.mat-slider-min-value:not(.mat-slider-thumb-label-showing).cdk-focused.mat-slider-disabled .mat-slider-thumb,.mat-slider-min-value:not(.mat-slider-thumb-label-showing):hover.mat-slider-disabled .mat-slider-thumb{border-color:rgba(0,0,0,.26)}.mat-slider-has-ticks .mat-slider-wrapper:after{border-color:rgba(0,0,0,.7)}.mat-slider-horizontal .mat-slider-ticks{background-image:repeating-linear-gradient(90deg,rgba(0,0,0,.7),rgba(0,0,0,.7) 2px,transparent 0,transparent);background-image:-moz-repeating-linear-gradient(.0001deg,rgba(0,0,0,.7),rgba(0,0,0,.7) 2px,transparent 0,transparent)}.mat-slider-vertical .mat-slider-ticks{background-image:repeating-linear-gradient(180deg,rgba(0,0,0,.7),rgba(0,0,0,.7) 2px,transparent 0,transparent)}.mat-step-header.cdk-keyboard-focused,.mat-step-header.cdk-program-focused,.mat-step-header:hover{background-color:rgba(0,0,0,.04)}@media (hover:none){.mat-step-header:hover{background:none}}.mat-step-header .mat-step-label,.mat-step-header .mat-step-optional{color:rgba(0,0,0,.54)}.mat-step-header .mat-step-icon{background-color:rgba(0,0,0,.54);color:#fff}.mat-step-header .mat-step-icon-selected,.mat-step-header .mat-step-icon-state-done,.mat-step-header .mat-step-icon-state-edit{background-color:#3f51b5;color:#fff}.mat-step-header .mat-step-icon-state-error{background-color:initial;color:#f44336}.mat-step-header .mat-step-label.mat-step-label-active{color:rgba(0,0,0,.87)}.mat-step-header .mat-step-label.mat-step-label-error{color:#f44336}.mat-stepper-horizontal,.mat-stepper-vertical{background-color:#fff}.mat-stepper-vertical-line:before{border-left-color:rgba(0,0,0,.12)}.mat-horizontal-stepper-header:after,.mat-horizontal-stepper-header:before,.mat-stepper-horizontal-line{border-top-color:rgba(0,0,0,.12)}.mat-horizontal-stepper-header{height:72px}.mat-stepper-label-position-bottom .mat-horizontal-stepper-header,.mat-vertical-stepper-header{padding:24px}.mat-stepper-vertical-line:before{top:-16px;bottom:-16px}.mat-stepper-label-position-bottom .mat-horizontal-stepper-header:after,.mat-stepper-label-position-bottom .mat-horizontal-stepper-header:before,.mat-stepper-label-position-bottom .mat-stepper-horizontal-line{top:36px}.mat-sort-header-arrow{color:#757575}.mat-tab-header,.mat-tab-nav-bar{border-bottom:1px solid rgba(0,0,0,.12)}.mat-tab-group-inverted-header .mat-tab-header,.mat-tab-group-inverted-header .mat-tab-nav-bar{border-top:1px solid rgba(0,0,0,.12);border-bottom:none}.mat-tab-label,.mat-tab-link{color:rgba(0,0,0,.87)}.mat-tab-label.mat-tab-disabled,.mat-tab-link.mat-tab-disabled{color:rgba(0,0,0,.38)}.mat-tab-header-pagination-chevron{border-color:rgba(0,0,0,.87)}.mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron{border-color:rgba(0,0,0,.38)}.mat-tab-group[class*=mat-background-] .mat-tab-header,.mat-tab-nav-bar[class*=mat-background-]{border-bottom:none;border-top:none}.mat-tab-group.mat-primary .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-primary .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-group.mat-primary .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-primary .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-primary .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-primary .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-primary .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-primary .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled){background-color:rgba(197,202,233,.3)}.mat-tab-group.mat-primary .mat-ink-bar,.mat-tab-nav-bar.mat-primary .mat-ink-bar{background-color:#3f51b5}.mat-tab-group.mat-primary.mat-background-primary .mat-ink-bar,.mat-tab-nav-bar.mat-primary.mat-background-primary .mat-ink-bar{background-color:#fff}.mat-tab-group.mat-accent .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-accent .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-group.mat-accent .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-accent .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-accent .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-accent .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-accent .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-accent .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled){background-color:rgba(255,128,171,.3)}.mat-tab-group.mat-accent .mat-ink-bar,.mat-tab-nav-bar.mat-accent .mat-ink-bar{background-color:#ff4081}.mat-tab-group.mat-accent.mat-background-accent .mat-ink-bar,.mat-tab-nav-bar.mat-accent.mat-background-accent .mat-ink-bar{background-color:#fff}.mat-tab-group.mat-warn .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-warn .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-group.mat-warn .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-warn .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-warn .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-warn .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-warn .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-warn .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled){background-color:rgba(255,205,210,.3)}.mat-tab-group.mat-warn .mat-ink-bar,.mat-tab-nav-bar.mat-warn .mat-ink-bar{background-color:#f44336}.mat-tab-group.mat-warn.mat-background-warn .mat-ink-bar,.mat-tab-nav-bar.mat-warn.mat-background-warn .mat-ink-bar{background-color:#fff}.mat-tab-group.mat-background-primary .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-background-primary .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-group.mat-background-primary .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-background-primary .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-primary .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-primary .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-primary .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-primary .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled){background-color:rgba(197,202,233,.3)}.mat-tab-group.mat-background-primary .mat-tab-header,.mat-tab-group.mat-background-primary .mat-tab-header-pagination,.mat-tab-group.mat-background-primary .mat-tab-links,.mat-tab-nav-bar.mat-background-primary .mat-tab-header,.mat-tab-nav-bar.mat-background-primary .mat-tab-header-pagination,.mat-tab-nav-bar.mat-background-primary .mat-tab-links{background-color:#3f51b5}.mat-tab-group.mat-background-primary .mat-tab-label,.mat-tab-group.mat-background-primary .mat-tab-link,.mat-tab-nav-bar.mat-background-primary .mat-tab-label,.mat-tab-nav-bar.mat-background-primary .mat-tab-link{color:#fff}.mat-tab-group.mat-background-primary .mat-tab-label.mat-tab-disabled,.mat-tab-group.mat-background-primary .mat-tab-link.mat-tab-disabled,.mat-tab-nav-bar.mat-background-primary .mat-tab-label.mat-tab-disabled,.mat-tab-nav-bar.mat-background-primary .mat-tab-link.mat-tab-disabled{color:hsla(0,0%,100%,.4)}.mat-tab-group.mat-background-primary .mat-tab-header-pagination-chevron,.mat-tab-nav-bar.mat-background-primary .mat-tab-header-pagination-chevron{border-color:#fff}.mat-tab-group.mat-background-primary .mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron,.mat-tab-nav-bar.mat-background-primary .mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron{border-color:hsla(0,0%,100%,.4)}.mat-tab-group.mat-background-primary .mat-ripple-element,.mat-tab-nav-bar.mat-background-primary .mat-ripple-element{background-color:hsla(0,0%,100%,.12)}.mat-tab-group.mat-background-accent .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-background-accent .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-group.mat-background-accent .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-background-accent .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-accent .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-accent .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-accent .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-accent .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled){background-color:rgba(255,128,171,.3)}.mat-tab-group.mat-background-accent .mat-tab-header,.mat-tab-group.mat-background-accent .mat-tab-header-pagination,.mat-tab-group.mat-background-accent .mat-tab-links,.mat-tab-nav-bar.mat-background-accent .mat-tab-header,.mat-tab-nav-bar.mat-background-accent .mat-tab-header-pagination,.mat-tab-nav-bar.mat-background-accent .mat-tab-links{background-color:#ff4081}.mat-tab-group.mat-background-accent .mat-tab-label,.mat-tab-group.mat-background-accent .mat-tab-link,.mat-tab-nav-bar.mat-background-accent .mat-tab-label,.mat-tab-nav-bar.mat-background-accent .mat-tab-link{color:#fff}.mat-tab-group.mat-background-accent .mat-tab-label.mat-tab-disabled,.mat-tab-group.mat-background-accent .mat-tab-link.mat-tab-disabled,.mat-tab-nav-bar.mat-background-accent .mat-tab-label.mat-tab-disabled,.mat-tab-nav-bar.mat-background-accent .mat-tab-link.mat-tab-disabled{color:hsla(0,0%,100%,.4)}.mat-tab-group.mat-background-accent .mat-tab-header-pagination-chevron,.mat-tab-nav-bar.mat-background-accent .mat-tab-header-pagination-chevron{border-color:#fff}.mat-tab-group.mat-background-accent .mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron,.mat-tab-nav-bar.mat-background-accent .mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron{border-color:hsla(0,0%,100%,.4)}.mat-tab-group.mat-background-accent .mat-ripple-element,.mat-tab-nav-bar.mat-background-accent .mat-ripple-element{background-color:hsla(0,0%,100%,.12)}.mat-tab-group.mat-background-warn .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-background-warn .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-group.mat-background-warn .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-group.mat-background-warn .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-warn .mat-tab-label.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-warn .mat-tab-label.cdk-program-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-warn .mat-tab-link.cdk-keyboard-focused:not(.mat-tab-disabled),.mat-tab-nav-bar.mat-background-warn .mat-tab-link.cdk-program-focused:not(.mat-tab-disabled){background-color:rgba(255,205,210,.3)}.mat-tab-group.mat-background-warn .mat-tab-header,.mat-tab-group.mat-background-warn .mat-tab-header-pagination,.mat-tab-group.mat-background-warn .mat-tab-links,.mat-tab-nav-bar.mat-background-warn .mat-tab-header,.mat-tab-nav-bar.mat-background-warn .mat-tab-header-pagination,.mat-tab-nav-bar.mat-background-warn .mat-tab-links{background-color:#f44336}.mat-tab-group.mat-background-warn .mat-tab-label,.mat-tab-group.mat-background-warn .mat-tab-link,.mat-tab-nav-bar.mat-background-warn .mat-tab-label,.mat-tab-nav-bar.mat-background-warn .mat-tab-link{color:#fff}.mat-tab-group.mat-background-warn .mat-tab-label.mat-tab-disabled,.mat-tab-group.mat-background-warn .mat-tab-link.mat-tab-disabled,.mat-tab-nav-bar.mat-background-warn .mat-tab-label.mat-tab-disabled,.mat-tab-nav-bar.mat-background-warn .mat-tab-link.mat-tab-disabled{color:hsla(0,0%,100%,.4)}.mat-tab-group.mat-background-warn .mat-tab-header-pagination-chevron,.mat-tab-nav-bar.mat-background-warn .mat-tab-header-pagination-chevron{border-color:#fff}.mat-tab-group.mat-background-warn .mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron,.mat-tab-nav-bar.mat-background-warn .mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron{border-color:hsla(0,0%,100%,.4)}.mat-tab-group.mat-background-warn .mat-ripple-element,.mat-tab-nav-bar.mat-background-warn .mat-ripple-element{background-color:hsla(0,0%,100%,.12)}.mat-toolbar{background:#f5f5f5;color:rgba(0,0,0,.87)}.mat-toolbar.mat-primary{background:#3f51b5;color:#fff}.mat-toolbar.mat-accent{background:#ff4081;color:#fff}.mat-toolbar.mat-warn{background:#f44336;color:#fff}.mat-toolbar .mat-focused .mat-form-field-ripple,.mat-toolbar .mat-form-field-ripple,.mat-toolbar .mat-form-field-underline{background-color:currentColor}.mat-toolbar .mat-focused .mat-form-field-label,.mat-toolbar .mat-form-field-label,.mat-toolbar .mat-form-field.mat-focused .mat-select-arrow,.mat-toolbar .mat-select-arrow,.mat-toolbar .mat-select-value{color:inherit}.mat-toolbar .mat-input-element{caret-color:currentColor}.mat-toolbar-multiple-rows{min-height:64px}.mat-toolbar-row,.mat-toolbar-single-row{height:64px}@media (max-width:599px){.mat-toolbar-multiple-rows{min-height:56px}.mat-toolbar-row,.mat-toolbar-single-row{height:56px}}.mat-tooltip{background:rgba(97,97,97,.9)}.mat-tree{background:#fff}.mat-nested-tree-node,.mat-tree-node{color:rgba(0,0,0,.87)}.mat-tree-node{min-height:48px}.mat-snack-bar-container{color:hsla(0,0%,100%,.7);background:#323232;box-shadow:0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12)}.mat-simple-snackbar-action{color:#ff4081} +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:initial;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:initial}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:initial}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}details{display:block}summary{display:list-item}[hidden],template{display:none}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:initial;background-image:none}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::placeholder,textarea::placeholder{color:#a0aec0}[role=button],button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}.space-x-2>:not(template)~:not(template){--space-x-reverse:0;margin-right:calc(.5rem * var(--space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--space-x-reverse)))}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-200{--bg-opacity:1;background-color:#edf2f7;background-color:rgba(237,242,247,var(--bg-opacity))}.bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.bg-opacity-25{--bg-opacity:0.25}.border-collapse{border-collapse:collapse}.border-white{--border-opacity:1;border-color:#fff;border-color:rgba(255,255,255,var(--border-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-gray-300{--border-opacity:1;border-color:#e2e8f0;border-color:rgba(226,232,240,var(--border-opacity))}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.border{border-width:1px}.border-t{border-top-width:1px}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.flex-1{flex:1 1 0%}.flex-auto{flex:1 1 auto}.flex-grow{flex-grow:1}.flex-shrink{flex-shrink:1}.clearfix:after{content:"";display:table;clear:both}.h-0{height:0}.h-2{height:.5rem}.h-10{height:2.5rem}.h-full{height:100%}.m-2{margin:.5rem}.m-4{margin:1rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mb-2{margin-bottom:.5rem}.mr-4{margin-right:1rem}.min-w-0{min-width:0}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.p-2{padding:.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.top-0{top:0}.right-0{right:0}.bottom-0{bottom:0}.left-0{left:0}.resize{resize:both}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.text-center{text-align:center}.text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.italic{font-style:italic}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.underline{text-decoration:underline}.ordinal{--font-variant-numeric-ordinal:var(--tailwind-empty,/*!*/ /*!*/);--font-variant-numeric-slashed-zero:var(--tailwind-empty,/*!*/ /*!*/);--font-variant-numeric-figure:var(--tailwind-empty,/*!*/ /*!*/);--font-variant-numeric-spacing:var(--tailwind-empty,/*!*/ /*!*/);--font-variant-numeric-fraction:var(--tailwind-empty,/*!*/ /*!*/);font-variant-numeric:var(--font-variant-numeric-ordinal) var(--font-variant-numeric-slashed-zero) var(--font-variant-numeric-figure) var(--font-variant-numeric-spacing) var(--font-variant-numeric-fraction);--font-variant-numeric-ordinal:ordinal}.visible{visibility:visible}.invisible{visibility:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.w-0{width:0}.w-2{width:.5rem}.w-1\/2{width:50%}.w-full{width:100%}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.transform{--transform-translate-x:0;--transform-translate-y:0;--transform-rotate:0;--transform-skew-x:0;--transform-skew-y:0;--transform-scale-x:1;--transform-scale-y:1;transform:translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y))}.transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}@keyframes spin{to{transform:rotate(1turn)}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}body,html{height:100%}body{margin:0;font-family:Roboto,Helvetica Neue,sans-serif} \ No newline at end of file diff --git a/www/worker-basic.min.js b/www/worker-basic.min.js new file mode 100644 index 00000000000..e9606030937 --- /dev/null +++ b/www/worker-basic.min.js @@ -0,0 +1,20 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +// tslint:disable:no-console + +self.addEventListener('install', event => { + self.skipWaiting(); +}); + +self.addEventListener('activate', event => { + event.waitUntil(self.clients.claim()); + self.registration.unregister().then(() => { + console.log('NGSW Safety Worker - unregistered old service worker'); + }); +});