diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2bd145f --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.pyc +.DS_Store +service-account.json +.eslintrc.js +*.sublime-project +*.sublime-workspace +node_modules/ +client/build +spectrogram/build +/.vscode +render_installation/png +.history/ +kubernetes/tools/average_image/test/ +kubernetes/tools/average_image/images/ +kubernetes/tools/classifier_clustering/out.svg diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e39c799 --- /dev/null +++ b/LICENSE @@ -0,0 +1,192 @@ + + 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 + + +Copyright 2019 Google Inc. All Rights Reserved. + +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. \ No newline at end of file diff --git a/README.md b/README.md index e69de29..f091539 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,11 @@ +# Pattern Radio: Whale Songs + +[Pattern Radio](https://patternradio.withgoogle.com) lets you navigate thousands of hours of underwater recordings in your browser. The experiment is split into two main components, [the backend code](./kubernetes/README.md), which uses Kubernetes to render thousands of spectrogram images from NOAA's audio dataset and place them into a Google Storage Bucket for the client-side to download, and the [the browser code](./client/README.md) which uses WebGL to render all of these pre-rendered images. + +![screenshot](preview.gif) + +We encourage open sourcing projects as a way of learning from each other. Please respect our and other creators’ rights, including copyright and trademark rights when present, when sharing these works and creating derivative work. If you want more info on Google's policy, you can find that [here](https://www.google.com/permissions/). + +You can get in touch with the team on [patternradio-support@google.com](mailto:patternradio-support@google.com) + +This is an experiment, not an official Google product. We’ll do our best to support and maintain this experiment but your mileage may vary. diff --git a/client/.eslintrc.js b/client/.eslintrc.js new file mode 100644 index 0000000..33844b3 --- /dev/null +++ b/client/.eslintrc.js @@ -0,0 +1,46 @@ +// Copyright 2019 Google Inc. +// +// 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. + +module.exports = { + env: { + browser: true, + es6: true, + }, + plugins: ['lit', 'prettier'], + extends: ['plugin:lit/recommended'], + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + }, + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + }, + rules: { + semi: ['warn', 'never'], + 'object-curly-spacing': ['error', 'always'], + 'require-jsdoc': 'off', + // 'indent': ['error', 4], + 'max-len': [ + 'error', + 120, + { + ignoreTemplateLiterals: true, + }, + ], + 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + 'no-underscore-dangle': ['error', { allowAfterThis: true }], + 'prettier/prettier': 'warn', + }, +} diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..2f3bf94 --- /dev/null +++ b/client/README.md @@ -0,0 +1,15 @@ +## Overview + +Pattern Radio lets you navigate thousands of hours of underwater recordings in your browser. The spectrograms for all of the audio are generated using an offline task run on Kubernetes. See (this README)[../kubernetes/README.md] for reference on how we did that. + +## Installation + +Make sure that you've got node and npm installed. To install all of the dependencies run: + +`npm install` + +Then to build the code and start a server run: + +`npm run watch` + +You can then view the experiment running locally at http://localhost:8080/build/ \ No newline at end of file diff --git a/client/assets/ann.jpg b/client/assets/ann.jpg new file mode 100644 index 0000000..54e90cc Binary files /dev/null and b/client/assets/ann.jpg differ diff --git a/client/assets/annie.jpg b/client/assets/annie.jpg new file mode 100644 index 0000000..f45155c Binary files /dev/null and b/client/assets/annie.jpg differ diff --git a/client/assets/annotations.tsv b/client/assets/annotations.tsv new file mode 100644 index 0000000..666ba82 --- /dev/null +++ b/client/assets/annotations.tsv @@ -0,0 +1,85 @@ +location time_start time_end comment author optional_anchor +Hawaii 2015-02-11T14:20:05.813Z 2015-02-11T14:20:10.133Z Is this a duck quacking, or maybe a robot whale? Turns out these funny sounding beeps throughout the recordings actually have to do with the recording equipment. Class Workshop +Hawaii 2015-02-11T14:21:28.879Z 2015-02-11T14:25:15.781Z These shapes reminded us of looking at an ancient alphabet. Class Workshop +Hawaii 2015-02-11T15:19:50.512Z 2015-02-11T15:19:55.954Z Check out this backwards F shape. It sounds like two whales might be harmonizing! Class Workshop +Hawaii 2015-02-11T15:26:21.014Z 2015-02-11T15:26:31.350Z Neat upward woop sounds repeated 7 times here. Class Workshop +Hawaii 2015-02-11T15:26:45.639Z 2015-02-11T15:26:50.998Z Sounds a lot like an elephant. Class Workshop +Hawaii 2015-02-11T15:26:54.830Z 2015-02-11T15:26:58.302Z The high-pitched squeaky sounds here are so interesting. Is that the whale producing those high sounds? Class Workshop +Hawaii 2015-02-17T04:55:18.597Z 2015-02-17T05:34:20.541Z Does anyone know what causes this long, high-frequency glow? Class Workshop +Hawaii 2015-02-25T04:25:35.670Z 2015-02-25T04:26:21.105Z See the upward bloop sounds? It's so interesting that the whale does 2 bloops, then 3, then 4, then 5, in sequence. Class Workshop +Hawaii 2014-12-06T05:13:06.654Z 2014-12-06T05:13:50.684Z The humpback whales have arrived in Hawaii for breeding season! Zoom in to see the detailed spectrogram and to listen to distant singing. Annie Lewandowski +Hawaii 2015-01-28T16:21:18.908Z 2015-01-28T16:22:21.167Z Humpback whale song is composed of several unique themes that are constantly evolving as they are being sung. Listen to the phrase that is repeated within the theme here: a few, broad-spectrum upsweep sound units are followed by two sound units that each include a sustain and small sweep in frequency, the first of these at a mid-range frequency and the second at a higher-range frequency. Do you hear the slight differences with each repetition of the phrase? That's the individual voice and spontaneity of the composer! Annie Lewandowski annie_first_theme +Hawaii 2015-01-28T16:22:22.167Z 2015-01-28T16:23:40.192Z Notice how the theme we’ve been listening to changes here into something transitional, containing elements of the theme we just heard, and foreshadowing the theme that’s about to come. Annie Lewandowski +Hawaii 2015-01-28T16:23:41.192Z 2015-01-28T16:28:41.192Z The new theme! We hear a single, longer broad-spectrum upsweep, followed by a low-mid range sound unit that oscillates in pitch, which is followed by a high squeal. Again, notice how the articulation of all three sound units that make up this phrase are not exact with repetition. Can you describe the ways in which each sound unit is flexible? Notice the freedom within the structure. Annie Lewandowski annie_new_theme +Hawaii 2015-01-28T16:30:20.700Z 2015-01-28T16:32:24.281Z Do you hear that familiar upsweep in the distance? Another whale! When breeding season is in full swing, a hydrophone can pick up (and your ears underwater can hear!) an asynchronous, barnyard chorus of male humpback whales singing. All male humpback whales within a population sing a similar, evolving song - remarkable evidence of horizontal cultural transmission in a non-human. Annie Lewandowski +Hawaii 2015-01-28T16:33:24.281Z 2015-01-28T16:36:23.120Z This is sound from a large ocean vessel covering the whole frequency spectrum of the humpback whale song. Marine scientist Chris Clark calls this cacophony of human-made noise [“acoustic bleaching”]( https://e360.yale.edu/features/how_ocean_noise_pollution_wreaks_havoc_on_marine_life) and it’s a real threat to the survival of humpback whales. These marine mammals can’t see beyond the length of their own bodies and use sound to facilitate diverse aspects of their survival. Annie Lewandowski +Hawaii 2015-01-28T17:25:39.240Z 2015-01-28T17:29:19.902Z More acoustic bleaching from ship noise. While ship noise is a major concern, of even greater concern is seismic air gun blasting. Ocean exploration for oil and gas involves firing loud pressurized blasts of air through the ocean and into the seafloor every ten seconds creating a storm of noise [“six or seven orders of magnitude greater than the loudest ship sounds”]( https://e360.yale.edu/features/how_ocean_noise_pollution_wreaks_havoc_on_marine_life) Annie Lewandowski +Hawaii 2015-01-28T17:29:20.902Z 2015-01-28T17:30:07.754Z As you hear the ship noise lessen here, can you hear the theme from earlier in the afternoon [at 16:31](#annie_new_theme) emerge? Annie Lewandowski annie_second_theme +Hawaii 2015-01-28T17:31:07.754Z 2015-01-28T17:35:48.335Z The next theme enters, composed of two sound units – one broad-spectrum upsweep followed by a mid-range sound unit that oscillates slightly, often bending up or down in pitch at the tail of the sound. Annie Lewandowski annie_third_theme +Hawaii 2015-01-28T17:35:49.335Z 2015-01-28T17:36:28.228Z This is where the transitional material begins – it contains elements of both the preceding and succeeding themes. Annie Lewandowski +Hawaii 2015-01-28T17:36:29.228Z 2015-01-28T17:38:53.605Z Following the transitional material, here we begin listening to the solid statement of the next theme - a short sustained sound (slightly higher in frequency than the lowest frequency of the former theme’s upsweep), followed by my favorite rumble-into-exasperation sound Annie Lewandowski +Hawaii 2015-01-28T17:38:54.605Z 2015-01-28T17:40:42.749Z And the next theme! Two low grunts followed by 3-5 upsweeps Annie Lewandowski +Hawaii 2015-01-28T17:40:43.749Z 2015-01-28T17:41:32.093Z This next theme is a real dazzler which points to whale behavior. This new theme is composed of high squeals and “popcorn.” While I was in Hawaii in February 2019 with the Hawai’i Marine Mammal Consortium and Katy Payne, I learned that the “popcorn” sounds that you hear are the sounds the singing whale when he comes up to the surface to breathe. Do you hear the sound attenuate (weaken) as he gets nearer the surface? Annie Lewandowski +Hawaii 2015-01-28T17:41:33.093Z 2015-01-28T17:41:47.291Z A ha! Perhaps the boat that we hear rev into high gear at this moment just saw the humpback whale blows following the “popcorn” sounds. Maybe they’re going to get a closer look? I hope they’re careful! [Whales are maimed and killed every year by ship strikes.](https://iwc.int/ship-strikes) Annie Lewandowski +Hawaii 2015-01-28T17:41:48.291Z 2015-01-28T17:42:48.088Z Now the singer has perhaps settled back into his singing posture, vertically with his head down, to sing what we’ll call the “first” theme following the breath – a few, short upsweep sound units followed by two longer sound units, the second sound higher in frequency than the first. Recognize this theme from [16:21](#annie_first_theme)? Annie Lewandowski +Hawaii 2015-01-28T17:42:49.088Z 2015-01-28T17:44:35.577Z And now the second theme after blowing - one upsweep, followed by a mid-range oscillating sustain, then a higher range oscillating sustain – we’ve heard one whole song cycle from 17:29-17:42! Annie Lewandowski +Hawaii 2015-01-28T17:44:36.577Z 2015-01-28T17:46:37.827Z Shipping noise is stressful for whales! Check out [this incredible research](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3350670/pdf/rspb20112429.pdf) done by a team lead by Rosalind Rolland in which they demonstrate “that reduced ship traffic in the Bay of Fundy, Canada, following the events of 11 September 2001, resulted in a 6 dB decrease in underwater noise with a significant reduction below 150 Hz. This noise reduction was associated with decreased baseline levels of stress-related faecal hormone metabolites (glucocorticoids) in North Atlantic right whales (Eubalaena glacialis). This is the first evidence that exposure to low-frequency ship noise may be associated with chronic stress in whales, and has implications for all baleen whales in heavy ship traffic areas, and for recovery of this endangered right whale population.” Annie Lewandowski +Hawaii 2015-01-28T17:46:38.827Z 2015-01-28T17:48:09.950Z And look how the song evolves as we’re listening! Here we can listen to transitional material that we didn’t hear in the last iteration of the song between [the second](#annie_second_theme) and [third](#annie_third_theme) themes. Is the whale auditioning new material for a future theme? What an improviser! Annie Lewandowski +Hawaii 2015-01-28T17:48:10.950Z 2015-01-28T17:56:08.742Z And now the third theme since blowing – one upsweep, one mid-range oscillation that’s a bit lower than the last time we heard it at [17:31](#annie_third_theme). Go on and continue listening - can you identify innovations in the themes as the breeding season moves forward, on the larger scale from this breeding season to the next? Annie Lewandowski +Hawaii 2014-03-25T00:00:00.059Z 2014-03-25T00:01:14.815Z This HARP was deployed on March 23 but we programmed it to start recording on March 25 so that we would miss all of the noisy sounds from it settling to the ocean floor. You can immediately hear a far off singing humpback. Ann Allen +Hawaii 2015-02-11T09:03:17.986Z 2015-02-11T09:09:00.642Z This is one full song from a humpback whale singing near the HARP. Ann Allen +Hawaii 2015-02-11T09:08:57.553Z 2015-02-11T09:09:29.210Z Here is a brief pause during which the whale goes to the surface to take a few breaths before starting his song over again. While he is away you can hear the faint song of other whales in the distance. Ann Allen +Hawaii 2015-02-11T09:05:10.098Z 2015-02-11T09:05:18.600Z You can hear the sound of the hard drive we use to record the acoustic data spinning as it writes the data to the disk. This happens every 75 seconds. When using electronic equipment in the ocean it's super hard to get rid of all noise. (Turn off "Reduce visual noise" in settings to see the sound clearly.) Ann Allen +Hawaii 2015-02-11T09:09:38.883Z 2015-02-11T09:15:05.554Z Do you notice the similarities between this song and the last song? It's the same whale singing the same song. All humpback whales in one region will sing a very similar song each year. The song changes over time and between areas of the world depending on what's trending at the time. Ann Allen +Hawaii 2015-02-11T11:51:11.558Z 2015-02-11T12:24:48.629Z Sometimes we get strange noises on the hydrophone. This sounds like something is knocking against the equipment, but we have no way of knowing what it is. Any ideas? Ann Allen +Hawaii 2015-02-11T16:21:24.414Z 2015-02-11T16:30:39.299Z A small boat is passing overhead. Even little boats generate a lot of sound that can travel a long way. Imagine if you had to listen to this whine all day while you were trying to sing to a crowd? Ann Allen +Hawaii 2015-02-13T14:03:48.395Z 2015-02-23T07:11:46.885Z If you zoom way out you can see a pattern of boat noise every day from sunrise to sunset. This is all of the fishing and recreational boats coming out on the ocean. They make a LOT of noise. Humans don't usually realize how noisy boats are, but it can make it hard for whales to hear each other. Ann Allen +Hawaii 2015-03-06T14:57:56.396Z 2015-04-24T22:20:10.018Z We had a bit of gap here while we switched out the HARP recorders. Sometimes the batteries run out or the memory fills before we can switch out recorders so we miss some of the humpback singing that year. Ann Allen +Hawaii 2015-05-10T06:47:22.438Z 2015-05-10T06:57:51.829Z This is pretty late in the year for a humpback to still be in Hawaii. Most of them migrate to Alaska in February or March to start feeding. I thought the computer AI got it wrong until I zoomed in and found one whale still hanging around and singing faintly! Ann Allen +Hawaii 2015-02-19T02:29:04.260Z 2015-02-19T02:29:06.948Z Bottom of the song, the lowest of the low, then listen to what happens… (click arrow for next comment) David Rothenberg +Hawaii 2015-02-19T02:31:17.192Z 2015-02-19T03:56:32.243Z The rich rumblings of the low throom… one big syllable from the whale. Some light echoes, probably against a sea mount. On the early recordings people thought this was artificial reverb added to the mix! [Here](https://soundcloud.com/david-rothenberg-530981395/a-varied-humpback-song) you can hear this same section with the background noise taken out. David Rothenberg +Hawaii 2015-02-19T02:30:36.246Z 2015-02-19T02:30:42.281Z An unusually fast rhythm, setting the stage for the rise. David Rothenberg +Hawaii 2015-02-19T02:30:52.521Z 2015-02-19T02:31:20.935Z Several whales get creative, take a breath, and expand their sound palette… David Rothenberg +Hawaii 2015-02-19T02:31:05.255Z 2015-02-19T02:31:16.940Z The wild whoop! This is the sound that convinced me I might reach out to the whales with my clarinet, and on one track they started to aim for a steady pitch, listen [here](http://www.thousandmilesong.com/jamming-with-whales-in-hawaii/). David Rothenberg +Hawaii 2015-02-19T02:36:13.572Z 2015-02-19T02:36:23.346Z A rich resonant grumble again, followed by a clear tone, showing the real difference the spectrogram can reveal. David Rothenberg +Hawaii 2015-02-19T02:38:18.181Z 2015-02-19T02:38:23.027Z How low can you go? Here, the humpback goes down into the deep frequency range of the blue whale… But the repeating pattern, clear melodic structure, assures us this is a humpback and not some other species. David Rothenberg +Hawaii 2015-02-19T02:38:58.758Z 2015-02-19T02:39:09.562Z The long extended grumbling sound — by looking at the spectrogram you can see its rhythmic component that can be a bit hard to hear… David Rothenberg +Hawaii 2015-02-19T02:39:52.442Z 2015-02-19T02:48:35.754Z The next several minutes really sound like a duet: two equadistant whales… somewhat unusual. How do you think they fit together? David Rothenberg +Hawaii 2015-02-19T02:39:58.540Z 2015-02-19T02:40:07.155Z Three kinds of humpback syllables in a row: clicks, rise, and whistle. Each doubled because the sound is echoing off a nearby undersea surface. David Rothenberg +Hawaii 2015-02-19T02:40:55.751Z 2015-02-19T02:41:00.460Z Two adjecent whales: one moans, the other nearby one whistles, seemingly in response. David Rothenberg +Hawaii 2015-02-19T02:38:45.845Z 2015-02-19T02:39:00.471Z This high whoop almost sounds like a different species, but it is still a humpback. They have a wide range from low to high, similar to that of a cello or the human voice. David Rothenberg +Hawaii 2015-02-19T02:40:22.338Z 2015-02-19T02:41:18.500Z Now we are really hearing an ensemble of whales. No one has really studied how these individual whale musicians interact… they aren’t really solo performers. The spectrogram shows a general descending movement slowly down, minute by minute, like a grounding, moving melody. I wanted a clearer way to visualize this whole structure, so we created [this](https://medium.com/@dealville/whales-synchronize-their-songs-across-oceans-and-theres-sheet-music-to-prove-it-b1667f603844). David Rothenberg +Hawaii 2015-02-19T02:41:22.114Z 2015-02-19T02:41:44.799Z Here we hear the most famous descending humpback melody, the one that Paul Winter and others have harmonized to sound a bit like Bach, listen [here](https://www.youtube.com/watch?v=09vcM449-X8) to “The Lullabye of the Great Mother Whale to the Baby Seal Pups.” [Here](https://soundcloud.com/david-rothenberg-530981395/the-most-famous-humpback-song) is a cleaner version of this section… David Rothenberg +Hawaii 2015-02-19T02:41:22.114Z 2015-02-19T02:41:44.799Z [Here](https://soundcloud.com/david-rothenberg-530981395/the-most-famous-humpback-song) is a cleaner version of this section… David Rothenberg +Hawaii 2015-02-19T02:42:29.723Z 2015-02-19T02:43:03.690Z You can hear the echo here, whale sound bouncing against the sea floor or a sea mount. A few researchers think the humpbacks could use these sounds as sonar, but most don’t buy that argument. There is tonal richness, the gravitas, the emotion held clearly in these songs. Humpback whale brains contain spindle neurons, previously only found in higher primates, suggesting an ability to experience and express complex emotions. David Rothenberg +Hawaii 2015-02-19T02:46:39.881Z 2015-02-19T02:46:39.881Z Look at the rich complex spectrogram of this single solid tone, full of energy and pathos! David Rothenberg +Hawaii 2015-02-19T04:26:48.525Z 2015-02-19T04:48:42.498Z Here is basically a clear, complete song, very early in the morning, not too much background noise. Better examples of complete whale songs can be heard here, on the compilation album I put together [New Songs of the Humpback Whale](https://importantrecords.com/products/imprec433). David Rothenberg +Hawaii 2015-02-19T06:11:17.648Z 2015-02-19T06:37:17.100Z At this scale we see the intertwining songs of two nearby whales singing simultaneously. David Rothenberg +Hawaii 2015-06-12T22:23:17.072Z 2015-06-12T22:23:24.412Z I think I hear a "whoop" here. Sometimes it's hard to tell whether you're hearing a whale call or imagining it, especially in the presence of noise. Matt Harvey +Hawaii 2015-01-31T02:39:37.857Z 2015-01-31T03:40:25.356Z The spectrogram on the 1-hour time scale has a few repeats of what is likely the same song. Just as Pattern Radio is displaying multi-scale images, deep learning models may choose to take inputs at multiple time scales. Matt Harvey +Hawaii 2015-01-31T03:01:19.253Z 2015-01-31T03:01:21.883Z About 2/3 of the way up the spectrogram, we see the same higher-frequency downsweep repeating 3 times. We hear it as reverb. Sound came from the whale to the hydrophone along multiple paths. For example it might have bounced off a reef. Matt Harvey +Hawaii 2015-05-30T11:32:42.228Z 2015-05-30T11:32:56.006Z This noise from the magnetic hard disks in the HARP appears every time it powers on; it's on a cycle so it can be left out longer. Early versions of our humpback detection model sometime mistook some of these for humpback calls. Matt Harvey +Hawaii 2015-05-30T10:50:10.807Z 2015-05-30T10:57:35.418Z The bright arches have a pitch trajectory similar to a humpback calls but over a longer time scale. This type of noise confused early versions of the model until we added some negative training examples. Matt Harvey +Hawaii 2015-05-30T20:50:26.110Z 2015-05-30T21:21:09.650Z We don't expect humpbacks in May, which is outside the winter season, but we can still find other sounds. I guess this is a large vessel passing. Matt Harvey +Hawaii 2014-03-27T03:04:15.540Z 2014-03-27T03:30:42.197Z Song interrupted. Actually the song keeps going the whole time, but it's much harder to hear over the boat noise. This effect is called masking. If the song is important to the singer's breeding prospects, this is not a win. Matt Harvey +Hawaii 2014-03-27T03:28:26.433Z 2014-03-27T03:28:32.252Z Minke boing. Also confused early versions of the classifier. Scientists tell me this sound comes from a whale, but the sound is so strange that I have the urge to see for myself. Matt Harvey +Hawaii 2014-03-27T03:25:21.967Z 2014-03-27T03:30:15.590Z A bunch of repeats of two song units don't vary in structure, but there is frequency modulation on the scale of minutes. We can see similar patterns at other times. Their meaning is a mystery to me. Matt Harvey +Hawaii 2014-03-27T02:08:44.535Z 2014-03-27T02:08:58.916Z A modeling challenge or at least a great example to have in your eval set: humpback song and two common error modes (Minke boing and hard disk noise) were all recorded at the same time. Matt Harvey +Hawaii 2014-03-26T13:31:28.007Z 2014-03-26T13:32:29.275Z There's song in this section, but it's very faint, probably because the whale is far away. Zoom out to see it surrounded by louder sections. My deep net machine learning model worked better than my ears for faint calls. Matt Harvey +Hawaii 2014-06-08T12:08:29.083Z 2014-06-13T15:13:04.583Z Day/night cycle is apparent from a spectrogram whose time scale is days. It's louder in the daytime. (The times displayed in the UI are UTC, so shifted from Hawaii time.) Matt Harvey +Hawaii 2015-05-12T05:20:37.809Z 2015-05-12T06:50:17.746Z In the upper third of the spectrogram, we see that the spectrum of the background noise is varying independent of sounds that are louder or have quicker onsets. Could it be caused by some large-scale oceanic condition? Matt Harvey +Hawaii 2014-05-06T05:58:44.123Z 2014-05-06T06:00:01.777Z May is late season for humpbacks in Hawaii, but it sounds like someone's still around, albeit a bit distant. Matt Harvey +Hawaii 2014-12-27T01:57:31.391Z 2014-12-27T02:14:26.855Z This is a boat passing overhead. The thing that I found strange is that the doppler effect is actually flipped. Usually the pitch would rise as something approaches you and fall as it goes away. This inverse doppler effect caused by sound bouncing off the ocean floor is called Lloyd's Mirror. Yotam Mann +Hawaii 2014-12-07T04:29:50.920Z 2014-12-07T04:29:57.619Z I'm not sure what this mechanical noise is. It looks similar to the other boat engines in terms of having a loud harmonic sound, but it doesn't have the same doppler shift as the other boats, it might be a boat standing overhead. Yotam Mann +Hawaii 2014-06-14T12:44:28.181Z 2014-06-17T06:34:41.955Z There is a really clear day/night pattern of activity which becomes really apparent at certain zoom levels. It seems like there might also be boats which pass overhead at around the same time each day. I wonder if it's possible to figure out the ferry schedule from studying these spectrograms. Yotam Mann +Hawaii 2014-12-06T07:13:51.065Z 2014-12-06T07:13:54.565Z The [HARP](http://cetus.ucsd.edu/technologies_AutonomousRecorders.html) (High-Frequency Acoustic Recording Package) combines a sensitive hydrophone (underwater microhpone) with 12 spinning hard drive disks in a water-tight container. You can hear the hard drive spinning up here. Turn off the denoising to see the sound more clearly in the spectrogram. Yotam Mann +Hawaii 2014-12-06T07:13:54.893Z 2014-12-06T07:14:04.983Z Immediately after you hear the disk spin up, there is an electronic or mechanical sound that lasts for about 10 seconds. I think that might be the disks write-head or some sort of electrical hum associated with the disk spinning up. Yotam Mann +Hawaii 2014-12-07T04:29:50.920Z 2014-12-07T04:29:57.619Z The HARP is designed to live on the ocean floor for 6 to 12 months. It stores the audio in small audio chunks around a minute long. The spectrograms are generated from these small chunks, so occasionally you can see where one audio file ends and another begins. In this recording of a boat passing overhead, you can see a small discontinuity as a vertical seam immediately before the disk spins up to write another chunk of sound. Yotam Mann +Hawaii 2015-02-10T10:33:14.276Z 2015-02-10T10:33:27.538Z Here you can see an artifact of our denoising algorithm. It essentially subtracted out the repeated HARP noise. Yotam Mann +Hawaii 2015-01-15T01:57:06.698Z 2015-01-21T20:07:02.683Z This tour starts with a one-week view of an acoustic scene. Follow the comments in order to zoom in and explore the different types of sounds that contribute to the scene. In the week view, do you see a pattern in the sounds? What do you think the cause of this pattern might be? Chris Clark +Hawaii 2015-01-17T10:09:06.269Z 2015-01-19T05:01:52.977Z Now we zoom into a two-day section of this same acoustic scene. What visual features of this more zoomed in look catch your eye? Can you hear differences between different parts of this time period? Are there visual features that you can see on the spectrogram but not hear? Chris Clark chris_two +Hawaii 2015-01-17T23:57:09.238Z 2015-01-18T05:29:02.482Z Now we're in a five-hour section of our acoustic scene. What features catch your eye? Can you see these features in [the two-day zoom level](#chris_two)? Can you hear differences between different parts of this scene? At this level, are there acoustic features you can hear but not see? Chris Clark +Hawaii 2015-01-18T03:31:25.930Z 2015-01-18T04:19:49.002Z We're zooming in even further to a 45 minute view of our scene. What features catch your eye? Can you see these features in [the two-day zoom level](#chris_two)? Can you hear differences between different parts of this scene? Are there acoustic features you can hear but not see? Chris Clark +Hawaii 2015-01-18T09:46:22.945Z 2015-01-18T12:36:56.890Z Now let's zoom out again and think again about what features are catching your eye. Can you hear differences between different parts of this scene? Are there acoustic features you can hear but not see? Chris Clark +Hawaii 2015-01-19T03:43:45.562Z 2015-01-19T04:01:27.852Z Now we're zooming back in, this time to a 12-minute view. What do we hear now? Do you hear a whale singing? What else do you hear? Can you see other sounds on the spectrogram that you hear in this scene? What do you think these sounds are? Chris Clark +Hawaii 2015-01-20T23:19:58.179Z 2015-01-20T23:21:20.987Z And now we're at a two-minute zoom level. What's different about what you see and hear at this level? Do you prefer to listen and watch when the scene is zoomed in or zoomed out? Why is that? What can you and can’t you observe using different zoom levels and acoustic scene durations? Chris Clark \ No newline at end of file diff --git a/client/assets/brian.jpg b/client/assets/brian.jpg new file mode 100644 index 0000000..985ab8a Binary files /dev/null and b/client/assets/brian.jpg differ diff --git a/client/assets/chris.jpg b/client/assets/chris.jpg new file mode 100644 index 0000000..20cec91 Binary files /dev/null and b/client/assets/chris.jpg differ diff --git a/client/assets/class.jpg b/client/assets/class.jpg new file mode 100644 index 0000000..c47fb43 Binary files /dev/null and b/client/assets/class.jpg differ diff --git a/client/assets/david.jpg b/client/assets/david.jpg new file mode 100644 index 0000000..af3f7a8 Binary files /dev/null and b/client/assets/david.jpg differ diff --git a/client/assets/favicon/1024x1024px.png b/client/assets/favicon/1024x1024px.png new file mode 100644 index 0000000..c00dad4 Binary files /dev/null and b/client/assets/favicon/1024x1024px.png differ diff --git a/client/assets/favicon/128x128px.png b/client/assets/favicon/128x128px.png new file mode 100644 index 0000000..a0f3d69 Binary files /dev/null and b/client/assets/favicon/128x128px.png differ diff --git a/client/assets/favicon/152x152px.png b/client/assets/favicon/152x152px.png new file mode 100644 index 0000000..c60b3ae Binary files /dev/null and b/client/assets/favicon/152x152px.png differ diff --git a/client/assets/favicon/16x16px.png b/client/assets/favicon/16x16px.png new file mode 100644 index 0000000..8737d15 Binary files /dev/null and b/client/assets/favicon/16x16px.png differ diff --git a/client/assets/favicon/256x256px.png b/client/assets/favicon/256x256px.png new file mode 100644 index 0000000..3dff6c2 Binary files /dev/null and b/client/assets/favicon/256x256px.png differ diff --git a/client/assets/favicon/32x32px.png b/client/assets/favicon/32x32px.png new file mode 100644 index 0000000..f4e9cd7 Binary files /dev/null and b/client/assets/favicon/32x32px.png differ diff --git a/client/assets/favicon/512x512px.png b/client/assets/favicon/512x512px.png new file mode 100644 index 0000000..2eb3dc9 Binary files /dev/null and b/client/assets/favicon/512x512px.png differ diff --git a/client/assets/favicon/64x64px.png b/client/assets/favicon/64x64px.png new file mode 100644 index 0000000..c649d72 Binary files /dev/null and b/client/assets/favicon/64x64px.png differ diff --git a/client/assets/icon-chat-yellow.svg b/client/assets/icon-chat-yellow.svg new file mode 100644 index 0000000..ce86d22 --- /dev/null +++ b/client/assets/icon-chat-yellow.svg @@ -0,0 +1,12 @@ + + + +Icons / Chat Yellow +Created with Sketch. + + + + + + diff --git a/client/assets/icon-comment.png b/client/assets/icon-comment.png new file mode 100644 index 0000000..85b8294 Binary files /dev/null and b/client/assets/icon-comment.png differ diff --git a/client/assets/matt.jpg b/client/assets/matt.jpg new file mode 100644 index 0000000..7053df2 Binary files /dev/null and b/client/assets/matt.jpg differ diff --git a/client/assets/pattern-radio-whale-song.svg b/client/assets/pattern-radio-whale-song.svg new file mode 100644 index 0000000..66c2881 --- /dev/null +++ b/client/assets/pattern-radio-whale-song.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/assets/peter.jpg b/client/assets/peter.jpg new file mode 100644 index 0000000..74fd90f Binary files /dev/null and b/client/assets/peter.jpg differ diff --git a/client/assets/share.jpg b/client/assets/share.jpg new file mode 100644 index 0000000..5ee3e93 Binary files /dev/null and b/client/assets/share.jpg differ diff --git a/client/assets/similarity-221-raw.png b/client/assets/similarity-221-raw.png new file mode 100644 index 0000000..4db7462 Binary files /dev/null and b/client/assets/similarity-221-raw.png differ diff --git a/client/assets/viridis.png b/client/assets/viridis.png new file mode 100644 index 0000000..3e2533d Binary files /dev/null and b/client/assets/viridis.png differ diff --git a/client/assets/yotam.jpg b/client/assets/yotam.jpg new file mode 100755 index 0000000..4b856e8 Binary files /dev/null and b/client/assets/yotam.jpg differ diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 0000000..8bd7b7b --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,9034 @@ +{ + "name": "pattern-radio", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.2.tgz", + "integrity": "sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helpers": "^7.2.0", + "@babel/parser": "^7.2.2", + "@babel/template": "^7.2.2", + "@babel/traverse": "^7.2.2", + "@babel/types": "^7.2.2", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.10", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.2.tgz", + "integrity": "sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==", + "dev": true, + "requires": { + "@babel/types": "^7.2.2", + "jsesc": "^2.5.1", + "lodash": "^4.17.10", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-call-delegate": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz", + "integrity": "sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-define-map": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", + "integrity": "sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz", + "integrity": "sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.2.2.tgz", + "integrity": "sha512-YRD7I6Wsv+IHuTPkAmAS4HhY0dkPobgLftHp0cRGZSdrRvmZY8rFvae/GVu3bD00qscuvK3WPHB3YdNpBXUqrA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/template": "^7.2.2", + "@babel/types": "^7.2.2", + "lodash": "^4.17.10" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0.tgz", + "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz", + "integrity": "sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "@babel/helpers": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.2.0.tgz", + "integrity": "sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==", + "dev": true, + "requires": { + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.5", + "@babel/types": "^7.2.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.2.tgz", + "integrity": "sha512-UNTmQ5cSLDeBGBl+s7JeowkqIHgmFAGBnLDdIzFmUNSuS5JF0XBcN59jsh/vJO/YjfsBqMxhMjoFGmNExmf0FA==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-1L5mWLSvR76XYUQJXkd/EEQgjq8HHRP6lQuZTTg0VA4tTGPpGemmCdAfQIz1rzEuWAm+ecP8PyyEm30jC1eQCg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz", + "integrity": "sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.2.0" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz", + "integrity": "sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz", + "integrity": "sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.2.tgz", + "integrity": "sha512-gEZvgTy1VtcDOaQty1l10T3jQmJKlNVxLDCs+3rCVPr6nMkODLELxViq5X9l+rfxbie3XrfrMCYYY6eX3aOcOQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.1.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.2.0.tgz", + "integrity": "sha512-coVO2Ayv7g0qdDbrNiadE4bU7lvCd9H539m2gMknyVjjMdwF/iCOM7R+E8PkntoqLkltO0rk+3axhpp/0v68VQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz", + "integrity": "sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", + "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz", + "integrity": "sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz", + "integrity": "sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", + "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz", + "integrity": "sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz", + "integrity": "sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz", + "integrity": "sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", + "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz", + "integrity": "sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA==", + "dev": true, + "requires": { + "@babel/helper-call-delegate": "^7.1.0", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz", + "integrity": "sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw==", + "dev": true, + "requires": { + "regenerator-transform": "^0.13.3" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz", + "integrity": "sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz", + "integrity": "sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + } + }, + "@babel/polyfill": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.2.5.tgz", + "integrity": "sha512-8Y/t3MWThtMLYr0YNC/Q76tqN1w30+b0uQMeFUYauG2UGTR19zyUtFrAzT23zNtBxPp+LbE5E/nwV/q/r3y6ug==", + "requires": { + "core-js": "^2.5.7", + "regenerator-runtime": "^0.12.0" + } + }, + "@babel/preset-env": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.2.0.tgz", + "integrity": "sha512-haGR38j5vOGVeBatrQPr3l0xHbs14505DcM57cbJy48kgMFvvHHoYEhHuRV+7vi559yyAUAVbTWzbK/B/pzJng==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.2.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.2.0", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.2.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.2.0", + "@babel/plugin-transform-classes": "^7.2.0", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.2.0", + "@babel/plugin-transform-dotall-regex": "^7.2.0", + "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.2.0", + "@babel/plugin-transform-function-name": "^7.2.0", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.2.0", + "@babel/plugin-transform-modules-systemjs": "^7.2.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-new-target": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-parameters": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.2.0", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.2.0", + "browserslist": "^4.3.4", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.3.0" + } + }, + "@babel/template": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2" + } + }, + "@babel/traverse": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.2.tgz", + "integrity": "sha512-E5Bn9FSwHpSkUhthw/XEuvFZxIgrqb9M8cX8j5EUQtrUG5DQUy6bFyl7G7iQ1D1Czudor+xkmp81JbLVVM0Sjg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.10" + }, + "dependencies": { + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz", + "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" + } + }, + "@pixi/constants": { + "version": "5.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-5.0.0-rc.3.tgz", + "integrity": "sha512-PrMmpD6EcQpwx4UkPnYcPuk63oqW3LG2Y+gFMEWVO0nBrkVw0JY/5uwJ9wRz0AiQotFdpd/AmMC3mZl8y1l7jw==", + "dev": true + }, + "@pixi/core": { + "version": "5.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@pixi/core/-/core-5.0.0-rc.3.tgz", + "integrity": "sha512-NuDKTmFkFEBYNKykT7BfpxmJdh5Cn3tmPVGvY/31LYvmo7XwYd6BEOOPL8RPJ/eaxPhPd6mRCeU9rmzPXQHA0g==", + "dev": true, + "requires": { + "@pixi/constants": "^5.0.0-rc.3", + "@pixi/display": "^5.0.0-rc.3", + "@pixi/math": "^5.0.0-rc.3", + "@pixi/runner": "^5.0.0-rc.3", + "@pixi/settings": "^5.0.0-rc.3", + "@pixi/ticker": "^5.0.0-rc.3", + "@pixi/utils": "^5.0.0-rc.3" + } + }, + "@pixi/display": { + "version": "5.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@pixi/display/-/display-5.0.0-rc.3.tgz", + "integrity": "sha512-lIcQeNH345FUTKcQDlsswLR1WBCwsUePqCXboB3YbDgH1O87kzNOMOM3UajKevZZY+dFUGPn+81EhazLDY/4dA==", + "dev": true, + "requires": { + "@pixi/math": "^5.0.0-rc.3", + "@pixi/settings": "^5.0.0-rc.3", + "@pixi/utils": "^5.0.0-rc.3" + } + }, + "@pixi/filter-adjustment": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-adjustment/-/filter-adjustment-2.7.0.tgz", + "integrity": "sha512-PZj8usw+Ven4PlEjC4ET3ZxBt+qpNEeLCPpv2zF9MyeECZRPPR/fNNDaEKNnjzVTE6Yhv201fTSzkDOWw5fEwA==", + "dev": true + }, + "@pixi/filter-color-map": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-map/-/filter-color-map-2.7.0.tgz", + "integrity": "sha512-Yz1pYi5RGL9NR0DMPqd1+i61yegEhh06ipLMRyLRpi+PGaG3kSYTib1gQByKEgAW4DTJlgSz1WLCqF2PRjlMEw==", + "dev": true + }, + "@pixi/filter-glow": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@pixi/filter-glow/-/filter-glow-2.7.0.tgz", + "integrity": "sha512-WmopMMAc17hQLWue3mrnGzlBJklVW5Q9OZXtZ/ffGsSadq3Bj9IqvndOvvLVrTLfFG9H63gjSAFkJQ4wYsGscw==" + }, + "@pixi/math": { + "version": "5.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@pixi/math/-/math-5.0.0-rc.3.tgz", + "integrity": "sha512-of/s2hHwTAN4wOxnLYQDIJftWFJgZ+S24U5N/fP6RHs6OQyegOX3q7f9uvwa+c6ssFNzGpPIF0Df7U2yKZdPFQ==", + "dev": true + }, + "@pixi/runner": { + "version": "5.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-5.0.0-rc.3.tgz", + "integrity": "sha512-Fk727Q2Pn8whUclbDima0uQDnmrjMQJ0SIN+3N2E7qe38RWKt4cXHVqzmIKsLVGmTnONPvuyVmEKAy9476u/Fw==", + "dev": true + }, + "@pixi/settings": { + "version": "5.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-5.0.0-rc.3.tgz", + "integrity": "sha512-brvJZMaeFMW3OKRZrFVG/I6PkES8N8euo7dmEoA/buDGimxSFBC0F8mbMgDHvI9DJ/YZxiCnYfBA6W/A+PPliQ==", + "dev": true, + "requires": { + "ismobilejs": "^0.5.1" + } + }, + "@pixi/ticker": { + "version": "5.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-5.0.0-rc.3.tgz", + "integrity": "sha512-FmWeMKyOb/TUEREpaeLVH3ZDs/NIEdIkB5+Yl3quXMFLhTBYxvecXkpRomEOsKGUOdQgoucsHlXk39sybsioHQ==", + "dev": true, + "requires": { + "@pixi/settings": "^5.0.0-rc.3" + } + }, + "@pixi/utils": { + "version": "5.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-5.0.0-rc.3.tgz", + "integrity": "sha512-vRrm4pfr6ROspMYKc95NAUjpFyyFLwvhlGu7z81LGbrGoR+BfWhO/BSh6RmKdz0HcFXIkZkBik10+zDHJmKybA==", + "dev": true, + "requires": { + "@pixi/constants": "^5.0.0-rc.3", + "@pixi/settings": "^5.0.0-rc.3", + "earcut": "^2.1.3", + "eventemitter3": "^3.1.0", + "url": "^0.11.0" + } + }, + "@polymer/polymer": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.1.0.tgz", + "integrity": "sha512-hwN8IMERsFATz/9dSMxYHL+84J9uBkPuuarxJWlTsppZ4CAYTZKnepBfNrKoyNsafBmA3yXBiiKPPf+fJtza7A==", + "requires": { + "@webcomponents/shadycss": "^1.5.2" + } + }, + "@tweenjs/tween.js": { + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-17.3.0.tgz", + "integrity": "sha512-SPkhNj9/wGfbdX2C3B3KhttLQ4iesd+Ny8Dv1RnqF1MFUIqsZz/OJVLzJEHSEl7zheNx70dvqrwfbCFDQ0sWBw==" + }, + "@webassemblyjs/ast": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz", + "integrity": "sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz", + "integrity": "sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz", + "integrity": "sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz", + "integrity": "sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz", + "integrity": "sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.7.11" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz", + "integrity": "sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz", + "integrity": "sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg==", + "dev": true + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz", + "integrity": "sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz", + "integrity": "sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz", + "integrity": "sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.11.tgz", + "integrity": "sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.1" + } + }, + "@webassemblyjs/utf8": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.11.tgz", + "integrity": "sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz", + "integrity": "sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/helper-wasm-section": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-opt": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "@webassemblyjs/wast-printer": "1.7.11" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz", + "integrity": "sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz", + "integrity": "sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz", + "integrity": "sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz", + "integrity": "sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/floating-point-hex-parser": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-code-frame": "1.7.11", + "@webassemblyjs/helper-fsm": "1.7.11", + "@xtuc/long": "4.2.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz", + "integrity": "sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11", + "@xtuc/long": "4.2.1" + } + }, + "@webcomponents/shadycss": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.8.0.tgz", + "integrity": "sha512-bx0TzeZ11VqYDGLuXfznen8+4u0hADk2dD5RNMFxWL9MM4c5NXCDCgNXgssb4i2zQOos/GGe4tl5AuE0LzJkLA==" + }, + "@webcomponents/webcomponentsjs": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.2.3.tgz", + "integrity": "sha512-8ndihKAmH01M1GifAxIwNMJU6yI2uFnMjm6rPvLxO4IG1nzeyOFJB1B2LQRHnx7ChQCKCLap2B18zIOVY4ROoQ==" + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.1.tgz", + "integrity": "sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", + "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", + "dev": true, + "requires": { + "acorn": "^5.0.0" + } + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "ajv": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "dev": true + }, + "asciichart": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/asciichart/-/asciichart-1.5.7.tgz", + "integrity": "sha512-XhFFEBxWZPfrEEy9Ekp36pGvOCxBr5fRBk2cjxWVPsgakKPdbba3aDpJpZ8i9jpUSRvQKBn2Zm7a+NLA60A/RA==", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "audiobuffer-to-wav": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/audiobuffer-to-wav/-/audiobuffer-to-wav-1.0.0.tgz", + "integrity": "sha1-1bQyJxRV5/7laxEc0PjWINf54QU=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-loader": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.4.tgz", + "integrity": "sha512-fhBhNkUToJcW9nV46v8w87AJOwAJDz84c1CL57n3Stj73FANM/b9TbCUK4YhdOwEyZ+OxhYpdeZDNzSI29Firw==", + "dev": true, + "requires": { + "find-cache-dir": "^1.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1", + "util.promisify": "^1.0.0" + } + }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "1.0.0" + } + }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "binary-extensions": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", + "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", + "dev": true + }, + "bit-twiddle": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bit-twiddle/-/bit-twiddle-1.0.2.tgz", + "integrity": "sha1-DGwfq+KyPRcXPZpht7cJPrnhdp4=" + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "dev": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "dev": true, + "requires": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true + }, + "cacache": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.1.tgz", + "integrity": "sha512-2PEw4cRRDu+iQvBTTuttQifacYjLPhET+SYO/gEFMy8uhi+jlJREDAjSF5FWSdV/Aw5h18caHA7vMTw2c+wDzA==", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "figgy-pudding": "^3.1.0", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.0", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "caniuse-lite": { + "version": "1.0.30000921", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000921.tgz", + "integrity": "sha512-Bu09ciy0lMWLgpYC77I0YGuI8eFRBPPzaSOYJK1jTI64txCphYCqnWbxJYjHABYVt/TYX/p3jNjLBR87u1Bfpw==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz", + "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "clean-webpack-plugin": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-1.0.0.tgz", + "integrity": "sha512-+f96f52UIET4tOFBbCqezx7KH+w7lz/p4fA1FEjf0hC6ugxqwZedBtENzekN2FnmoTF/bn1LrlkvebOsDZuXKw==", + "dev": true, + "requires": { + "rimraf": "^2.6.1" + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.0.tgz", + "integrity": "sha512-CwyopLkuRYO5ei2EpzpIh6LqJMt6Mt+jZhO5VI5f/wJLZriXQE32/SSqzmrh+QB+AZT81Cj8yv+7zwToW8ahZg==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "commonmark": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.0.tgz", + "integrity": "sha512-Wc3kvAIm0EK85pHsM95Fev31wEN6/zQpwd2qcLDL8psjHRoUFvUeGHevIJAdToWUuFoX8WI/gmeDauqy32xgJQ==", + "requires": { + "entities": "~ 1.1.1", + "mdurl": "~ 1.0.1", + "minimist": "~ 1.2.0", + "string.prototype.repeat": "^0.2.0" + } + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "concurrently": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.0.tgz", + "integrity": "sha512-pwzXCE7qtOB346LyO9eFWpkFJVO3JQZ/qU/feGeaAHiX1M3Rw3zgXKc5cZ8vSH5DGygkjzLFDzA/pwoQDkRNGg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "date-fns": "^1.23.0", + "lodash": "^4.17.10", + "read-pkg": "^4.0.1", + "rxjs": "^6.3.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^4.5.0", + "tree-kill": "^1.1.0", + "yargs": "^12.0.1" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } + } + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz", + "integrity": "sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + }, + "dependencies": { + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + } + } + }, + "core-js": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.2.tgz", + "integrity": "sha512-NdBPF/RVwPW6jr0NCILuyN9RiqLo2b1mddWHkUL+VnvcB7dzlnBJ1bXYntjpTGOgkZiiLWj2JxmOr7eGE3qK6g==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", + "dev": true + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-loader": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.0.1.tgz", + "integrity": "sha512-XIVwoIOzSFRVsafOKa060GJ/A70c0IP/C1oVPHEX4eHIFF39z0Jl7j8Kua1SUTiqWDupUnbY3/yQx9r7EUB35w==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "loader-utils": "^1.0.2", + "lodash": "^4.17.11", + "postcss": "^7.0.6", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^2.0.2", + "postcss-modules-scope": "^2.0.0", + "postcss-modules-values": "^2.0.0", + "postcss-value-parser": "^3.3.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-selector-tokenizer": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "http://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "css-what": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz", + "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==", + "dev": true + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, + "d3-color": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.3.tgz", + "integrity": "sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw==", + "dev": true + }, + "d3-interpolate": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", + "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", + "dev": true, + "requires": { + "d3-color": "1" + } + }, + "d3-time": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz", + "integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw==" + }, + "d3-time-format": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", + "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", + "requires": { + "d3-time": "1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "dat.gui": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/dat.gui/-/dat.gui-0.7.6.tgz", + "integrity": "sha512-9Uqr4aQUvp9q5P2b4y6gK604HXafubOq578OmOS8mjrIkYrBP4EbQ9gz9YRXgyPh7aQi+b9H/jAG7EucmhYpSA==", + "dev": true + }, + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dir-glob": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.1.tgz", + "integrity": "sha512-UN6X6XwRjllabfRhBdkVSo63uurJ8nSvMGrwl94EYVz6g+exhTV+yVSYk5VC/xl3MBFBTtC0J20uFKce4Brrng==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + } + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "~1.1.1", + "entities": "~1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", + "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "duplexify": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", + "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "earcut": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.1.4.tgz", + "integrity": "sha512-ttRjmPD5oaTtXOoxhFp9aZvMB14kBjapYaiBuzBB1elOgSLU9P2Ev86G2OClBg+uspUXERsIzXKpUWweH2K4Xg==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ecstatic": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", + "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", + "dev": true, + "requires": { + "he": "^1.1.1", + "mime": "^1.6.0", + "minimist": "^1.1.0", + "url-join": "^2.0.5" + } + }, + "electron-to-chromium": { + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.95.tgz", + "integrity": "sha512-0JZEDKOQAE05EO/4rk3vLAE+PYFI9OLCVLAS4QAq1y+Bb2y1N6MyQJz62ynzHN/y0Ka/nO5jVJcahbCEdfiXLQ==", + "dev": true + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io-client": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz", + "integrity": "sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "dev": true, + "requires": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + } + } + }, + "eslint-config-google": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.12.0.tgz", + "integrity": "sha512-SHDM3nIRCJBACjf8c/H6FvCwRmKbphESNl3gJFBNbw4KYDLCONB3ABYLXDGF+iaVP9XSTND/Q5/PuGoFkp4xbg==", + "dev": true + }, + "eslint-plugin-lit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.0.0.tgz", + "integrity": "sha512-jFD2t6iPvOlqMTau6zssszICJQSBm41dbuxeENmjItA6YeSBgmig7R1lFncMegkRE3bspj9fRM+e6+OIWQMQkQ==", + "requires": { + "parse5": "^5.1.0", + "parse5-htmlparser2-tree-adapter": "^5.1.0", + "requireindex": "^1.2.0" + } + }, + "eslint-plugin-prettier": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.1.tgz", + "integrity": "sha512-/PMttrarPAY78PLvV3xfWibMOdMDl57hmlQ2XqFeA37wd+CJ7WSxV7txqjVPHi/AAFKd2lX0ZqfsOc/i5yFCSQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "eventemitter3": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fft-js": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/fft-js/-/fft-js-0.0.11.tgz", + "integrity": "sha1-NONcNlCIFaobhvNAoOeshbjKa9U=", + "requires": { + "bit-twiddle": "~1.0.2", + "commander": "~2.7.1" + }, + "dependencies": { + "commander": { + "version": "2.7.1", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.7.1.tgz", + "integrity": "sha1-XUGaK77Swy7j5Nypu0Wrg+zDBlo=", + "requires": { + "graceful-readlink": ">= 1.0.0" + } + } + } + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "follow-redirects": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", + "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "dev": true, + "requires": { + "debug": "^3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "aproba": { + "version": "1.2.0", + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "resolved": false, + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "resolved": false, + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "resolved": false, + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "resolved": false, + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": false, + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "resolved": false, + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "minipass": { + "version": "2.2.4", + "resolved": false, + "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "resolved": false, + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "resolved": false, + "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "resolved": false, + "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "resolved": false, + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "resolved": false, + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "resolved": false, + "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": false, + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": false, + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "resolved": false, + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "resolved": false, + "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "resolved": false, + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yallist": { + "version": "3.0.2", + "resolved": false, + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "dev": true + } + } + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "requires": { + "globule": "^1.0.0" + } + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global-modules-path": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.3.1.tgz", + "integrity": "sha512-y+shkf4InI7mPRHSo2b/k6ix6+NLDtyccYv86whhxrSGX9wjPX1VMITmrDbE1eh7zkzhiWtW2sHklJYoQ62Cxg==", + "dev": true + }, + "globals": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", + "dev": true + }, + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "dev": true, + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + } + }, + "html-webpack-plugin": { + "version": "3.2.0", + "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", + "dev": true, + "requires": { + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", + "util.promisify": "1.0.0" + }, + "dependencies": { + "json5": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } + } + }, + "htmlparser2": { + "version": "3.3.0", + "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.1", + "domutils": "1.1", + "readable-stream": "1.0" + }, + "dependencies": { + "domutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", + "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", + "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-server": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.11.1.tgz", + "integrity": "sha512-6JeGDGoujJLmhjiRGlt8yK8Z9Kl0vnl/dQoQZlc4oeqaUoAKQg94NILLfrY3oWzSyFaQCVNTcKE5PZ3cH8VP9w==", + "dev": true, + "requires": { + "colors": "1.0.3", + "corser": "~2.0.0", + "ecstatic": "^3.0.0", + "http-proxy": "^1.8.1", + "opener": "~1.4.0", + "optimist": "0.6.x", + "portfinder": "^1.0.13", + "union": "~0.4.3" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "icss-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.0.0.tgz", + "integrity": "sha512-bA/xGiwWM17qjllIs9X/y0EjsB7e0AV08F3OL8UPsoNkNRibIuu8f1eKTnQ8QO1DteKKTxTUAn+IEWUToIwGOA==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "ismobilejs": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-0.5.1.tgz", + "integrity": "sha512-QX4STsOcBYqlTjVGuAdP1MiRVxtiUbRHOKH0v7Gn1EvfUVIQnrSdgCM4zB4VCZuIejnb2NUMUx0Bwd3EIG6yyA==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "js-base64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.0.tgz", + "integrity": "sha512-wlEBIZ5LP8usDylWbDNhKPEFVFdI5hCHpnVoT/Ysvoi/PRhJENm/Rlh9TvjYB38HFfKZN7OzEbRjmjvLkFw11g==", + "dev": true + }, + "js-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.4.tgz", + "integrity": "sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow==", + "dev": true + }, + "js-longest-repeated-substring": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/js-longest-repeated-substring/-/js-longest-repeated-substring-1.0.3.tgz", + "integrity": "sha1-vMvDZ7zDFehpAja9J9sxdgYj8EQ=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lit-element": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.1.0.tgz", + "integrity": "sha512-0z/KHm1xZweivfOVRr8AKR06+D3k02u15m9s4jkuRdnGe5wfmEwePzrQQBsSZNILdnfJvfo3TJOeGhBCVZaPbw==", + "requires": { + "lit-html": "^1.0.0" + }, + "dependencies": { + "lit-html": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.0.0.tgz", + "integrity": "sha512-oeWlpLmBW3gFl7979Wol2LKITpmKTUFNn7PnFbh6YNynF61W74l6x5WhwItAwPRSATpexaX1egNnRzlN4GOtfQ==" + } + } + }, + "lit-html": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.1.0.tgz", + "integrity": "sha512-ZDJHpJi09yknMpjwPI8fuSl5sUG7+pF+eE5WciFtgyX7zebvgMDBgSLq4knXa7grxM00RkQ7PBd7UZQiruA78Q==", + "dev": true + }, + "livereload-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "loader-runner": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.1.tgz", + "integrity": "sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw==", + "dev": true + }, + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" + }, + "dependencies": { + "json5": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.mergewith": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", + "dev": true + }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, + "mem": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", + "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^1.1.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "mini-signals": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mini-signals/-/mini-signals-1.2.0.tgz", + "integrity": "sha1-RbCAE8X65RokqhqTXNMXye1yHXQ=", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", + "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "node-interval-tree": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-interval-tree/-/node-interval-tree-1.3.3.tgz", + "integrity": "sha512-K9vk96HdTK5fEipJwxSvIIqwTqr4e3HRJeJrNxBSeVMNSC/JWARRaX7etOLOuTmrRMeOI/K5TCJu3aWIwZiNTw==", + "dev": true, + "requires": { + "shallowequal": "^1.0.2" + } + }, + "node-libs-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", + "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.0", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.10.3", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-releases": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.2.tgz", + "integrity": "sha512-j1gEV/zX821yxdWp/1vBMN0pSUjuH9oGUdLCb4PfUko6ZW7KdRs3Z+QGGwDUhYtSpQvdVVyLd2V0YvLsmdg5jQ==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "node-sass": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", + "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", + "dev": true, + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash.assign": "^4.2.0", + "lodash.clonedeep": "^4.3.2", + "lodash.mergewith": "^4.6.0", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.10.0", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "ooura": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/ooura/-/ooura-2.1.6.tgz", + "integrity": "sha512-FsO5M1o7zS9mbiaIje/s8THCixKSeFGkwQDwm6ZfrqkuOv2x52CZHNHldwWxpH1QZcWmD7HigiSSYh53puxMJQ==" + }, + "opener": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", + "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=", + "dev": true + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", + "dev": true, + "requires": { + "execa": "^0.10.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pako": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz", + "integrity": "sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ==", + "dev": true + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "parse-asn1": { + "version": "5.1.1", + "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse-uri": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-uri/-/parse-uri-1.0.0.tgz", + "integrity": "sha1-KHLcwi8aeXrN4Vg9igrClVLdrCA=", + "dev": true + }, + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" + }, + "parse5-htmlparser2-tree-adapter": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-5.1.0.tgz", + "integrity": "sha512-OrI4DNmghGcwDB3XN8FKKN7g5vBmau91uqj+VYuwuj/r6GhFBMBNymsM+Z9z+Z1p4HHgI0UuQirQRgh3W5d88g==", + "requires": { + "parse5": "^5.1.0" + } + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pixi-gl-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pixi-gl-core/-/pixi-gl-core-1.1.4.tgz", + "integrity": "sha1-i0tcQzsx5Bm8N53FZc4bg1qRs3I=", + "dev": true + }, + "pixi.js": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-4.8.5.tgz", + "integrity": "sha512-eYGvuzIOAOepoFDHu3GmvRqM2tJ/IBwHnfxB6BxGxDOQOaNfWMT9LA5IlUY9K3YEnEd3YtD0Nsx5aRxCyzYQeQ==", + "dev": true, + "requires": { + "bit-twiddle": "^1.0.2", + "earcut": "^2.1.4", + "eventemitter3": "^2.0.0", + "ismobilejs": "^0.5.1", + "object-assign": "^4.0.1", + "pixi-gl-core": "^1.1.4", + "remove-array-items": "^1.0.0", + "resource-loader": "^2.2.3" + }, + "dependencies": { + "eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=", + "dev": true + } + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "portfinder": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", + "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.2.tgz", + "integrity": "sha512-qghHvHeydUBQ3EQic5NjYryZ5jzXzAYxHR7lZQlCNmjGpJtINRyX/ELnh/7fxBBmHNkEzNkq2l5cV6trfidYng==", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^7.0.6", + "postcss-value-parser": "^3.3.1" + } + }, + "postcss-modules-scope": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.0.1.tgz", + "integrity": "sha512-7+6k9c3/AuZ5c596LJx9n923A/j3nF3ormewYBF1RrIQvjvjXe1xE8V8A1KFyFwXbvnshT6FBZFX0k/F1igneg==", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^7.0.6" + } + }, + "postcss-modules-values": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz", + "integrity": "sha512-Ki7JZa7ff1N3EIMlPnGTZfUMe69FFwiQPnVSXC9mnn3jozCRBYIxiZd44yJOV2AmabOo4qFf8s0dC/+lweG7+w==", + "dev": true, + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^7.0.6" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prettier": { + "version": "1.16.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", + "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", + "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "dev": true, + "requires": { + "bytes": "1", + "string_decoder": "0.10" + }, + "dependencies": { + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "dev": true, + "requires": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz", + "integrity": "sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + }, + "regenerator-transform": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.3.tgz", + "integrity": "sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==", + "dev": true, + "requires": { + "private": "^0.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true + }, + "regexpu-core": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.4.0.tgz", + "integrity": "sha512-eDDWElbwwI3K0Lo6CqbQbA6FwgtCz4kYTarrri1okfkRLZAqstU+B3voZBCjg8Fl6iq0gXrJG6MvRgLthfvgOA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^7.0.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.0.2" + } + }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", + "dev": true + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-array-items": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/remove-array-items/-/remove-array-items-1.1.1.tgz", + "integrity": "sha512-MXW/jtHyl5F1PZI7NbpS8SOtympdLuF20aoWJT5lELR1p/HJDd5nqW8Eu9uLh/hCRY3FgvrIT5AwDCgBODklcA==", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "renderkid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.2.tgz", + "integrity": "sha512-FsygIxevi1jSiPY9h7vZmBFUbAOcbYm9UwyiLNdVsLRs/5We9Ob5NMPbGYUTWiLq5L+ezlVdE0A8bbME5CWTpg==", + "dev": true, + "requires": { + "css-select": "^1.1.0", + "dom-converter": "~0.2", + "htmlparser2": "~3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + } + } + }, + "requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", + "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "resource-loader": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/resource-loader/-/resource-loader-2.2.3.tgz", + "integrity": "sha512-SsxilncV7gxwr12clSq0E2HZh8QjcosVVSEAnZ3VF9VtBNNtO3UFxxXT9pJgkVEJUHAlXg26MkkTOMjyz1kDdw==", + "dev": true, + "requires": { + "mini-signals": "^1.1.1", + "parse-uri": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rotate-matrix": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/rotate-matrix/-/rotate-matrix-0.0.3.tgz", + "integrity": "sha1-0diiJBIzofqhnwYE7nlZcarLE6A=", + "dev": true + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "*" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + } + } + } + }, + "sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "dev": true, + "requires": { + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0", + "semver": "^5.5.0" + } + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "serialize-javascript": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", + "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io-client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.2.0.tgz", + "integrity": "sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==", + "dev": true, + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.3.1", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + } + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz", + "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.repeat": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", + "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "style-loader": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", + "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + } + } + }, + "tapable": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", + "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==", + "dev": true + }, + "tar": { + "version": "2.2.1", + "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "terser": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.12.0.tgz", + "integrity": "sha512-Jx2XedVgku7U1eLvEsl8UD9wa9AT6sjcjAu1h1cggY7jzRZuyLM0cWsKIZNXNltM5jmUNOprFxQoIf5Y9/1vOA==", + "dev": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1", + "source-map-support": "~0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.1.0.tgz", + "integrity": "sha512-61lV0DSxMAZ8AyZG7/A4a3UPlrbOBo8NIQ4tJzLPAdGOQ+yoNC7l5ijEow27lBAL2humer01KLS6bGIMYQxKoA==", + "dev": true, + "requires": { + "cacache": "^11.0.2", + "find-cache-dir": "^2.0.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "terser": "^3.8.1", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", + "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "threads": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/threads/-/threads-0.12.0.tgz", + "integrity": "sha512-4B7hd61lDsVW1Z/+FAVX7D9QbiQYUbtGMHVkkwWT/nKPKas8u4FEc+Rg8E8h2erhNTQGNqNJ0TsholmhpKNPRg==", + "dev": true, + "requires": { + "eventemitter3": "^2.0.2", + "native-promise-only": "^0.8.1" + }, + "dependencies": { + "eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=", + "dev": true + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "requires": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" + }, + "dependencies": { + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, + "tinycolor2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tone": { + "version": "13.6.1", + "resolved": "https://registry.npmjs.org/tone/-/tone-13.6.1.tgz", + "integrity": "sha512-Setu9o8GSHb3w8QgPCtq5tgs+ImUiw4obBCgcbldbkz8nxqePK7VflIfxUM1NBSHwI6cSdLyVf2MWcvWrl9q/g==" + }, + "toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "requires": { + "glob": "^7.1.2" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz", + "integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz", + "integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==", + "dev": true + }, + "union": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/union/-/union-0.4.6.tgz", + "integrity": "sha1-GY+9rrolTniLDvy2MLwR8kopWeA=", + "dev": true, + "requires": { + "qs": "~2.3.3" + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", + "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-join": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", + "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz", + "integrity": "sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "webpack": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.28.0.tgz", + "integrity": "sha512-gPNTMGR5ZlBucXmEQ34TRxRqXnGYq9P3t8LeP9rvhkNnr+Cn+HvZMxGuJ4Hl7zdmoRUZP+GosniqJiadXW/RqQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/wasm-edit": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "acorn": "^5.6.2", + "acorn-dynamic-import": "^3.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.1.0", + "terser-webpack-plugin": "^1.1.0", + "watchpack": "^1.5.0", + "webpack-sources": "^1.3.0" + } + }, + "webpack-cli": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.2.tgz", + "integrity": "sha512-Cnqo7CeqeSvC6PTdts+dywNi5CRlIPbLx1AoUPK2T6vC1YAugMG3IOoO9DmEscd+Dghw7uRlnzV1KwOe5IrtgQ==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.0", + "global-modules-path": "^2.3.0", + "import-local": "^2.0.0", + "interpret": "^1.1.0", + "loader-utils": "^1.1.0", + "supports-color": "^5.5.0", + "v8-compile-cache": "^2.0.2", + "yargs": "^12.0.2" + } + }, + "webpack-livereload-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webpack-livereload-plugin/-/webpack-livereload-plugin-2.2.0.tgz", + "integrity": "sha512-sx9xA5mHoNOUgLQI0PmXT3KV9ecsVmUaTgr+fsoL69qAOHw/7VzkL1+ZMDQ8n0dPbWounswK6cBRSgMod7Nhgg==", + "dev": true, + "requires": { + "portfinder": "^1.0.17", + "tiny-lr": "^1.1.1" + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "websocket-driver": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "worker-farm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", + "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "worker-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz", + "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==", + "dev": true, + "requires": { + "loader-utils": "^1.0.0", + "schema-utils": "^0.4.0" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + } + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..80d21c1 --- /dev/null +++ b/client/package.json @@ -0,0 +1,68 @@ +{ + "name": "pattern-radio", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "watch": "concurrently \"webpack -w --mode=development\" \"http-server\"", + "build": "webpack", + "serve": "http-server" + }, + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@babel/polyfill": "^7.2.5", + "@pixi/filter-glow": "^2.7.0", + "@polymer/polymer": "^3.1.0", + "@tweenjs/tween.js": "^17.3.0", + "@webcomponents/webcomponentsjs": "^2.2.3", + "babel-polyfill": "^6.26.0", + "color": "^3.1.0", + "commonmark": "^0.29.0", + "d3-time": "^1.0.11", + "d3-time-format": "^2.1.3", + "eslint-plugin-lit": "^1.0.0", + "fft-js": "0.0.11", + "lit-element": "^2.1.0", + "ooura": "^2.1.6", + "tone": "^13.6.1" + }, + "devDependencies": { + "@babel/core": "^7.2.2", + "@babel/preset-env": "^7.2.0", + "@pixi/core": "^5.0.0-rc.3", + "@pixi/filter-adjustment": "^2.7.0", + "@pixi/filter-color-map": "^2.7.0", + "asciichart": "^1.5.7", + "audiobuffer-to-wav": "^1.0.0", + "babel-loader": "^8.0.4", + "clean-webpack-plugin": "^1.0.0", + "concurrently": "^4.1.0", + "copy-webpack-plugin": "^4.6.0", + "core-js": "^2.6.2", + "css-loader": "^2.0.1", + "d3-interpolate": "^1.3.2", + "dat.gui": "^0.7.6", + "eslint": "4", + "eslint-config-google": "^0.12.0", + "eslint-plugin-prettier": "^3.0.1", + "html-webpack-plugin": "^3.2.0", + "http-server": "^0.11.1", + "js-longest-repeated-substring": "^1.0.3", + "lit-html": "^1.1.0", + "node-interval-tree": "^1.3.3", + "node-sass": "^4.11.0", + "pixi.js": "^4.8.5", + "prettier": "^1.16.4", + "rotate-matrix": "0.0.3", + "sass-loader": "^7.1.0", + "socket.io-client": "^2.2.0", + "style-loader": "^0.23.1", + "threads": "^0.12.0", + "tinycolor2": "^1.4.1", + "webpack": "^4.28.0", + "webpack-cli": "^3.1.2", + "webpack-livereload-plugin": "^2.2.0", + "worker-loader": "^2.0.0" + } +} diff --git a/client/src/components/AnnotationLayer.js b/client/src/components/AnnotationLayer.js new file mode 100644 index 0000000..76722a1 --- /dev/null +++ b/client/src/components/AnnotationLayer.js @@ -0,0 +1,757 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Sprite, Container, Texture, Graphics, Circle, BLEND_MODES } from 'pixi.js' +import { GlowFilter } from '@pixi/filter-glow' +import * as TWEEN from '@tweenjs/tween.js' +import { Globals, Config } from '../globals' +import { hexToRGB, rgbToNum } from '../util/Math' +import { getAnnotations } from '../network/Annotations' + +const strokeWeight = 8 +const commentPadding = 3 +const commentLayerHeight = strokeWeight + commentPadding * 2 +const blue = 0xbbffff +const gray = 0x333333 +let roundedEdgeLeft +const shortTimeTransition = 110 +const highlightAlpha = 0.3 + +export class AnnotationLayer extends Container { + constructor(spectrogram, commentEl, infoBubble) { + super() + this.spectrogram = spectrogram + this.comment = commentEl + this.infoBubble = infoBubble + this.annotationsData = null + this.commentsContainer = new Container() + this.commentsContainer.y = -commentPadding * 3 + this.addChild(this.commentsContainer) + this.commentGroupsContainer = new Container() + this.addChild(this.commentGroupsContainer) + this.selectedSpriteIndex = null + this.highlightedIndex = null + this.hoveredComment = null + this.disabled = true + this.commentNavSkip = 0 + const highlight = new Sprite(Texture.WHITE) + highlight.tint = 0xbbffff + highlight.blendMode = BLEND_MODES.ADD + highlight.alpha = highlightAlpha + highlight.visible = false + highlight.alpha = 0 + this.highlight = highlight + this.addChild(this.highlight) + const leftEdgeGraphic = new Graphics() + leftEdgeGraphic + .beginFill(0xffffff) + .arc(0, 0, strokeWeight, Math.PI / 2, -Math.PI / 2) + .endFill() + roundedEdgeLeft = Globals.pixiApp.renderer.generateTexture(leftEdgeGraphic) + this.commentGroups = [] + this.settling = false + this.positioning = null + this.firstCommentSelectable = true + this.lastCommentSelectable = true + this.comment.addEventListener('authorChange', (e) => { + Globals.controls.tweens.position.stop() + Globals.controls.tweens.duration.stop() + this.positioning = null + this.commentNavSkip = 0 + if (e.detail !== null) { + this.disabled = false + this._filterComments(e.detail.name) + } else { + this.disabled = true + this.commentsContainer.children.forEach((otherSprite) => { + otherSprite.tint = gray + }) + this._hideHighlight() + this.selectedSpriteIndex = null + setTimeout(() => { + this._filterComments() + }, shortTimeTransition) + } + }) + this.comment.addEventListener('nextComment', () => { + this.nextComment() + }) + this.comment.addEventListener('prevComment', () => { + this.previousComment() + }) + Globals.controls.$$canvas.addEventListener('mousemove', (e) => { + if (this.hoveredComment) { + this.infoBubble.coords = { + x: e.clientX - this.infoBubble.$$container.offsetWidth / 2, + y: this.hoveredComment.worldTransform.ty - this.infoBubble.containerHeight - commentLayerHeight / 2, + } + } + }) + Globals.events.on('zoom', this.updateGroups.bind(this)) + this.highlightTween = new TWEEN.Tween(this.highlight) + } + + set location(location) { + this._location = location + this._filterComments() + } + + get location() { + return this._location + } + + _filterComments(filter = false) { + let selectedIndex = null + let annotations = this.annotationsData[this.location.name].slice().sort((a, b) => { + return a.timeStart - b.timeStart + }) + // filtering by author name + if (filter) { + annotations = annotations.filter((annotation) => annotation.author === filter) + if (!annotations.length) { + console.error('No annotations for ', filter) + return + } + // start at the first comment by default for a selected author + selectedIndex = 0 + } + if (this.hoveredComment) { + this.hoveredComment = null + this.infoBubble.author = null + } + AnnotationLayer.emptyContainer(this.commentsContainer) + // populate annotations based on the data for this location + this.yLevels = [0] + const commentGroups = [] + + this.filteredAnnotations = annotations + annotations.forEach((annotation, i) => { + const sprite = new CommentSprite(this, annotation, i) + // determine what y-position level to put this comment on (above the spectrogram) + for (let level = 0; level < this.yLevels.length; level++) { + if (this.yLevels[level] <= sprite.data.timeStart - 1000) { + sprite.yLevel = level + this.yLevels[level] = sprite.data.timeEnd + break + } + } + if (sprite.yLevel === null) { + sprite.yLevel = this.yLevels.length + this.yLevels.push(sprite.data.timeEnd) + } + this.commentsContainer.addChild(sprite) + // add to comment overlap groups + if (i) { + const group = commentGroups[commentGroups.length - 1] + if (sprite.data.timeStart <= group.end) { + group.addComment(sprite) + } else { + commentGroups.push(new CommentGroup(this, sprite)) + } + } else { + commentGroups.push(new CommentGroup(this, sprite)) + } + }) + this.commentGroups = [] + AnnotationLayer.emptyContainer(this.commentGroupsContainer) + commentGroups.forEach((group) => { + if (group.comments.length > 1) { + group.initialize(this.commentGroupsContainer, this.commentGroups) + } + }) + this.updateGroups() + this.commentsContainer.children.forEach((sprite) => { + sprite.y = (this.yLevels.length - sprite.yLevel) * commentLayerHeight - strokeWeight + }) + const yPos = this.yLevels.length * commentLayerHeight + this.highlight.y = yPos + this.commentGroupsContainer.y = yPos - commentPadding * 2 + // handle selection + if (selectedIndex !== null) { + this._navigateToComment(selectedIndex) + } + } + + async load() { + this.annotationsData = await getAnnotations() + } + + update() { + this._updateYPosition() + this._updateSpritePositions() + } + + _updateYPosition() { + this.y = (window.innerHeight - this.spectrogram.height) / 2 - this.yLevels.length * commentLayerHeight + } + + _updateSpritePositions() { + const windowDuration = Globals.timeManager.windowDuration + const time = Globals.timeManager.currentTime + if (!windowDuration || !this.commentsContainer.children.length) return + const windowStart = Globals.timeManager.windowStartTime + const windowEnd = Globals.timeManager.windowEndTime + let commentSelected = false + // update individual sprites + this.commentsContainer.children.forEach((sprite, spriteIndex) => { + // if sprite is onscreen, and not consolidated into a group, set position + // and check if it should be showing in the pinned element + if ( + (!sprite.group || !sprite.group.consolidated) && + sprite.data.timeStart < windowEnd && + sprite.data.timeEnd > windowStart + ) { + sprite.visible = true + // position and size + sprite.update() + // check if comment involves the current time + // and set the currently selected sprite + if (sprite.data.timeStart <= time && sprite.data.timeEnd >= time) { + if (!sprite.selectable) { + if (!this.settling && this.positioning === null) { + this._selectComment(sprite, spriteIndex) + } + sprite.selectable = true + } + } else { + if (sprite.selectable) { + sprite.selectable = false + if (this.selectedSpriteIndex === spriteIndex && this.positioning === null) { + // find last (latest) comment that is selectable + for (let i = this.commentsContainer.children.length - 1; i >= 0; i--) { + const selectedSprite = this.commentsContainer.getChildAt(i) + if (selectedSprite.selectable) { + this._selectComment(selectedSprite, i) + break + } + } + } + } + } + if (sprite.selectable && !commentSelected) { + commentSelected = true + } + } else { + sprite.visible = false + sprite.selectable = false + } + }) + // update sprite overlap groups + this.commentGroups.forEach((group) => { + if ( + group.consolidated && + group.median < windowEnd + strokeWeight / 2 && + group.median > windowStart - strokeWeight / 2 + ) { + group.graphic.visible = true + group.updatePosition() + // check if group involves the current time + // and set the currently selected sprite + // const firstComment = group.comments[0] + // if (group.start <= time && group.end > = time) { + // if (!group.selected && this.positioning === null) { + // this._selectComment(firstComment, firstComment.index) + // group.selected = true + // } + // } else { + // group.selected = false + // } + // if (group.selected && !commentSelected) { + // commentSelected = true + // } + } else { + group.selected = false + group.graphic.visible = false + } + }) + // close the comment if it is open but no annotation is selected + if (!commentSelected && this.comment.annotation !== null) { + this.comment.annotation = null + this._hideHighlight() + if (!this.disabled && this.positioning === null) { + this.commentsContainer.children.forEach((otherSprite) => { + otherSprite.tint = blue + }) + this.commentGroups.forEach((group) => { + group.tint = blue + }) + } + this.selectedSpriteIndex = null + } + // position highlight + if (this.highlightedIndex !== null && this.positioning === null) { + const selectedSprite = this.commentsContainer.getChildAt(this.highlightedIndex) + this.highlight.x = selectedSprite.x + commentPadding + this.highlight.width = selectedSprite.middle.width + strokeWeight + this.highlight.height = this.spectrogram.height + } + // handle comment selectability for the < and > icons in the Comment element + this.firstCommentSelectable = + Globals.timeManager.currentTime > this.commentsContainer.getChildAt(0).data.timeStart && + ((this.selectedSpriteIndex === 0 && this.positioning !== null) || this.selectedSpriteIndex !== 0) + const lastCommentIndex = this.commentsContainer.children.length - 1 + this.lastCommentSelectable = + Globals.timeManager.currentTime < this.commentsContainer.getChildAt(lastCommentIndex).data.timeEnd && + ((this.selectedSpriteIndex === lastCommentIndex && this.positioning !== null) || + this.selectedSpriteIndex !== lastCommentIndex) + if (this.comment.firstCommentSelectable !== this.firstCommentSelectable) { + this.comment.firstCommentSelectable = this.firstCommentSelectable + } + if (this.comment.lastCommentSelectable !== this.lastCommentSelectable) { + this.comment.lastCommentSelectable = this.lastCommentSelectable + } + this.settling = false + } + + _selectComment(sprite, index) { + if (this.disabled) return + this.comment.annotation = sprite.data + this.selectedSpriteIndex = index + if (!sprite.group || !sprite.group.consolidated) { + this._showHighlight() + } else { + this._hideHighlight() + } + sprite.tint = blue + if (sprite.group) { + sprite.group.tint = blue + } + this.commentsContainer.children.forEach((otherSprite, i) => { + if (i !== index) { + otherSprite.tint = gray + } + }) + this.commentGroups.forEach((group) => { + if (group !== sprite.group) { + group.tint = gray + } + }) + this.settling = true + } + + navigateToCommentAnchor(anchor) { + const annotation = this.filteredAnnotations.findIndex((x) => x.anchor == anchor) + if (annotation !== -1) { + this._navigateToComment(annotation) + } else { + console.error('Anchor ' + anchor + ' not found') + } + } + + _navigateToComment(index) { + if (this.disabled) return + this.selectedSpriteIndex = index + const sprite = this.commentsContainer.getChildAt(index) + + this._hideHighlight() + this.positioning = sprite + + Globals.controls.navigateTo(sprite.data.timeStart, AnnotationLayer.getIdealWindowDuration(sprite), () => { + if (this.positioning !== null) { + this._selectComment(this.positioning, this.positioning.index) + } + this.positioning = null + this.commentNavSkip = 0 + + this.updateGroups() + }) + } + + nextComment() { + if (this.positioning !== null) { + this.commentNavSkip++ + } + let nextIndex = this.commentsContainer.children.findIndex((sprite) => { + return sprite.data.timeStart > Globals.timeManager.currentTime + }) + if (nextIndex !== -1) { + nextIndex += this.commentNavSkip + if (nextIndex >= 0 && nextIndex < this.commentsContainer.children.length) { + this._navigateToComment(nextIndex) + } + } + } + + previousComment() { + if (this.positioning !== null) { + this.commentNavSkip-- + } + let prevIndex = + this.commentsContainer.children.length - + 1 - + this.commentsContainer.children + .slice() + .reverse() + .findIndex((sprite) => { + return sprite.data.timeStart < Globals.timeManager.currentTime + }) + if (prevIndex !== -1) { + prevIndex += this.commentNavSkip + if (prevIndex && prevIndex === this.selectedSpriteIndex) { + prevIndex-- + } + if (prevIndex >= 0 && prevIndex < this.commentsContainer.children.length) { + this._navigateToComment(prevIndex) + } + } + } + + updateGroups() { + if (!this.spectrogram.windowDuration || !this.annotationsData || !this.commentGroups.length) return + const minGroupWidth = strokeWeight * 3 + this.commentGroups.forEach((group) => { + const pixelDuration = Globals.timeManager.durationToPx(group.start, group.end) + if (!group.consolidated && pixelDuration < minGroupWidth) { + group.consolidated = true + } else if (group.consolidated && pixelDuration >= minGroupWidth) { + group.consolidated = false + } + }) + } + + static emptyContainer(container) { + container.children.forEach((child) => { + child.filters = null + Object.values(child.tweener.tweens).forEach((tween) => { + tween.stop() + TWEEN.remove(tween) + }) + child.destroy({ children: true }) + }) + container.removeChildren() + } + + spritePointerDown(sprite) { + if (this.disabled) { + setTimeout(() => { + Globals.controls.scrub.dragStop() + }, 100) + this.comment.startTour(AnnotationLayer.getAuthorID(sprite.data.author)) + } else { + this._navigateToComment(sprite.index) + } + } + + spriteMouseOver(e, obj, sprite = false, filterObj = false) { + sprite = sprite || obj + filterObj = filterObj || obj + if (this.disabled) { + this.commentsContainer.children.forEach((otherSprite) => { + otherSprite.tint = otherSprite.data.author === sprite.data.author ? blue : gray + }) + } + + obj.glowFilter.color = obj.tint + filterObj.filters = [obj.glowFilter] + obj.tweens.glow + .stop() + .to({ outerStrength: filterConfig.outerStrength }, shortTimeTransition) + .onComplete(() => {}) + .start() + this.hoveredComment = filterObj + this.infoBubble.author = AnnotationLayer.getAuthorID(sprite.data.author) + this.infoBubble.coords = { + x: e.data.global.x - this.infoBubble.$$container.offsetWidth / 2, + y: sprite.worldTransform.ty - this.infoBubble.containerHeight - commentLayerHeight / 2, + } + } + + spriteMouseOut(obj, filterObj = false) { + filterObj = filterObj || obj + + obj.tweens.glow + .stop() + .to({ outerStrength: 0 }, shortTimeTransition) + .onComplete(() => { + filterObj.filters = null + }) + .start() + if (this.hoveredComment === filterObj) { + if (this.disabled) { + this.commentsContainer.children.forEach((otherSprite) => { + otherSprite.tint = gray + }) + } + this.hoveredComment = null + this.infoBubble.author = null + } + } + + _showHighlight() { + this.highlightedIndex = this.selectedSpriteIndex + this.highlightTween + .stop() + .to({ alpha: highlightAlpha }, shortTimeTransition) + .onStart(() => { + this.highlight.visible = true + }) + .onComplete(() => {}) + .start() + } + + _hideHighlight() { + this.highlightTween + .stop() + .to({ alpha: 0 }, shortTimeTransition) + .onStart(() => {}) + .onComplete(() => { + this.highlightedIndex = null + this.highlight.visible = false + }) + .start() + } + + static getAuthorID(name) { + return Object.values(Config.authors).find((authorObj) => { + return authorObj.name === name + }).id + } + + static getIdealWindowDuration(sprite) { + const windowPctOfComment = 2.5 + return (sprite.data.duration * windowPctOfComment * Config.tileWidth) / window.innerWidth + } +} + +const filterConfig = { + distance: 8, + outerStrength: 1.5, + quality: 0.75, +} + +class CommentSprite extends Container { + constructor(annotationLayer, annotation, index) { + super() + this.annotationLayer = annotationLayer + this.data = annotation + this.index = index + const middle = new Sprite(Texture.WHITE) + middle.height = strokeWeight + middle.x = strokeWeight / 2 + commentPadding + middle.y = commentPadding + this.middle = middle + this.addChild(this.middle) + const bg = new Sprite(Texture.EMPTY) + bg.renderable = false + bg.height = commentLayerHeight + this.bg = bg + this.addChild(this.bg) + this.selectable = false + this.yLevel = null + this.group = null + this.visible = false + this.interactive = true + this.cursor = 'pointer' + this.on('pointerdown', () => { + this.annotationLayer.spritePointerDown(this) + }) + this.on('mouseover', (e) => { + this.annotationLayer.spriteMouseOver(e, this) + }) + this.on('mouseout', () => { + this.annotationLayer.spriteMouseOut(this) + }) + const leftEdge = new Sprite(roundedEdgeLeft) + leftEdge.width = strokeWeight / 2 + leftEdge.height = strokeWeight + leftEdge.x = leftEdge.y = commentPadding + this.leftEdge = leftEdge + const rightEdge = new Sprite(roundedEdgeLeft) + rightEdge.scale.x = -1 + rightEdge.y = commentPadding + rightEdge.width = strokeWeight / 2 + rightEdge.height = strokeWeight + this.rightEdge = rightEdge + this.addChild(this.leftEdge) + this.addChild(this.rightEdge) + this.glowFilter = new GlowFilter(filterConfig.distance, 0, 0, gray, filterConfig.quality) + const rgb = hexToRGB(gray) + this.colorTweenHelper = { + r: rgb[0], + g: rgb[1], + b: rgb[2], + } + this.tweener = this + this.tweens = { + color: new TWEEN.Tween(this.colorTweenHelper), + glow: new TWEEN.Tween(this.glowFilter), + } + this._tint = gray + this._syncTints(this.tint) + } + + get tint() { + return this._tint + } + + set tint(tint) { + if (this.tint !== tint) { + if (this.group && this.group.tint !== tint) { + this.group.tint = tint + } + const nextRgb = hexToRGB(tint) + this.tweens.color + .stop() + .to( + { + r: nextRgb[0], + g: nextRgb[1], + b: nextRgb[2], + }, + shortTimeTransition + ) + .onUpdate((color) => { + this._syncTints(rgbToNum(color.r, color.g, color.b)) + }) + .start() + this._tint = tint + } + } + + _syncTints(tint) { + this.glowFilter.color = this.middle.tint = this.leftEdge.tint = this.rightEdge.tint = tint + } + + update() { + this.x = Globals.timeManager.timeToPx(this.data.timeStart) - strokeWeight / 2 + let width = Globals.timeManager.durationToPx(this.data.timeStart, this.data.timeEnd) + if (width < strokeWeight + 1) { + width = strokeWeight + } + const fullWidth = width + commentPadding * 2 + this.bg.width = fullWidth + this.middle.width = width - strokeWeight + this.rightEdge.x = width + commentPadding + } +} + +class CommentGroup { + constructor(annotationLayer, sprite) { + this.annotationLayer = annotationLayer + this.comments = [] + this.start = null + this.end = null + this.selected = false + this.addComment(sprite) + this._consolidated = false + } + + initialize(container, commentGroups) { + this.duration = this.end - this.start + this.median = this.start + this.duration / 2 + const graphic = new Graphics() + graphic + .beginFill(0xffffff) + .drawCircle(0, 0, strokeWeight / 2) + .endFill() + graphic.hitArea = new Circle(0, 0, commentLayerHeight / 2) + graphic.visible = false + graphic.pivot.y = strokeWeight / 2 + graphic.tweener = this + graphic.interactive = true + graphic.cursor = 'pointer' + graphic.on('pointerdown', () => { + this.annotationLayer.spritePointerDown(this.comments[0]) + }) + graphic.on('mouseover', (e) => { + this.annotationLayer.spriteMouseOver(e, this, this.comments[0], this.graphic) + }) + graphic.on('mouseout', () => { + this.annotationLayer.spriteMouseOut(this, this.graphic) + }) + this.graphic = graphic + container.addChild(this.graphic) + commentGroups.push(this) + this.glowFilter = new GlowFilter(filterConfig.distance, 0, 0, gray, filterConfig.quality) + const rgb = hexToRGB(gray) + this.colorTweenHelper = { + r: rgb[0], + g: rgb[1], + b: rgb[2], + } + this.tweens = { + color: new TWEEN.Tween(this.colorTweenHelper), + glow: new TWEEN.Tween(this.glowFilter), + } + this._tint = gray + this._syncTints(this.tint) + } + + addComment(sprite) { + this.comments.push(sprite) + sprite.group = this + if (sprite.data.timeEnd > this.end || this.end === null) { + this.end = sprite.data.timeEnd + } + if (sprite.data.timeStart < this.start || this.start === null) { + this.start = sprite.data.timeStart + } + } + + updatePosition() { + this.graphic.x = Globals.timeManager.timeToPx(this.median) + } + + get tint() { + return this._tint + } + + set tint(tint) { + if (this.tint !== tint && this.graphic) { + const nextRgb = hexToRGB(tint) + this.tweens.color + .stop() + .to( + { + r: nextRgb[0], + g: nextRgb[1], + b: nextRgb[2], + }, + shortTimeTransition + ) + .onUpdate((color) => { + this._syncTints(rgbToNum(color.r, color.g, color.b)) + }) + .start() + this._tint = tint + } + this._tint = tint + } + + _syncTints(tint) { + if (this.graphic) { + this.graphic.tint = this.glowFilter.color = tint + } + } + + set consolidated(consolidated) { + this._consolidated = consolidated + if (consolidated) { + const commentHoverIndex = this.comments.findIndex((comment) => { + return this.annotationLayer.hoveredComment === comment + }) + if (commentHoverIndex !== -1) { + this.annotationLayer.spriteMouseOut(this.comments[commentHoverIndex]) + } + } else { + if (this.annotationLayer.hoveredComment === this.graphic) { + this.annotationLayer.spriteMouseOut(this, this.graphic) + } + } + } + + get consolidated() { + return this._consolidated + } +} diff --git a/client/src/components/Audio.js b/client/src/components/Audio.js new file mode 100644 index 0000000..4c0aa04 --- /dev/null +++ b/client/src/components/Audio.js @@ -0,0 +1,149 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import Tone, { Buffer, BufferSource } from 'tone' +import { audioOutput } from './AudioOutput' + +const playingAudio = [] + +/** + * + * const audio = new Audio('https://path/to/file.mp3', 1423823918750) + * + * audio.sync(time: ms, playing: boolean, duration: (length of segment)) + * Audio.testStops(this.time) + * + */ + +export class Audio { + /** + * Stops audio in playingAudio array + * from playing. + * + * @param {number} time - takes an epoch + * + * playingAudio is a singleton cache of new Audio()s + * + * This needs to be called on each frame + */ + static testStops(time) { + playingAudio.forEach((audio) => { + const offset = time - audio.startTime + if (audio.playing && (0 > offset || offset > audio._audioElement.duration * 1000)) { + audio._stopAudio() + } + }) + } + + /** + * new Audio(filename, this.startTime) + * + * @param {string} url - path to audio file + * @param {int} startTime - timestamp in ms + */ + constructor(url, startTime) { + this._url = url + this.playing = false + this.loaded = false + this.loading = false + this.startTime = startTime + } + + get url() { + return this._url + } + + _effectsChain() { + const source = Tone.context.createMediaElementSource(this._audioElement) + Tone.connect(source, audioOutput) + } + + async load() { + this.loading = true + // this._audioElement = document.createElement('audio') + + const src = `https://storage.googleapis.com/deepblue-transcoded-audio/${this._url}` + + // this._audioElement.src = + // this._audioElement.crossOrigin = 'anonymous' + this._buffer = await Buffer.fromUrl(src) + + // console.timeEnd('loading') + + // this._effectsChain() + // this._audioElement.load() + // await new Promise((done) => + // this._audioElement.addEventListener('canplay', () => done()) + // ) + this.loaded = true + this.loading = false + } + + pause() { + if (this._source && this._source.state !== 'stopped') { + this._source.stop() + } + this._source = null + this.playing = false + } + + _stopAudio() { + this.pause() + const index = playingAudio.indexOf(this) + if (index !== -1) { + playingAudio.splice(index, 1) + } + this.playing = false + } + + async sync(time, playing) { + const duration = time + 30000 + const offset = time - this.startTime + // load if its offset is less than 30 seconds away + const shouldLoad = Math.abs(offset) < duration * 1.5 + if (!this.loaded && !this.loading && shouldLoad) { + await this.load() + return + } + if (!this.loaded || this._syncing) { + return + } + + this._syncing = true + + if (0 <= offset && offset <= this._buffer.duration * 1000) { + if (!this.playing && playing) { + this._source = new BufferSource({ + buffer: this._buffer, + fadeIn: 0.01, + fadeOut: 0.4, + onended() { + this._source = null + }, + }).connect(audioOutput) + this._source.start(Tone.now(), offset / 1000).toMaster() + playingAudio.push(this) + this.playing = true + } + } else if (this.playing) { + this._stopAudio() + } + + this._syncing = false + } + + dispose() { + this._stopAudio() + } +} diff --git a/client/src/components/AudioNoiseCancelling.js b/client/src/components/AudioNoiseCancelling.js new file mode 100644 index 0000000..8360ead --- /dev/null +++ b/client/src/components/AudioNoiseCancelling.js @@ -0,0 +1,71 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { GUI } from '../ui/Dat' +import Tone, { Filter, Gain, Compressor } from 'tone' +export const noiseCanceller = new Gain(2) + +const config = { + cancelNoise: false, +} +export const AudioNoiseCancellingConfig = config + +let audioFolder +if (GUI) { + audioFolder = GUI.addFolder('audio') + + //the gain node which sets the master volume. + // seems like a value between 2-8 works reasonably well + audioFolder.add(noiseCanceller.gain, 'value', 2, 8).name('volume') +} + +const lowpassFilter = new Filter({ + type: 'lowpass', + frequency: 1800, + Q: 0.7, + rolloff: -12, +}) +const highpassFilter = new Filter({ + type: 'highpass', + frequency: 200, + Q: 0.7, + rolloff: -12, +}) + +const compressor = new Compressor({ + threshold: -32, + ratio: 6, + attack: 0.1, + release: 0.4, +}) +Tone.connectSeries(noiseCanceller, lowpassFilter, highpassFilter, compressor, Tone.Master) + +if (GUI) { + audioFolder.add(compressor.threshold, 'value', -70, 0).name('comp thresh') + audioFolder.add(compressor.ratio, 'value', 1, 8).name('comp ratio') + audioFolder.add(highpassFilter.frequency, 'value', 100, 300).name('highpass') + audioFolder.add(lowpassFilter.frequency, 'value', 1000, 3000).name('lowpass') +} +// audioFolder.add(compressor.ratio, 'value', 0, 0).name('comp thresh') +/** + * Turn the audio noise cancelling on and off + */ +export function setNoiseCancelling() { + throw new Error('noise cancelling feature has been removed, please update the settings') +} + +export function setGain(value) { + //scale 0-1 into the range of 2-8 + noiseCanceller.gain.value = value * 8 +} diff --git a/client/src/components/AudioOutput.js b/client/src/components/AudioOutput.js new file mode 100644 index 0000000..e64ff5e --- /dev/null +++ b/client/src/components/AudioOutput.js @@ -0,0 +1,23 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import Tone, { Gain, Compressor } from 'tone' +import { noiseCanceller } from './AudioNoiseCancelling' + +export const audioOutput = new Gain(Tone.dbToGain(30)).connect(noiseCanceller) +// export const audioOutput = new Gain(Tone.dbToGain(30)).toMaster() +// Tone.connectSeries( +// audioOutput, +// noiseCanceller, +// ) \ No newline at end of file diff --git a/client/src/components/Axis.js b/client/src/components/Axis.js new file mode 100644 index 0000000..c41071f --- /dev/null +++ b/client/src/components/Axis.js @@ -0,0 +1,553 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Container, particles, Graphics, Sprite, Text, TextStyle } from 'pixi.js' +import { getControlsInfo } from '../ui/Controls' +import { scaleToRange } from '../util/Math' +import { Globals } from '../globals' +import * as d3Time from 'd3-time' +import * as d3TimeFormat from 'd3-time-format' + +const defaultTextStyle = { + fontFamily: 'Roboto Mono', + fill: '#ffffff', +} +export const tickConfig = { + ticks: [ + { + // major tick + yOffset: 32, + textStyle: new TextStyle(Object.assign({}, defaultTextStyle, { fontSize: 15 })), + }, + { + // minor tick + yOffset: 8, + textStyle: new TextStyle(Object.assign({}, defaultTextStyle, { fontSize: 11 })), + }, + ], + xOffset: 3, + stickyXOffset: 8, + tinyTickMinSize: 3, + tinyTickMaxSize: 6, +} +export const majorTickHeight = tickConfig.ticks[0].yOffset + tickConfig.ticks[0].textStyle.fontSize +export const minorTickHeight = tickConfig.ticks[1].yOffset + tickConfig.ticks[1].textStyle.fontSize +let tickTexture + +export class Axis extends Container { + constructor(spectrogram) { + super() + this.spectrogram = spectrogram + this.interactiveChildren = false + this.lines = new particles.ParticleContainer() + this.addChild(this.lines) + const lineGraphic = new Graphics() + lineGraphic + .lineStyle(1, 0xffffff) + .moveTo(0, 0) + .lineTo(0, tickConfig.ticks[0].yOffset + tickConfig.ticks[0].textStyle.fontSize) + tickTexture = Globals.pixiApp.renderer.generateTexture(lineGraphic) + this.tickContainers = [] + tickConfig.ticks.forEach(() => { + this.tickContainers.push(new Container()) + this.addChild(this.tickContainers[this.tickContainers.length - 1]) + }) + this.stickyMajorTick = new Text('', tickConfig.ticks[0].textStyle) // sticky bottom left major tick + this.tickContainers[0].addChild(this.stickyMajorTick) + this._updateYPosition() + this.ticks = {} + this.lastTimeBreakIndex = 4 + + // determine pixel width of largest minor tick text + this.tickWindowPadding = 0 + const widthTestText = new Text('', tickConfig.ticks[1].textStyle) + timeFormatBreaks.forEach((timeFormat) => { + widthTestText.text = timeFormat.ticks[1].format(Date.now()) + timeFormat.labelWidth = widthTestText.width + this.tickWindowPadding = + widthTestText.width > this.tickWindowPadding ? widthTestText.width : this.tickWindowPadding + }) + widthTestText.destroy() + + this.axisLine = new Graphics() + this.addChild(this.axisLine) + + this.resize() + } + + update() { + this._computeTicks() + this._updateYPosition() + } + + _computeTicks() { + if (!Globals.timeManager.updated && !Globals.spectrogram.dirty) return + const windowDuration = Globals.timeManager.idealWindowDuration + if (isNaN(windowDuration)) return + let timeBreakIndex = 4 // default to minutes display + // find which time breakpoint windowDuration falls within + if (windowDuration > timeFormatBreaks[0].breakpoint) { + timeBreakIndex = 0 + } else { + for (let i = 1; i < timeFormatBreaks.length; i++) { + if ( + windowDuration > timeFormatBreaks[i].breakpoint && + windowDuration <= timeFormatBreaks[i - 1].breakpoint + ) { + timeBreakIndex = i + break + } + } + } + Object.keys(this.ticks).forEach((tickTime) => { + this.ticks[tickTime].remove = true + }) + // using found breakpoint info, render ticks + const timeFormat = timeFormatBreaks[timeBreakIndex] + const windowStartTimeEpoch = Globals.timeManager.windowStartTime + // start grabbing ticks a bit before the current window so they can scroll off cleanly + const tickWindowPadding = Globals.timeManager.pxToDuration(-this.tickWindowPadding, 0) + const tickWindowStartTimeEpoch = windowStartTimeEpoch - tickWindowPadding + const windowEndTimeEpoch = Globals.timeManager.windowEndTime + const windowStartTime = new Date(windowStartTimeEpoch) + const tickWindowStartTime = new Date(tickWindowStartTimeEpoch) + const windowEndTime = new Date(windowEndTimeEpoch) + const tickDates = Axis.getTickDates(timeFormat, tickWindowStartTime, windowEndTime) + const zoomAmt = getControlsInfo().zoom + let firstMajorTickDrawn = false + for (let i = 0; i < tickDates.length; i++) { + const tickTime = tickDates[i].getTime() + const gap = Globals.timeManager.gapAtTime(tickTime) + const nextTickTime = i < tickDates.length - 1 ? tickDates[i + 1].getTime() : windowEndTimeEpoch + 1 + // draw tick if it's onscreen + if (tickTime < windowEndTimeEpoch && nextTickTime > tickWindowStartTimeEpoch && !gap) { + const tickX = Globals.timeManager.timeToPx(tickTime) + let tick = { + tickTexts: [], + remove: false, + } + if (!this.ticks[tickTime]) { + this._drawTick(tick, tickDates[i], timeFormat) + this.ticks[tickTime] = tick + } else { + tick = this.ticks[tickTime] + tick.remove = false + // if we've hit a new breakpoint but still have a tick at this time + // reformat the tick based on the new breakpoint + if (this.lastTimeBreakIndex != timeBreakIndex) { + this._drawTick(tick, tickDates[i], timeFormat, true) + } + } + tick.line.x = tickX + let isTinyTick = true + tick.tickTexts.forEach((tickElement) => { + if (tickElement) { + tickElement.x = tickX + tickConfig.xOffset + isTinyTick = false + } + }) + // set tiny tick line size based on zoom amount + if (isTinyTick) { + tick.line.height = scaleToRange( + zoomAmt, + 0, + 1, + tickConfig.tinyTickMinSize, + tickConfig.tinyTickMaxSize + ) + } + // handle special major tick details + if (tick.tickTexts[0]) { + // to facilitate sticky tick transitions + tick.tickTexts[0].visible = tickX >= tickConfig.stickyXOffset - tickConfig.xOffset + // sticky bottom left major tick + if (!firstMajorTickDrawn) { + firstMajorTickDrawn = true + this.stickyMajorTick.text = timeFormat.ticks[0] + .format( + tickX < tickConfig.stickyXOffset - tickConfig.xOffset ? tickDates[i] : windowStartTime + ) + .toUpperCase() + if ( + tickX > this.stickyMajorTick.width + 2 * tickConfig.stickyXOffset || + tickX < tickConfig.stickyXOffset - tickConfig.xOffset + ) { + this.stickyMajorTick.x = tickConfig.stickyXOffset + } else { + this.stickyMajorTick.x = tickX - this.stickyMajorTick.width - tickConfig.stickyXOffset + } + } + } + } + } + // if no major tick has been drawn, set the sticky tick text + if (!firstMajorTickDrawn) { + this.stickyMajorTick.text = timeFormat.ticks[0].format(windowStartTime).toUpperCase() + this.stickyMajorTick.x = tickConfig.stickyXOffset + } + // remove ticks that are not currently in use + Object.keys(this.ticks).forEach((tickTime) => { + const tick = this.ticks[tickTime] + if (tick.remove) { + this.lines.removeChild(tick.line) + tick.line.destroy() + tick.tickTexts.forEach((tickElement, i) => { + if (tickElement) { + this.tickContainers[i].removeChild(tickElement) + tickElement.destroy() + } + }) + delete this.ticks[tickTime] + } + }) + this.lastTimeBreakIndex = timeBreakIndex + } + + _updateYPosition() { + this.tickContainers.forEach((tickContainer, i) => { + tickContainer.y = tickConfig.ticks[i].yOffset + i + 1 + }) + } + + static getTickDates(timeFormat, start, end) { + const audioPeriods = Globals.timeManager.getAudioPeriods(start, end) + + let tickDates = [] + for (let i = 0; i < audioPeriods.length; i++) { + const pstart = Math.max(audioPeriods[i].timeStart, start) + const pend = Math.min(audioPeriods[i].timeEnd, end) + const addTicks = timeFormat.timeFunc.range(pstart, pend) + if (timeFormat.intermediateSteps) { + if (!addTicks.length || addTicks[0] > pstart) { + const prevTick = timeFormat.timeFunc(pstart) + if (Axis.tickAddable(tickDates, prevTick)) { + tickDates.push(prevTick) + } + } + if (addTicks[0] > tickDates[tickDates.length - 1]) { + tickDates = tickDates.concat(addTicks) + } else { + for (let j = 1; j < addTicks.length; j++) { + if (Axis.tickAddable(tickDates, addTicks[j])) { + tickDates.push(addTicks[j]) + } + } + } + if (i < audioPeriods.length - 1 && (!addTicks.length || addTicks[addTicks.length - 1] < pend)) { + const nextTick = timeFormat.timeFunc(pend + timeFormat.interval) + if (Axis.tickAddable(tickDates, nextTick)) { + tickDates.push(nextTick) + } + } + } else { + tickDates = tickDates.concat(addTicks) + } + } + + if (timeFormat.intermediateSteps) { + // if no ticks are returned, our time interval is too big + // for our time window and there are no interval breaks + // within the window, so add one previous to it + if (!tickDates.length) { + const onlyTick = timeFormat.timeFunc(start) + tickDates.push(onlyTick) + } + // if we need extra ticks extending off the left side of + // the screen because the first tick doesn't start at 0px, + // create a tick previous to the set + if (tickDates[0] > start) { + const firstTick = new Date(tickDates[0].getTime() - timeFormat.interval) + tickDates.unshift(timeFormat.timeFunc(firstTick)) + } + // populate intermediate ticks + // caution: may produce inconsistent results for month or + // year intervals due to timespan irregularities + if (tickDates[tickDates.length - 1] < end) { + const lastTick = new Date(tickDates[tickDates.length - 1].getTime() + timeFormat.interval) + tickDates.push(lastTick) + } + const divisionInterval = timeFormat.interval / (timeFormat.intermediateSteps + 1) + const dividedTickDates = [] + for (let i = 0; i < tickDates.length - 1; i++) { + dividedTickDates.push(tickDates[i]) + if (tickDates[i + 1] - tickDates[i] === timeFormat.interval) { + for (let j = 1; j <= timeFormat.intermediateSteps; j++) { + const intermediateTick = new Date(tickDates[i].getTime() + divisionInterval * j) + dividedTickDates.push(intermediateTick) + } + } + } + dividedTickDates.push(tickDates[tickDates.length - 1]) + return dividedTickDates + } + return tickDates + } + + static tickAddable(tickDates, date) { + return !tickDates.length || date > tickDates[tickDates.length - 1] + } + + static dateMatchesInterval(date, interval) { + // if the interval is a month or longer, + // we have to use a special time function due to timespan irregularities + if (interval === 3.154e10) { + // one year + return +d3Time.utcYear(date) === +date + } else if (interval === 2.628e9) { + // one month + return +d3Time.utcMonth(date) === +date + } else { + return date.getTime() % interval === 0 + } + } + + _addLine(height) { + const line = new Sprite(tickTexture) + line.height = height + this.lines.addChild(line) + return line + } + + _drawTick(tick, tickDate, timeFormat, edit = false) { + // edit is for if we've hit a new breakpoint and have to edit an existing tick + // otherwise we're creating a new tick + let lineDrawn = false + timeFormat.ticks.forEach((tickFormat, tickType) => { + if (!edit) { + tick.tickTexts.push(null) + } + if (Axis.dateMatchesInterval(tickDate, tickFormat.interval)) { + // axis line + if (!lineDrawn) { + const tickHeight = + tickConfig.ticks[tickType].yOffset + tickConfig.ticks[tickType].textStyle.fontSize + if (edit) { + tick.line.height = tickHeight + } else { + tick.line = this._addLine(tickHeight) + } + lineDrawn = true + } + // tick text + const text = tickFormat.format(tickDate).toUpperCase() + if (edit && tick.tickTexts[tickType]) { + tick.tickTexts[tickType].text = text + } else { + tick.tickTexts[tickType] = new Text(text, tickConfig.ticks[tickType].textStyle) + this.tickContainers[tickType].addChild(tick.tickTexts[tickType]) + } + } else { + if (edit && tick.tickTexts[tickType]) { + this.tickContainers[tickType].removeChild(tick.tickTexts[tickType]) + tick.tickTexts[tickType].destroy() + tick.tickTexts[tickType] = null + } + } + }) + // tiny ticks are drawn if nothing else has been drawn at this time spot + if (!lineDrawn) { + if (edit) { + tick.line.height = tickConfig.tinyTickMaxSize + } else { + tick.line = this._addLine(tickConfig.tinyTickMaxSize) + } + } + } + + // format timestamp to display the least granular relevant date info + // more important to know that a new year started than a new day + static multiFormat(date) { + return (d3Time.utcMinute(date) < date + ? timeFormats.secondsFormat + : d3Time.utcDay(date) < date + ? timeFormats.minutesFormat + : timeFormats.monthDayFormat)(date) + } + + resize() { + // draw axis line + this.axisLine + .clear() + .lineStyle(1, 0xffffff) + .moveTo(0, 0) + .lineTo(window.innerWidth + this.tickWindowPadding, 0) + // compute breakpoints + const padding = 15 + for (let i = 1; i < timeFormatBreaks.length; i++) { + if (!timeFormatBreaks[i].labelWidth) continue + timeFormatBreaks[i - 1].breakpoint = + (window.innerWidth / (timeFormatBreaks[i].labelWidth + padding)) * timeFormatBreaks[i].ticks[1].interval + } + } +} + +export const timeFormats = { + yearFormat: d3TimeFormat.utcFormat('%Y'), + monthFormat: d3TimeFormat.utcFormat('%b'), + monthYearFormat: d3TimeFormat.utcFormat('%b %Y'), + dayFormat: d3TimeFormat.utcFormat('%d'), + monthDayFormat: d3TimeFormat.utcFormat('%b %d'), + monthDayFormatNoPadding: d3TimeFormat.utcFormat('%b %-d'), + monthDayYearFormat: d3TimeFormat.utcFormat('%b %-d %Y'), + minutesFormat: d3TimeFormat.utcFormat('%H:%M'), + secondsFormat: d3TimeFormat.utcFormat('%H:%M:%S'), +} + +// breakpoints for different tick formats, from biggest to smallest time windows +const timeFormatBreaks = [ + { + breakpoint: 3.924e9, // 1 month and 15 days + timeFunc: d3Time.utcMonth, + interval: 2.628e9, + intermediateSteps: 5, + ticks: [ + { + interval: 3.154e10, // major tick label every year + format: timeFormats.yearFormat, + }, + { + interval: 2.628e9, // minor tick label every month + format: timeFormats.monthFormat, + }, + ], + }, + { + breakpoint: 6.048e8, // 7 days + timeFunc: d3Time.utcDay, + interval: 8.64e7, + intermediateSteps: 5, + ticks: [ + { + interval: 2.628e9, + format: timeFormats.monthYearFormat, + }, + { + interval: 8.64e7, // minor tick label every day + format: timeFormats.dayFormat, + }, + ], + }, + { + breakpoint: 2.592e8, // 3 days + timeFunc: d3Time.utcDay, + interval: 8.64e7, + intermediateSteps: 11, + ticks: [ + { + interval: 2.628e9, + format: timeFormats.monthYearFormat, + }, + { + interval: 4.32e7, // minor tick label every 12 hours + format: Axis.multiFormat, + }, + ], + }, + { + breakpoint: 8.64e7, // 1 day + timeFunc: d3Time.utcDay, + interval: 8.64e7, + intermediateSteps: 23, + ticks: [ + { + interval: 2.628e9, + format: timeFormats.monthYearFormat, + }, + { + interval: 2.16e7, // minor tick label every 6 hours + format: Axis.multiFormat, + }, + ], + }, + { + breakpoint: 1.8e7, // 5 hour + timeFunc: d3Time.utcHour, + interval: 3.6e6, + intermediateSteps: 5, + ticks: [ + { + interval: 8.64e7, + format: timeFormats.monthDayYearFormat, + }, + { + interval: 3.6e6, + format: timeFormats.minutesFormat, + }, + ], + }, + { + breakpoint: 7.2e6, // 2 hour + timeFunc: d3Time.utcHour, + interval: 3.6e6, + intermediateSteps: 11, + ticks: [ + { + interval: 8.64e7, + format: timeFormats.monthDayYearFormat, + }, + { + interval: 1.8e6, + format: timeFormats.minutesFormat, + }, + ], + }, + { + breakpoint: 1.8e6, // half hour + timeFunc: d3Time.utcHour, + interval: 3.6e6, + intermediateSteps: 59, + ticks: [ + { + interval: 8.64e7, + format: timeFormats.monthDayYearFormat, + }, + { + interval: 300000, + format: timeFormats.minutesFormat, + }, + ], + }, + { + breakpoint: 180000, // 3 minutes + timeFunc: d3Time.utcMinute, + interval: 60000, + intermediateSteps: 5, // tiny tick every 10 seconds + ticks: [ + { + interval: 8.64e7, + format: timeFormats.monthDayYearFormat, + }, + { + interval: 60000, + format: timeFormats.minutesFormat, + }, + ], + }, + { + breakpoint: 0, + timeFunc: d3Time.utcSecond, + interval: 1000, + intermediateSteps: 0, // tiny tick every second + ticks: [ + { + interval: 8.64e7, + format: timeFormats.monthDayYearFormat, + }, + { + interval: 10000, + format: timeFormats.secondsFormat, + }, + ], + }, +] diff --git a/client/src/components/ClassificationHeatmapLayer.js b/client/src/components/ClassificationHeatmapLayer.js new file mode 100644 index 0000000..7b98bc8 --- /dev/null +++ b/client/src/components/ClassificationHeatmapLayer.js @@ -0,0 +1,395 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Container, Graphics, utils } from 'pixi.js' +import { Globals, Device } from '../globals' +import { interpolateMultiColor, clamp } from '../util/Math' +import { GUI } from '../ui/Dat' +import { ClassificationDataTile } from '../models/ClassificationDataTile' +import { TileDefinitions } from '../models/TileDefinitions' +import { Tween, Easing } from '@tweenjs/tween.js' +const idealBarWidth = 10 +const minTileZoom = -4 +const maxTileZoom = 9 +export class ClassificationHeatmapLayer extends Container { + constructor(infoBubble) { + super() + + this.infoBubble = infoBubble + this.classifData = [] + this.graphics = new Graphics() + this.addChild(this.graphics) + this._colorStart = [31, 31, 53] + this._colorMid = [143, 0, 50] + this._colorEnd = [255, 0, 0] + + this._updateColorMap() + this._windowStart = 0 + this._windowEnd = 0 + this.needsRender = false + + if (GUI) { + GUI.addColor(this, 'colorStart') + GUI.addColor(this, 'colorMid') + GUI.addColor(this, 'colorEnd') + } + + this.tileDefinitions = new TileDefinitions() + this.dataTiles = {} + + this.loaded = false + + // Setup mouse interaction with tooltip (infobubble) + this.interactive = true + this.on('pointerdown', () => {}) + this.on('mouseover', (e) => { + this.mouseOver(e) + }) + this.on('mouseout', () => { + this.mouseOut() + }) + this.on('mousemove', (e) => { + this.mouseMove(e) + }) + + this._heatmapTransition = 1 + this.tween = new Tween(this) + this.tween.easing(Easing.Quadratic.InOut).onUpdate(() => { + this.needsRender = true + }) + } + + mouseOver() { + if (!Device.isTouch) this.hover = true + } + mouseMove(e) { + if (this.hover) { + const score = this.getClassifsForPosition(e.data.global.x) + if (score !== null) { + this.infoBubble.coords = { + x: e.data.global.x + 10, + y: e.data.global.y + 10, + } + this.infoBubble.text = `Humpback whale detection confidence: ${Math.floor(score * 100)}%` + } else { + this.infoBubble.open = false + } + } + } + + mouseOut() { + this.hover = false + this.infoBubble.open = false + } + + _updateColorMap() { + this.colormap = interpolateMultiColor([this._colorStart, this._colorMid, this._colorEnd]) + } + set colorStart(c) { + this._colorStart = c.map((x) => Math.round(x)) + this._updateColorMap() + } + set colorMid(c) { + this._colorMid = c.map((x) => Math.round(x)) + this._updateColorMap() + } + set colorEnd(c) { + this._colorEnd = c.map((x) => Math.round(x)) + this._updateColorMap() + } + get colorStart() { + return this._colorStart + } + get colorMid() { + return this._colorMid + } + get colorEnd() { + return this._colorEnd + } + + get windowStart() { + return Globals.timeManager.windowStartTime + } + get windowEnd() { + return Globals.timeManager.windowEndTime + } + + // Hide the heatmap with transition + hide() { + if (!this.hidden) { + this.hidden = true + this.tween + .stop() + .to({ _heatmapTransition: 0 }, 100) + .start() + } + } + + // Show the heatmap with transition + show() { + if (this.hidden) { + this.hidden = false + this.tween + .stop() + .to({ _heatmapTransition: 1 }, 100) + .start() + } + } + + isVisible() { + return this._heatmapTransition > 0 + } + + isFullyVisible() { + return this._heatmapTransition == 1 + } + + getClosestTileTime(time, incr) { + return time - (time % incr) + } + + getRange(startTime, endTime, zoomLevel) { + const zoomOutFactor = 12 + const gaps = Globals.timeManager.getGaps(startTime, endTime) + let range = this.tileDefinitions.getRange(startTime, endTime, zoomLevel - zoomOutFactor) + return range.filter((r) => { + return !gaps.find((g) => g.timeStart <= r.time && g.timeEnd >= r.time + r.duration) + }) + } + + closestZoomLevel(ms) { + return clamp(Math.round(TileDefinitions.getZoomLevel(ms)), minTileZoom, maxTileZoom) + } + + getTile(startTime, zoomLevel) { + if (!this.dataTiles[zoomLevel]) { + this.dataTiles[zoomLevel] = {} + } + + return this.dataTiles[zoomLevel][startTime] + } + + searchLowerClassif(startTime, endTime, zoomLevelStart) { + for (let i = zoomLevelStart - 1; i >= minTileZoom; i--) { + const timeranges = this.getRange(startTime, endTime, i) + + if (this.getTile(timeranges[0].time, i) && this.getTile(timeranges[0].time, i).loaded) { + const tile = this.getTile(timeranges[0].time, i) + return tile + } + } + } + searchHigherClassif(startTime, endTime, zoomLevelStart) { + for (let i = zoomLevelStart + 1; i <= maxTileZoom; i++) { + const timeranges = this.getRange( + // const timeranges = this.getTimeRanges( + startTime, + endTime, + i + ) + + let tiles = [] + for (let r of timeranges) { + if (this.getTile(r.time, i) && this.getTile(r.time, i).loaded) { + const tile = this.getTile(r.time, i) + tiles.push(tile) + } + } + if (tiles.length > 0) return tiles + } + } + + getClassifsForCurrentWindow() { + if (!this.windowStart) { + return + } + const zoomLevel = this.closestZoomLevel(this.resolution * idealBarWidth) + + const loadStartTime = this.windowStart - this.duration * 2 + const loadEndTime = this.windowEnd + this.duration * 2 + + const audioPeriods = Globals.timeManager.getAudioPeriods(loadStartTime, loadEndTime) + // Calculate visible time ranges + + let tileDefinitions = [] + for (let period of audioPeriods) { + let t = this.getRange( + Math.max(loadStartTime, period.timeStart), + Math.min(loadEndTime, period.timeEnd), + zoomLevel + ) + tileDefinitions = tileDefinitions.concat(t) + } + + let data = [] + + // Check if timerange tiles are loaded, if not load them + for (const def of tileDefinitions) { + if (!this.getTile(def.time, zoomLevel) && ClassificationDataTile.fetchCount == 0) { + // Load new data tile + this.dataTiles[zoomLevel][def.time] = new ClassificationDataTile( + def.time, + def.time + def.duration, + zoomLevel + ) + this.dataTiles[zoomLevel][def.time].fetch().then(() => { + // Force an update when tile has loaded + this.needsRender = true + this.loaded = true + }) + } + + const tile = this.getTile(def.time, zoomLevel) + if (tile && tile.loaded) { + data = data.concat(tile.data) + } else { + // Search for tiles at lower zoom levels to use as backup data + let backupTile = this.searchLowerClassif(def.time, def.time + def.duration, zoomLevel) + if (backupTile && backupTile.data) { + let d = backupTile.data.filter( + (d) => d.time_start < def.time + def.duration && d.time_end > def.time + ) + data = data.concat(d) + } else { + let higherBackupTiles = this.searchHigherClassif(def.time, def.time + def.duration, zoomLevel) + if (higherBackupTiles) { + for (let t of higherBackupTiles) { + let d = [] + // Create new mocked up tile + let skip = (t.zoomLevel - zoomLevel) * 2 + for (let i = 0; i <= t.data.length - skip; i += skip) { + let score = 0 + for (let ii = 0; ii < skip; ii++) { + score += t.data[ii + i].score + } + score /= skip + d.push({ + score: score, + time_start: t.data[i].time_start, + time_end: t.data[i + skip - 1].time_end, + zoomLevel: zoomLevel, + }) + } + data = data.concat(d) + } + } + } + } + } + if (data.length > 0) { + this.classifData = data + } + } + + getAverageClassification() { + if (this.classifData && this.classifData.length) { + const total = this.classifData.reduce((total, current) => total + current.score, 0) + return total / this.classifData.length + } else { + return 0 + } + } + + get duration() { + return Globals.timeManager.idealWindowDuration + } + + get resolution() { + return Globals.timeManager.resolution + } + + update() { + this.getClassifsForCurrentWindow() + + if (Globals.timeManager.updated || Globals.windowResizing || Globals.spectrogram.dirty) { + this.needsRender = true + } + this.render() + } + + getClassifsForPosition(px) { + let currentClassif = this.classifData.find((item) => { + const itemStartTimePx = Globals.timeManager.timeToPx(item.time_start) + const itemEndTimePx = Globals.timeManager.timeToPx(item.time_end) + return itemStartTimePx <= px && itemEndTimePx >= px + }) + if (currentClassif) { + return clamp(currentClassif.score, 0, 1) + } else { + return null + } + } + + render() { + // console.log(Globals.spectrogram.windowDuration, Globals.timeManager.windowDuration, new Date(Globals.timeManager.currentTime)) + + if (this.classifData.length == 0) return + if (!this.needsRender) { + return + } + this.needsRender = false + const idealDuration = this.resolution * idealBarWidth + const zoomLevel = this.closestZoomLevel(idealDuration) + let fadeFactor = + Math.abs(idealDuration - TileDefinitions.zoomLevelDuration(zoomLevel - 1)) - + Math.abs(idealDuration - TileDefinitions.zoomLevelDuration(zoomLevel)) + fadeFactor /= TileDefinitions.zoomLevelDuration(zoomLevel) + fadeFactor = clamp(fadeFactor, 0, 1) + + const height = this.renderHeight * this._heatmapTransition + + this.graphics.clear() + + const windowStartTime = Globals.timeManager.windowStartTime + const windowEndTime = Globals.timeManager.windowEndTime + this.classifData = this.classifData.filter((d) => { + return d.time_end > windowStartTime && d.time_start < windowEndTime + }) + for (let [index, item] of this.classifData.entries()) { + const even = item.index % 2 == 0 + const pairIndex = even ? index + 1 : index - 1 + + let intensity = item.score + + // If data is from correct zoom leve, interpolate between zoom levels + if ( + this.classifData[pairIndex] && + item.zoomLevel == zoomLevel && + this.classifData[pairIndex].zoomLevel == zoomLevel + ) { + let otherIntensity = this.classifData[pairIndex].score + intensity = intensity * fadeFactor + ((1 - fadeFactor) * (intensity + otherIntensity)) / 2 + } + + intensity = clamp(intensity, 0, 1) + + const rgb = this.colormap(intensity) + for (let i = 0; i < rgb.length; i++) { + rgb[i] = rgb[i] > 255 ? 255 : rgb[i] + } + const hex = utils.rgb2hex([rgb[0] / 255, rgb[1] / 255, rgb[2] / 255]) + this.graphics.beginFill(hex) + + let itemStartTimePx = Globals.timeManager.timeToPx(item.time_start) + let itemEndTimePx = Globals.timeManager.timeToPx(item.time_end) + const width = itemEndTimePx - itemStartTimePx + + if (itemEndTimePx > 0 && itemStartTimePx < window.innerWidth) { + const yPos = (1 - this._heatmapTransition) * this.renderHeight + this.graphics.drawRect(itemStartTimePx, yPos, width, height) + } + this.graphics.endFill() + } + } +} diff --git a/client/src/components/Frequencies.js b/client/src/components/Frequencies.js new file mode 100644 index 0000000..d661a93 --- /dev/null +++ b/client/src/components/Frequencies.js @@ -0,0 +1,83 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Container, Text, Graphics } from 'pixi.js' +import { scaleToRange } from '../util/Math' + +const ticks = [] + +const minFreq = 70 +// const maxFreq = 4000 +const octaves = 5.8 +const ticksPerOctave = 2 +for (let i = 0; i < octaves; i += 1 / ticksPerOctave) { + const freq = minFreq * Math.pow(2, i) + if (freq > 1000) { + ticks.push((freq / 1000).toFixed(1) + 'k') + } else { + ticks.push(freq.toFixed(0)) + } +} +ticks.reverse() + +const dashWidth = 10 +export const FrequenciesConfig = { + showFrequencies: false, +} +export class Frequencies extends Container { + constructor(spectrogram) { + super() + this.spectrogram = spectrogram + this.ticks = [] + ticks.forEach((tick, index) => { + const text = new Text(tick, { fontFamily: 'Roboto Mono', fontSize: 10, align: 'right', fill: '#FFFFFF' }) + this.addChild(text) + text.anchor.set(1, 0) + text.y = index * 20 + this.ticks.push(text) + const graphics = new Graphics() + graphics + .lineStyle(1, 0xffffff) + .moveTo(0, 0) + .lineTo(dashWidth, 0) + text.addChild(graphics) + graphics.y = 5 + graphics.x = 6 + }) + } + + update() { + if (this.spectrogram) { + this.y = (window.innerHeight - this.spectrogram.height) / 2 + this.x = window.innerWidth - dashWidth + const topPadding = 0 + if (!FrequenciesConfig.showFrequencies) { + this.alpha = 0 + return + } else { + this.alpha = 1 + } + this.ticks.forEach((tick, index) => { + tick.alpha = this.alpha + if (this.spectrogram.height <= 320) { + //hide every other + if (index % 2 === 0) { + tick.alpha = 0 + } + } + tick.y = scaleToRange(index, 0, this.ticks.length, topPadding, this.spectrogram.height - topPadding) + }) + } + } +} diff --git a/client/src/components/GapLayer.js b/client/src/components/GapLayer.js new file mode 100644 index 0000000..9c4dfb9 --- /dev/null +++ b/client/src/components/GapLayer.js @@ -0,0 +1,132 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Container, Graphics, Sprite, Texture, extras, Text } from 'pixi.js' +import { Globals, Config } from '../globals' +import { minorTickHeight, timeFormats } from './Axis' + +export class GapLayer extends Container { + constructor() { + super() + this.graphics = new Graphics() + this.addChild(this.graphics) + this.interactiveChildren = false + this.gapSprites = {} + } + + update() { + this.gaps = Globals.timeManager.getGaps(Globals.timeManager.windowStartTime, Globals.timeManager.windowEndTime) + this.render() + } + + render() { + if (!Globals.timeManager.updated && !Globals.spectrogram.dirty) return + Object.keys(this.gapSprites).forEach((gapKey) => { + this.gapSprites[gapKey].removing = true + }) + for (let gap of this.gaps) { + const timeStart = gap.timeStart + let gapSprite + if (this.gapSprites[timeStart]) { + gapSprite = this.gapSprites[timeStart] + gapSprite.removing = false + gapSprite.data = gap + } else { + gapSprite = new DataGap(this, gap) + this.gapSprites[timeStart] = gapSprite + this.addChild(gapSprite) + gapSprite.on('DataGapRemoved', (target) => { + this.removeChild(target) + target.destroy({ children: true }) + delete this.gapSprites[timeStart] + }) + } + gapSprite.update() + } + Object.keys(this.gapSprites).forEach((gapKey) => { + this.gapSprites[gapKey].handleRemove() + }) + } +} + +class DataGap extends Container { + constructor(gapLayer, gapData) { + super() + this.gapLayer = gapLayer + this.data = gapData + this.removing = false + this.duration = this.time_end - this.time_start + const background = new Sprite(Texture.WHITE) + background.tint = Config.bgColor + if (Config.debug) { + background.alpha = 0.4 + } + this.background = background + this.addChild(this.background) + const dashedLineGraphic = new Graphics() + const dashSize = 2 + dashedLineGraphic + .lineStyle(1, 0xffffff) + .moveTo(0, 0) + .lineTo(dashSize, 0) + .lineStyle(1, Config.bgColor) + .moveTo(dashSize, 0) + .lineTo(dashSize * 2, 0) + const dashedLineTexture = Globals.pixiApp.renderer.generateTexture(dashedLineGraphic) + const dashedLine = new extras.TilingSprite(dashedLineTexture) + dashedLine.height = 1 + dashedLine.width = 1 + this.dashedLine = dashedLine + this.addChild(this.dashedLine) + const textFormat = DataGap.multiFormat(this.data.timeEnd - this.data.timeStart) + const textContent = 'No audio from\n' + textFormat(this.data.timeStart) + ' to ' + textFormat(this.data.timeEnd) + this.text = new Text(textContent, { + fontFamily: 'Roboto Mono', + fontSize: 10, + align: 'center', + fill: '#FFFFFF', + leading: 8, + }) + this.text.anchor.set(0.5) + this.addChild(this.text) + } + + update() { + const width = this.data.pixelPositionEnd - this.data.pixelPosition + this.visible = width >= 1 + if (!this.visible) return + this.background.width = width + this.dashedLine.width = width + this.dashedLine.y = this.gapLayer.renderHeight + this.background.height = this.gapLayer.renderHeight + this.x = this.data.pixelPosition + this.text.visible = width > this.text.width + 20 + this.text.x = width / 2 + this.text.y = this.gapLayer.renderHeight / 2 + } + + handleRemove() { + if (this.removing) { + this.emit('DataGapRemoved', this) + } + } + + static multiFormat(duration) { + return duration < 60000 + ? timeFormats.secondsFormat + : duration < 8.64e7 + ? timeFormats.minutesFormat + : timeFormats.monthDayFormatNoPadding + } +} diff --git a/client/src/components/Layer.js b/client/src/components/Layer.js new file mode 100644 index 0000000..2182717 --- /dev/null +++ b/client/src/components/Layer.js @@ -0,0 +1,47 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Container } from 'pixi.js' +import { Globals, Config } from '../globals' + +export class Layer extends Container { + constructor(tileDuration, startTime, fullAudioDuration, layerIndex) { + super() + this.fullCoverageTime = new Date() + this.startTime = startTime + // Amount of time represented per tile + this.tileDuration = tileDuration + this.layerIndex = layerIndex + this.fullAudioDuration = fullAudioDuration + + // Give us the pow resolution - 1,2,4,8,16,32 + this.tileZoomLevel = Math.pow(2, this.layerIndex) + + // Number of tiles in this audio track at this resolution/layer + this.tileCount = Math.ceil(fullAudioDuration / this.tileDuration) + + // Width of all tiles in this layer. + // Not including scale, and we might need to consider + // the tail end, but we'll do -1 for now. + this.layerWidth = (this.tileCount - 1) * Config.tileWidth + } + + get timeScale() { + if (this.tileDuration && Globals.spectrogram.duration) { + return this.layerWidth / this.fullAudioDuration + } else { + return 1 + } + } +} diff --git a/client/src/components/Player.js b/client/src/components/Player.js new file mode 100644 index 0000000..d61340b --- /dev/null +++ b/client/src/components/Player.js @@ -0,0 +1,212 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LocationsModel } from '../models/Locations' +import { Audio } from './Audio' +import { Globals } from '../globals' + +/** + * Moved from Spectrogram + */ +export class Player { + constructor(location, startTime) { + /** + * The location of this player + */ + this.setLocation(location) + + /** + * the current file's start time + */ + this._startTime = startTime + + /** + * The realtime offset between the playhead and Date.now() + */ + this._realTimeStart = 0 + + this._hasInit = false + + this.currentFiles = [] + + this._currentFilename = '' + + this.buffering = false + + this.playingWithoutAudio = false + + /** + * Kick off the synchronization loop + */ + setInterval(this._sync.bind(this), 300) + } + + async setLocation(location) { + // if (this._spectrogramTiles) { + // this._spectrogramTiles.dispose() + // } + this._location = location + this.locationModel = await LocationsModel.get(location) + await this._fetchFiles() + } + + get location() { + return this._location + } + + get startTime() { + return this.locationModel.startTime + } + + get currentFilename() { + let currentFilename = '' + this.currentFiles.forEach((file) => { + if (file.audio && file.audio.playing) { + currentFilename = file.audio.url + } + }) + return currentFilename + } + + start() { + this._realTimeStart = Date.now() + } + + pause() { + this.currentFiles.forEach((file) => { + if (file.audio) { + file.audio.pause() + } + }) + this._startTime = this.time + this._realTimeStart = 0 + } + + _sync() { + if (!Globals.isScrubbing) { + this._fetchFiles() + } + } + + audioLoop() { + if (!Globals.isScrubbing) { + this.currentFiles.forEach((file) => { + if (file.audio) { + file.audio.sync(this.time, this.playing) + } + }) + } + const anyPlaying = this.currentFiles.some((f) => f.audio.playing) + const anyLoading = this.currentFiles.some((f) => f.audio.loading) + const buffering = this.playing && anyLoading && !anyPlaying + this.playingWithoutAudio = this.playing && !anyPlaying + if (buffering !== this.buffering) { + this.buffering = buffering + if (!buffering) { + Globals.controls.showAudioLoading = false + } + } + if (anyPlaying && Globals.controls.showAudioLoading) { + Globals.controls.showAudioLoading = false + } + if (this.playing) { + const thisGap = Globals.timeManager.getGap(this.time) + if (thisGap) { + this.pause() + Globals.controls.tweens.position + .stop() + .to( + { + position: thisGap.timeEnd - Globals.currentLocation.startTime, + }, + 750 + ) + .start() + } + } + } + + async _fetchFiles() { + if (!this.locationModel || this.fetchingFiles) return + this.loading = true + this.currentFiles.forEach((file) => { + file.remove = true + }) + this.fetchingFiles = true + let files + try { + files = await this.locationModel.getFiles(this.time) + this.fetchingFiles = false + } catch (err) { + console.log('file error', err) + this.fetchingFiles = false + } + + if (!this._hasInit) { + // TODO: if we switch locations, + // we need to empty this array + // and delete the items + this.currentFiles = [] + this._hasInit = true + } + + if (files.length > 0) { + files.forEach((file) => { + const matchedFiles = this.currentFiles.filter((f) => f.filename === file.filename) + + if (matchedFiles.length > 0) { + // let's loop just in case there are + // more than one + matchedFiles.forEach((mf) => { + mf.remove = false + }) + } else { + file.audio = new Audio(file.filename, file.startTime) + this.currentFiles.push(file) + file.remove = false + } + }) + this.currentFiles.forEach((item, index) => { + if (item.remove) { + item.audio.dispose() + delete this.currentFiles[index] + } + }) + } else { + // Stop player if no files have been found to play + if (Globals.player.playing) { + this.pause() + Globals.controls.$$playButton.style.display = 'flex' + Globals.controls.$$pause.style.display = 'none' + Globals.controls.playing = false + } + } + } + + get time() { + if (this._realTimeStart > 0) { + return Date.now() - (this._realTimeStart - this._startTime) + } else { + return this._startTime + } + } + + set time(time) { + this._startTime = time + } + + get playing() { + return this._realTimeStart > 0 + } +} diff --git a/client/src/components/SimilarityLayer.js b/client/src/components/SimilarityLayer.js new file mode 100644 index 0000000..7586f05 --- /dev/null +++ b/client/src/components/SimilarityLayer.js @@ -0,0 +1,327 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Container, Graphics, utils, BLEND_MODES } from 'pixi.js' +import { Globals, Config } from '../globals' +import { scaleToRange, clamp, interpolateMultiColor } from '../util/Math' +import IntervalTree from 'node-interval-tree' +import { SimilarityTile } from './SimilarityTile' +import { SimilarityTileDefinitions } from '../models/SimilarityTileDefinitions' +import { GUI } from '../ui/Dat' + +const config = { + color: [187, 255, 255], + barOverlayColor: [0, 240, 233], + blend: 'OVERLAY', + alpha: 0.5, + heatmapPow: 1.19, +} +export const SimilarityLayerConfig = config + +let simFolder +if (GUI) { + simFolder = GUI.addFolder('similarity') + // + simFolder.addColor(config, 'color') + simFolder.addColor(config, 'barOverlayColor') + simFolder.add(config, 'heatmapPow', 0.1, 4) +} + +const MAX_DURATION = 440000 + +window.BLEND_MODES = BLEND_MODES + +export class SimilarityLayer extends Container { + constructor(infoBubble) { + super() + this.infoBubble = infoBubble + this.similarityData = [] + this.heatmapData = [] + this.graphics = new Graphics() + this.graphicsHeatmap = new Graphics() + + this.graphicsHeatmap.position.y = 0 + + this.graphics.blendMode = BLEND_MODES.ADD + this.addChild(this.graphics) + this.addChild(this.graphicsHeatmap) + // window.graphics = this.graphics + // + if (GUI) { + simFolder + .add(config, 'blend', Object.keys(BLEND_MODES)) + .name('blend mode color') + .onChange((value) => { + this.graphics.blendMode = BLEND_MODES[value] + }) + simFolder.add(config, 'alpha', 0, 1) + } + + this._intervalTree = new IntervalTree() + this._tileMap = new Map() + + // this.getSimilarityData = throttle(() => { + // this.syncWindowToSpectrogramTime() + // this.getSimilarityForCurrentWindow() + // }, 500) + this.getSimilarityData = () => { + this.getSimilarityForCurrentWindow() + } + + this.tileDefinitions = new SimilarityTileDefinitions() + + this.heatmap = { + renderHeight: 0, + position: { + y: 0, + }, + } + + this._heatmapStart = [31, 31, 53] + this._heatmapEnd = [30, 30, 122] + + if (GUI) { + simFolder.addColor(this, 'heatmapStart') + simFolder.addColor(this, 'heatmapEnd') + } + + this._updateHeatmapColorMap() + + // Setup mouse interaction with tooltip (infobubble) + this.interactive = true + this.on('pointerdown', () => {}) + this.on('mouseover', (e) => { + this.mouseOver(e) + }) + this.on('mouseout', () => { + this.mouseOut() + }) + this.on('mousemove', (e) => { + this.mouseMove(e) + }) + } + + mouseOver() { + this.hover = true + } + mouseMove(e) { + if (this.hover) { + this.infoBubble.coords = { + x: e.data.global.x + 10, + y: e.data.global.y + 10, + } + this.infoBubble.text = `At this close-up zoom level, repeated sounds are highlighted + to help you visualize the patterns of whale songs. + ` + } + } + + mouseOut() { + this.hover = false + this.infoBubble.open = false + } + + async getSimilarityForCurrentWindow() { + if (!Globals.timeManager || !Globals.spectrogram.tileDefinitions) { + return + } + + if (Globals.timeManager.idealWindowDuration > MAX_DURATION) { + this.similarityData = [] + return + } + + const audioPeriods = Globals.timeManager.getAudioPeriods( + Globals.timeManager.windowStartTime, + Globals.timeManager.windowEndTime + ) + let tileData = [] + for (let period of audioPeriods) { + let t = this.tileDefinitions.getRange( + Math.max(Globals.timeManager.windowStartTime, period.timeStart), + Math.min(Globals.timeManager.windowEndTime, period.timeEnd), + 4 + ) + tileData = tileData.concat(t) + } + + const tiles = [] + tileData.forEach((desc) => { + if (!this._tileMap.has(desc.file)) { + const tile = new SimilarityTile(desc.file, desc.time, desc.duration) + this._tileMap.set(desc.file, tile) + tiles.push(tile) + } else { + // last touched time used for garbage collection to find old data + const tile = this._tileMap.get(desc.file) + tile.touched = new Date() + tiles.push(tile) + } + }) + + let similarityData = [] + tiles.forEach((tile) => { + similarityData = [ + ...similarityData, + ...tile.getSimilarity(Globals.timeManager.windowStartTime, Globals.timeManager.windowEndTime), + ] + }) + + this.similarityData = similarityData + + this.heatmapData = [] + tiles.forEach((tile) => { + this.heatmapData = [...this.heatmapData, ...tile.getAverage()] + }) + } + + update() { + this.getSimilarityData() + this.render() + this.cleared = false + } + + clear() { + if (!this.cleared) { + this.cleared = true + this.graphics.clear() + this.graphicsHeatmap.clear() + } + } + + _updateHeatmapColorMap() { + this.heatmapColorMap = interpolateMultiColor([this._heatmapStart, this._heatmapEnd]) + } + + set heatmapStart(c) { + this._heatmapStart = c + this._updateHeatmapColorMap() + } + set heatmapEnd(c) { + this._heatmapEnd = c + this._updateHeatmapColorMap() + } + get heatmapStart() { + return this._heatmapStart + } + get heatmapEnd() { + return this._heatmapEnd + } + + render() { + if (!Globals.spectrogram) { + return + } + + this.graphics.clear() + this.graphicsHeatmap.clear() + + const averageWhale = Globals.spectrogram.classificationLayer.getAverageClassification() + + //scale log between 0.1 - 1 + const scaledWhale = Math.pow(averageWhale, 0.5) * 0.9 + 0.1 + const scaling = scaledWhale //scaleToRange(currentDur, MIN_DURATION, MAX_DURATION, 1, 0) * scaledWhale + + const spectrogramHeight = Globals.spectrogram.height + const spectroOverlayColor = utils.rgb2hex(config.color.map((v) => v / 255)) + this.drawSimilarity(scaling, 0, spectrogramHeight, spectroOverlayColor, this.graphics) + + //draw the heatmap + this.drawHeatmap(scaling) + + //draw another box over the top of the bar + const barOverlayColor = utils.rgb2hex(config.barOverlayColor.map((v) => v / 255)) + this.drawSimilarity( + scaling, + this.heatmap.position.y - this.position.y - 5, + 5 + this.heatmap.renderHeight, + barOverlayColor, + this.graphicsHeatmap + ) + } + + drawSimilarity(scaling, y, height, color, g) { + if (this.similarityData.length) { + const firstItem = this.similarityData[0] + // const color = utils.rgb2hex(config.color.map(v => v / 255)) + + const windowStartTime = Globals.timeManager.windowStartTime + const windowEndTime = Globals.timeManager.windowEndTime + + this.similarityData.forEach((item, index) => { + const itemStartTime = firstItem.startTime + index * firstItem.duration + const itemEndTime = firstItem.startTime + (index + 1) * firstItem.duration + + if (itemEndTime > windowStartTime && itemStartTime < windowEndTime) { + const itemStartTimePx = Globals.timeManager.timeToPx(itemStartTime) + const itemEndTimePx = Globals.timeManager.timeToPx(itemEndTime) + const value = item.value + + // const height = Globals.spectrogram.height + const width = itemEndTimePx - itemStartTimePx + + const val = Math.pow(clamp(value * scaling, 0, 1), 0.8) + // + //skip values that are too small + if (val > 0.01) { + if (0 <= itemEndTimePx && itemStartTimePx <= window.innerWidth) { + const xVal = itemStartTimePx + // this.graphics.beginFill(utils.rgb2hex([0, 240/255, 233/255]), val * 0.5) + g.beginFill(color, val * config.alpha) + g.drawRect(xVal, y, width, height) + g.endFill() + } + } + } + }) + } + } + + drawHeatmap(scaling) { + if (this.heatmapData.length) { + const firstItem = this.heatmapData[0] + + const windowStartTime = Globals.timeManager.windowStartTime + const windowEndTime = Globals.timeManager.windowEndTime + + this.heatmapData.forEach((item, index) => { + const itemStartTime = firstItem.startTime + index * firstItem.duration + const itemEndTime = firstItem.startTime + (index + 1) * firstItem.duration + + if (itemEndTime > windowStartTime && itemStartTime < windowEndTime) { + const itemStartTimePx = Globals.timeManager.timeToPx(itemStartTime) + const itemEndTimePx = Globals.timeManager.timeToPx(itemEndTime) + + const value = item.value + + const width = itemEndTimePx - itemStartTimePx + const height = this.heatmap.renderHeight + const val = Math.pow(clamp(value * scaling, 0, 1), config.heatmapPow) + + if (0 <= itemEndTimePx && itemStartTimePx <= window.innerWidth) { + const xVal = itemStartTimePx + const rgb = this.heatmapColorMap(val) + for (let i = 0; i < rgb.length; i++) { + rgb[i] = rgb[i] > 255 ? 255 : rgb[i] + } + const hex = utils.rgb2hex([rgb[0] / 255, rgb[1] / 255, rgb[2] / 255]) + this.graphicsHeatmap.beginFill(hex) + this.graphicsHeatmap.drawRect(xVal, this.heatmap.position.y - this.position.y, width, height) + this.graphicsHeatmap.endFill() + } + } + }) + } + } +} diff --git a/client/src/components/SimilarityTile.js b/client/src/components/SimilarityTile.js new file mode 100644 index 0000000..12fe2ab --- /dev/null +++ b/client/src/components/SimilarityTile.js @@ -0,0 +1,172 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { GUI } from '../ui/Dat' + +const config = { + threshold: 0.03, + alpha: 0.9, + scaling: 2, + offsetTime: 0, + offsetPixels: 0, +} + +if (GUI) { + GUI.add(config, 'threshold', 0, 1).name('sim thresh') + GUI.add(config, 'alpha', 0, 1).name('sim smoothing') + GUI.add(config, 'scaling', 0.1, 3).name('sim scaling') + GUI.add(config, 'offsetTime', -2000, 2000).name('sim offset') + GUI.add(config, 'offsetPixels', -10, 10).name('offset px') +} + +export class SimilarityTile { + constructor(url, startTime, duration) { + this._url = url + this.startTime = startTime + this.duration = duration + this.touched = new Date() + this.loaded = false + this.loading = false + this.load() + } + + async load() { + if (this.loading) { + return + } + this.loading = true + + const image = new Image() + image.crossOrigin = 'anonymous' + image.src = this._url + try { + await new Promise((done, error) => { + image.onload = done + image.onerror = error + }) + } catch (e) { + return + } + + const canvas = document.createElement('canvas') + canvas.width = image.width + canvas.height = image.height + const context = canvas.getContext('2d') + context.drawImage(image, 0, 0, image.width, image.height) + const imageData = context.getImageData(0, 0, image.width, image.height) + // create the 2d matrix with all the data + const matrix = [] + for (let column = 0; column < imageData.width; column++) { + const subarray = [] + for (let row = 0; row < imageData.height; row++) { + const index = row * imageData.width * 4 + column * 4 + subarray.push(imageData.data[index] / 255) + } + matrix.push(subarray) + } + + this.imageData = { + data: matrix, + // the matrix has been rotated + width: imageData.height, + height: imageData.width, + } + + this._generateAverage() + + this.loaded = true + this.loading = false + } + + _generateAverage() { + // const centerColumnIndex = Math.floor(0.5 * this.imageData.width) + const averageColumn = this.imageData.data.map((row) => { + // return Math.max(...row) + return (row.reduce((a, b) => a + b, 0) / row.length) * 100 + }) + + const tickDuration = (1 / averageColumn.length) * this.duration + this.averageColumn = averageColumn.map((value, index) => { + const rowTime = (index / averageColumn.length) * this.duration + this.startTime + return { + value, + startTime: rowTime, + duration: tickDuration, + } + }) + } + + _getTimeOffset(offset) { + // the y row as a percentage of the total time + const { width, height } = this.imageData + const percentageHeight = height * offset + const row = Math.floor(percentageHeight) + // const pixelOffset = percentageHeight - row + const pixelOffset = percentageHeight - row + + if (offset > 1 || offset < 0 || isNaN(row) || !this.loaded) { + return [] + } + + const offsetPixels = Math.floor(config.offsetPixels) + const subarray = this.imageData.data[row + offsetPixels] + const nextRow = this.imageData.data[row + offsetPixels + 1] + + if (!subarray || !nextRow) { + return [] + } + //interpolate between the two rows using the pixel offset + const resultingArray = subarray.map((value, index) => { + return pixelOffset * nextRow[index] + (1 - pixelOffset) * value + }) + + this.currentRow = resultingArray + + const imageHeightDuration = this.duration + const imageWidthDuration = this.duration * (width / height) + // const imageWidthDuration = 120000 + + const offsetTime = offset * imageHeightDuration + this.startTime + // const threshValue = config.threshold + + const tickDuration = (1 / this.currentRow.length) * imageWidthDuration + const retTimes = this.currentRow.map((value, index) => { + // const indexTime = (((index - pixelOffset - 1) / width) - 0.5) * imageWidthDuration + offsetTime + const indexTime = ((index - 1) / width - 0.5) * imageWidthDuration + offsetTime + return { + startTime: indexTime + tickDuration, + duration: tickDuration, + value: Math.pow(value, config.scaling), + } + }) + return retTimes + } + + getSimilarity(windowStart, windowEnd) { + if (!this.loaded) { + return [] + } + const centerPoint = (windowStart + windowEnd) / 2 + const offset = (centerPoint - this.startTime) / this.duration + const data = this._getTimeOffset(offset) + return data + } + + getAverage() { + if (!this.loaded) { + return [] + } + return this.averageColumn + } +} diff --git a/client/src/components/Spectrogram.js b/client/src/components/Spectrogram.js new file mode 100644 index 0000000..94326df --- /dev/null +++ b/client/src/components/Spectrogram.js @@ -0,0 +1,451 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Graphics } from 'pixi.js' +import { SpectrogramTileDefinitions } from '../models/SpectrogramTileDefinitions' +import { TileImage } from '../components/TileImage' +import { clamp, logScale } from '../util/Math' +import { Axis, majorTickHeight, minorTickHeight } from './Axis' +import { Layer } from './Layer' +import { AnnotationLayer } from './AnnotationLayer' +import { TimeManager } from '../models/TimeManager' +import { LocationsModel } from '../models/Locations' +import { Globals, Config, Device } from '../globals' + +import { ClassificationHeatmapLayer } from './ClassificationHeatmapLayer' +import { SimilarityLayer } from '../components/SimilarityLayer' + +import { TileDefinitions } from '../models/TileDefinitions' +import { Frequencies } from './Frequencies' +import { GapLayer } from './GapLayer' + +const minTileZoom = -6 +const maxTileZoom = 8 +const preventGarbageCollectBelow = 1 + +export class Spectrogram { + constructor(container) { + this._windowDuration = 0 + this.prevWindowDuration = 0 + this.container = container + this.layers = [] + + this.currentTiles = {} + this._duration = 0 + this._time = Globals.controls.position + Globals.currentLocation.startTime + this.prevTime = this.time + this._location = null + + this.annotationLayer = new AnnotationLayer(this, Globals.controls.$$comment, Globals.controls.$$infoBubble) + this.container.parent.addChild(this.annotationLayer) + + this.tileWindowChanging = false + + this.similarityLayer = new SimilarityLayer(Globals.controls.$$infoBubble) + this.container.parent.addChild(this.similarityLayer) + + this.classificationLayer = new ClassificationHeatmapLayer(Globals.controls.$$infoBubble) + window.classificationLayer = this.classificationLayer + this.container.parent.addChild(this.classificationLayer) + + this.axis = new Axis(this) + this.container.parent.addChild(this.axis) + + this.gapLayer = new GapLayer() + this.container.parent.addChild(this.gapLayer) + + this.loaded = false + this.dirty = false + + this.frequencies = new Frequencies(this) + this.container.parent.addChild(this.frequencies) + + this.paddingBandaid = new Graphics() + this.paddingBandaid + .beginFill(Config.bgColor) + .drawRect(0, 0, window.innerWidth, Config.scalePadding) + .endFill() + this.container.parent.addChild(this.paddingBandaid) + + this.moveToPlayhead = new Graphics() + this.moveToPlayhead.alpha = 0.4 + + this.container.parent.addChild(this.moveToPlayhead) + + this.container.interactive = true + this.container.current = 'pointer' + + this.container.on('mouseover', (e) => { + this.mouseHover = true + }) + this.container.on('mouseout', (e) => { + this.mousePlayheadPosition = 0 + this.mouseHover = false + }) + this.container.on('mousemove', (e) => { + if (this.mouseHover) { + this.mousePlayheadPosition = e.data.global.x + } + }) + } + + async updateLocationData() { + this.loaded = false + this.locationModel = await LocationsModel.get(this.location) + if (!this.tileDefinitions) { + this.tileDefinitions = new SpectrogramTileDefinitions(this.locationModel.name, this.locationModel.startTime) + } else { + this.tileDefinitions.location = this.locationModel.name + this.tileDefinitions.startTime = this.locationModel.startTime + } + + // TODO: clear out any previous layers and tiles cache. + + if (!this.annotationLayer.annotationsData) { + await this.annotationLayer.load() + } + this.annotationLayer.location = Globals.currentLocation + + for (let z = minTileZoom; z <= maxTileZoom; z++) { + const tileDur = TileDefinitions.zoomLevelDuration(z) + const layer = new Layer( + tileDur, + this.locationModel.startTime, + this.locationModel.endTime - this.locationModel.startTime, + z - minTileZoom + ) + this.container.addChild(layer) + this.layers.push(layer) + } + + // load most zoomed-out layer + let preloadTiles = this.tileDefinitions.getRange( + this.locationModel.startTime, + this.locationModel.endTime, + minTileZoom + ) + + preloadTiles = preloadTiles.filter((tile) => { + return !this.locationModel.timerangeInGap(tile.time, tile.time + tile.duration) + }) + + // Preload zoomed out layer + const zoomedOutLayer = this.layers[0] + preloadTiles.forEach((tile) => { + if (!this.locationModel.timerangeInGap(tile.time, tile.time + tile.duration)) { + const sprite = new TileImage(tile, zoomedOutLayer) + this.currentTiles[tile.file] = sprite + zoomedOutLayer.addChild(sprite) + } + }) + } + + get height() { + if (Globals.fullscreen) { + return window.innerHeight - majorTickHeight + } + return this.container.scale.y * Config.tileHeight + } + + set location(location) { + this._location = location + this.updateLocationData() + this.update() + } + + get location() { + return this._location + } + + set duration(duration) { + this._duration = parseFloat(duration) + this.dirty = true + this.update() + } + + get duration() { + return this._duration + } + + set time(time) { + // Timeline starts in the center of the window + // this._time = parseFloat(time) - this.duration / 2 + this._time = parseFloat(time) + if (this.prevTime !== this.time) { + this.dirty = true + this.prevTime = this.time + } + this.update() + } + + get time() { + return this._time + } + + /** + * Returns an object with the startTime and endTime + * of the visible spectrogram in the window + */ + get windowDuration() { + return this._windowDuration + } + + /** + * Returns the resultion (ms / px) of the window + */ + get windowResolution() { + return this.windowDuration / window.innerWidth + } + + update() { + const resolutionIndex = this.closestZoomLevel(this.duration) - minTileZoom + // The current layer we're going to update tiles on: + const layer = this.layers[resolutionIndex] + if (!layer) return + this.currentLayer = layer + this._windowDuration = TimeManager.calcIdealWindowDuration(this.duration) + if (this.windowDuration !== this.prevWindowDuration) { + Globals.events.emit('zoom', { + detail: 1 - this.duration / parseFloat(Config.maxDuration), + }) + } + this.prevWindowDuration = this.windowDuration + + Globals.timeManager.setWindowDuration(this.windowDuration) + + // Scale the height of the group to match the + // duration scale —> the more zoomed in the user is, + // the taller it gets + + if (Globals.fullscreen) { + this.container.scale.y = (window.innerHeight - majorTickHeight - minorTickHeight) / Config.tileHeight + this.container.y = 0 + } else { + let scale = logScale( + this.duration, + Config.tileScaleMax, + Config.tileScaleMin, + Config.minDuration, + Config.maxDuration + ) + scale = clamp(scale, Config.tileScaleMin, Config.tileScaleMax) + this.container.scale.y = scale + + // Center container based off of scale + this.container.y = window.innerHeight / 2 - this.height / 2 + } + + // Load tiles a bit off the sides of the viewport to prevent too much blurriness + const loadingWindowStart = Globals.timeManager.windowStartTime - Globals.timeManager.idealWindowDuration + const loadingWindowEnd = Globals.timeManager.windowEndTime + Globals.timeManager.idealWindowDuration + // Start hiding/removing all the tiles. + // Note, the next loop will override this + // and show/keep tiles that are still in view. + Object.keys(this.currentTiles).forEach((tileImage) => { + const tile = this.currentTiles[tileImage] + // Keep the most zoomed-out layer + if (tile.layer.layerIndex) { + // Don't garbage collect the lower zoom levels + // unless they are out of the viewing range + if ( + tile.resolution >= preventGarbageCollectBelow || + !tile.isInRange(loadingWindowStart, loadingWindowEnd) + ) { + tile.remove() + } else { + tile.hide() + } + } + }) + + // Fade in or keep tiles that are in view. + // We'll only need to do this for layers that aren't the most zoomed-out layer + let allLoaded = true + if (resolutionIndex) { + const audioPeriods = Globals.timeManager.getAudioPeriods(loadingWindowStart, loadingWindowEnd) + // Give us the tiles for this range. + // It returns the data for the tiles, not actual objects. + let tiles = [] + for (let period of audioPeriods) { + let t = this.tileDefinitions.getRange( + Math.max(loadingWindowStart, period.timeStart), + Math.min(loadingWindowEnd, period.timeEnd), + // To account for the negative indices + resolutionIndex + minTileZoom + ) + tiles = tiles.concat(t) + } + + if (tiles.length < 75) { + tiles.forEach((item) => { + if (this.currentTiles[item.file]) { + const sprite = this.currentTiles[item.file] + if (resolutionIndex + minTileZoom === sprite.resolution && !sprite.loaded) { + allLoaded = false + } + sprite.keep() + } else { + // console.log(item, this.locationModel.timeInGap(item.time)) + + allLoaded = false + // Debounce new tile creation + if (!this.tileWindowChanging) { + const newSprite = new TileImage(item, layer) + this.currentTiles[item.file] = newSprite + layer.addChild(newSprite) + newSprite.on('TileImageRemoved', (target) => { + layer.removeChild(target) + target.destroy(true) + delete this.currentTiles[target.file] + }) + } + } + }) + } + } + + // This logic is to keep another layer visible until + // the new layer's tiles finish loading + if (allLoaded === true) { + // Record the time this layer was fully loaded + layer.fullCoverageTime = new Date() + if (!this.loaded) { + this.loaded = true + Globals.events.emit('load') + + // Feature detects Navigation Timing API support. + if (window.performance) { + // Gets the number of milliseconds since page load + // (and rounds the result since the value must be an integer). + var timeSincePageLoad = Math.round(performance.now()) + + // Sends the timing event to Google Analytics. + gtag('event', 'timing_complete', { + name: 'spectrogram_load', + value: timeSincePageLoad, + event_category: 'Initial Spectrogram Load', + }) + } + } + } + // Get closest layer with the most coverage of the window area + const newestFullyLoadedLayer = this.layers.reduce((prev, current) => + prev.fullCoverageTime > current.fullCoverageTime ? prev : current + ) + + const heatmapHeight = this.height * 0.08 + + // + // update position of heatmaps + + const beneathSpectrogram = Globals.fullscreen + ? this.height - 1 - minorTickHeight + : this.height - 1 + (window.innerHeight - this.height) / 2 + // If zoomed far enough in, show the similarity layer, and hide the classification layer + if (this.duration < Config.similarityBreakpoint) { + this.classificationLayer.hide() + + this.similarityLayer.position.y = this.container.position.y + this.similarityLayer.heatmap.renderHeight = heatmapHeight + this.similarityLayer.heatmap.position.y = beneathSpectrogram + } else { + this.classificationLayer.show() + this.classificationLayer.position.y = beneathSpectrogram + } + + // Only update similarity layer when classification layer is fully visible (covering) + if (!this.classificationLayer.isFullyVisible()) { + this.similarityLayer.update() + } else { + this.similarityLayer.clear() + } + + this.classificationLayer.renderHeight = heatmapHeight + this.classificationLayer.update() + + // Gap layer + this.gapLayer.renderHeight = this.height + heatmapHeight + this.gapLayer.renderHeight += Globals.fullscreen ? -1 - minorTickHeight : 3.5 + this.gapLayer.position.y = Globals.fullscreen ? 0 : (window.innerHeight - this.height) / 2 - Config.scalePadding + this.gapLayer.update() + + // Update axis position + this.axis.position.y = beneathSpectrogram + heatmapHeight + this.axis.update() + + if (!Globals.fullscreen) { + this.annotationLayer.visible = true + this.annotationLayer.update() + } else { + this.annotationLayer.visible = false + } + this.frequencies.update() + + if (Globals.fullscreen) { + this.paddingBandaid.visible = false + } else { + this.paddingBandaid.visible = true + this.paddingBandaid.y = (window.innerHeight - this.height) / 2 - Config.scalePadding + } + + this.moveToPlayhead.clear() + if (this.mousePlayheadPosition && !Device.isTouch) { + this.moveToPlayhead + .lineStyle(1, 0xffffff) + .moveTo(this.mousePlayheadPosition, Globals.fullscreen ? 0 : (window.innerHeight - this.height) / 2) + .lineTo( + this.mousePlayheadPosition, + this.height + (Globals.fullscreen ? -minorTickHeight : (window.innerHeight - this.height) / 2) + ) + } + // tick will render the fade-in/out + Object.keys(this.currentTiles).forEach((tileImage) => { + const tile = this.currentTiles[tileImage] + if (!allLoaded && tile.layer.layerIndex && tile.layer.layerIndex === newestFullyLoadedLayer.layerIndex) { + // We're going to hang on to any tiles from + // the recent layer that we've already loaded + tile.keep() + } + tile.tick(this.duration) + }) + this.dirty = false + } + + getCurrentZoomLevel() { + return this.closestZoomLevel(this.duration) + } + + closestZoomLevel(ms) { + return clamp(Math.round(TileDefinitions.getZoomLevel(ms)), minTileZoom, maxTileZoom) + } + + closestTimeInterval(ms) { + return TileDefinitions.zoomLevelDuration(this.closestZoomLevel(ms)) + } + + updateTileHeights() { + Object.keys(this.currentTiles).forEach((tileImage) => { + this.currentTiles[tileImage].updateHeight() + }) + } + + resize() { + this.axis.resize() + this.paddingBandaid + .clear() + .beginFill(Config.bgColor) + .drawRect(0, 0, window.innerWidth, Config.scalePadding) + .endFill() + } +} diff --git a/client/src/components/TileImage.js b/client/src/components/TileImage.js new file mode 100644 index 0000000..9816551 --- /dev/null +++ b/client/src/components/TileImage.js @@ -0,0 +1,279 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Sprite, Texture, Rectangle, Graphics } from 'pixi.js' +import { Config, Globals } from '../globals' + +export class TileImage extends Sprite { + constructor(tile, layer) { + super() + this.file = tile.file + this.time = tile.time + this.duration = tile.duration + this.resolution = tile.tileZoomLevel + this.index = tile.index + this.layer = layer + this.alpha = 0 + this.removing = false + this.hiding = false + this.loaded = false + this.loadImage(this.file) + } + + set resolution(resolution) { + this._resolution = resolution + } + + get resolution() { + return this._resolution + } + + keep() { + this.removing = false + this.hiding = false + } + + remove() { + this.hide() + this.removing = true + } + + hide() { + this.hiding = true + } + + tick(duration) { + if (this.loaded) { + const speed = 0.04 + if (!this.hiding) { + if (this.alpha < 1) { + this.alpha += speed + } else if (this.alpha > 1) { + this.alpha = 1 + } + } else { + if (this.alpha > 0) { + this.alpha -= speed / 3 + } else { + this.handleRemove() + } + } + // position and scale tile if it's loaded successfully and onscreen + if ( + this.transform && + this.time < Globals.timeManager.windowEndTime && + this.time + this.duration > Globals.timeManager.windowStartTime + ) { + this.visible = true + this.x = Globals.timeManager.timeToPx(this.time) + + // Is tile in a gap? + const gaps = Globals.timeManager.getGaps(this.time, this.time + this.duration) + if (gaps.length) { + // If tile is entirely in a gap, hide it. + if (gaps.find((g) => g.timeStart <= this.time && g.timeEnd >= this.time + this.duration)) { + this.visible = false + } + // If tile is starting in a gap, we need to move it + else if (Globals.timeManager.gapAtTime(this.time)) { + const gap = gaps.find((g) => g.timeStart <= this.time && g.timeEnd >= this.time) + if (gap) { + const offset = gap.timeEnd - this.time + const px = Globals.timeManager.timeToPx(gap.timeEnd) + this.x = px - offset / Globals.timeManager.resolution + } + } + } + this.width = (this.layer.tileDuration / duration) * Config.tileWidth + } else { + this.visible = false + } + + if (Config.debug && this.texture && this._trimLine) { + this._trimLine.clear() + } + const gaps = Globals.timeManager.getGaps(this.time, this.time + this.duration) + if (gaps.length && this.texture) { + // Check if theree is a gap at thee beginning + if (Globals.timeManager.gapAtTime(this.time)) { + // Trim the texture + const gapEnd = gaps[gaps.length - 1].timeEnd - this.time + const pct = gapEnd / this.duration + this.texture.trim = new Rectangle( + this.texture.orig.width * pct, + 0, + this.texture.orig.width * (1 - pct), + this.texture.orig.height + ) + + this.texture.frame = new Rectangle( + this.texture.orig.width * pct, + 0, + this.texture.orig.width * (1 - pct), + this.texture.orig.height + ) + // eslint-disable-next-line no-underscore-dangle + this.texture._updateUvs() + + if (Config.debug && this._trimLine) { + this._trimLine.lineStyle(15, 0xffffff) + this._trimLine + .moveTo(0, this.textureHeight / 2 - 40) + .lineTo(this.textureWidth * pct, this.textureHeight / 2 - 40) + } + } + // Similarily, check if there is a gap at the end, and crop + else if (Globals.timeManager.gapAtTime(this.time + this.duration)) { + const gapStart = gaps[0].timeStart - this.time + const pct = gapStart / this.duration + this.texture.trim = new Rectangle(0, 0, this.texture.orig.width * pct, this.texture.orig.height) + this.texture.frame = new Rectangle(0, 0, this.texture.orig.width * pct, this.texture.orig.height) + // eslint-disable-next-line no-underscore-dangle + this.texture._updateUvs() + if (Config.debug && this._trimLine) { + this._trimLine.lineStyle(15, 0xffffff) + this._trimLine + .moveTo(0, this.textureHeight / 2 + 40) + .lineTo(this.textureWidth * pct, this.textureHeight / 2 + 40) + } + } + } + } else { + this.handleRemove() + } + } + + handleRemove() { + if (this.removing) { + this.emit('TileImageRemoved', this) + } + } + + updateDebug() {} + + async loadImage(file) { + try { + const img = await TileImage.loadImageUrl(file) + + this.texture = img + this.alpha = 0 + this.loaded = true + + this.textureWidth = this.texture.orig.width + this.textureHeight = this.texture.orig.height + + if (Config.debug) { + this.metadata = await TileImage.loadMetadata(file) + } + + if (Config.debug) { + this._line = new Graphics() + this._line.position.set(0, 0) + this._line + .lineStyle(2, 0xffffff) + .moveTo(0, 0) + .lineTo(this.textureWidth, this.textureHeight) + .moveTo(this.textureWidth, 0) + .lineTo(0, this.textureHeight) + + .moveTo(0, 0) + .lineTo(0, this.textureHeight) + .moveTo(this.textureWidth, 0) + .lineTo(this.textureWidth, this.textureHeight) + + this.addChild(this._line) + + this._trimLine = new Graphics() + this._trimLine.position.set(0, 0) + this.addChild(this._trimLine) + } + + this.height = Config.tileHeight + // console.log(this.analyze(this)) + } catch (err) { + // console.error(err) + // Even if it returns an error, + // we want to know it's done + this.loaded = true + } + this.updateDebug() + this.emit('TileImageLoaded') + } + + static async loadMetadata(url) { + return fetch(url, { method: 'HEAD' }).then((res) => { + const metadata = {} + if (res.status == 200) { + res.headers.forEach(function(value, name) { + if (name.includes('x-goog-meta-')) { + name = name.replace('x-goog-meta-', '') + metadata[name] = value + } else if (name == 'last-modified') { + metadata[name] = value + } + }) + return metadata + } else { + throw new Error('Not found') + } + }) + } + + static async loadImageUrl(url) { + return new Promise((done, reject) => { + const img = new Image() + img.crossOrigin = 'Anonymous' + img.addEventListener('load', () => { + const texture = Texture.from(img) + done(texture) + }) + img.addEventListener('error', (err) => { + reject(err) + }) + img.src = url + }) + } + + // Analyze image pixels (currently not used or run) + analyze() { + const pixels = Globals.pixiApp.renderer.plugins.extract.pixels(this) + + const numBins = 30 + const bins = new Array(numBins) + for (let i = 0; i < numBins; i++) { + bins[i] = 0 + } + const skip = 1 + + for (let i = 0; i < pixels.length; i += 4 * skip) { + let p = pixels[i] + p /= 256 + p *= numBins + p = Math.floor(p) + bins[p] += 1 + } + + for (let i = 0; i < numBins; i++) { + bins[i] /= pixels.length / (4 * skip) + } + } + + isInRange(start, end) { + return this.time < end && this.time + this.duration > start + } + + updateHeight() { + this.height = Config.tileHeight + } +} diff --git a/client/src/export/Audio.js b/client/src/export/Audio.js new file mode 100644 index 0000000..42f5eb0 --- /dev/null +++ b/client/src/export/Audio.js @@ -0,0 +1,49 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import {getFiles} from '../network/Files' +import Tone, {Buffer} from 'tone' +import toWav from 'audiobuffer-to-wav' + +/** + * Export the audio files in a selected range + */ +export async function exportAudio(location, startTime, duration){ + const files = await getFiles(location, startTime, duration) + const prefix = 'https://storage.googleapis.com/deepblue-transcoded-audio/' + const audioBuffers = await Promise.all(files.map(f => Buffer.fromUrl(prefix + f.filename))) + const sampleRate = Tone.context.sampleRate + const outputBuffer = Tone.context.createBuffer(1, ((endTime - startTime) / 1000) * sampleRate, sampleRate) + const outputData = outputBuffer.getChannelData(0) + let arrayPosition = 0 + console.log(startTime - files[0].startTime) + audioBuffers.forEach((buffer, index) => { + const startOffset = Math.max(((startTime - files[index].startTime) / 1000) * sampleRate, 0) + const addedSamples = buffer.getChannelData(0).length - startOffset + outputBuffer.copyToChannel(buffer.getChannelData(0).subarray(startOffset), 0, arrayPosition) + arrayPosition += addedSamples + }) + const filename = `${location}_${startTime}_${endTime}` + const wave = toWav(outputBuffer) + const blob = new Blob([wave], { type: "audio/wav" }); + const blobUrl = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + //download all the files + a.href = blobUrl; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(blobUrl); +} + +// exportAudio('Hawaii19', 1422753662000, 120000) \ No newline at end of file diff --git a/client/src/globals.js b/client/src/globals.js new file mode 100644 index 0000000..f766f27 --- /dev/null +++ b/client/src/globals.js @@ -0,0 +1,257 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { EventEmitter } from 'events' +import { css } from 'lit-element' +import { TileDefinitions } from './models/TileDefinitions' +import { tickConfig } from './components/Axis' +const queryParams = getUrlParams(document.location.search) +const ua = navigator.userAgent.toLowerCase() +const isTouch = 'ontouchstart' in document.documentElement + +const anchorParams = parseUrlAnchor(document.location.hash) +const cache = !queryParams.nocache + +window.onhashchange = function() { + hashChanged(window.location.hash) +} + +/** + * + * Helpful device variables + * + * */ +export const Device = { + isTouch, + android: ua.indexOf('android') > -1, + ios: /iPad|iPhone|iPod/.test(ua) && !window.MSStream, + mobile: window.innerWidth < 768 && isTouch, + tablet: window.innerWidth >= 768 && isTouch, + tabletLandscape: window.innerWidth >= 768 && window.innerHeight <= 768 && isTouch, + desktop: window.innerWidth >= 992 && !isTouch, + laptop: window.innerHeight <= 800 && !isTouch, +} + +/** Store defaults here */ +export const Config = { + defaultLocation: queryParams.location || 'Hawaii', + // defaultLocation: 'HAWAII10', + // defaultPosition: 0, + defaultPosition: anchorParams.position || parseFloat(queryParams.position) || 1423645400000, + defaultDuration: anchorParams.duration || parseFloat(queryParams.duration) || 236034, + initialZoom: 0.35, + annotationUrl: queryParams.liveAnnotations + ? 'https://us-central1-gweb-deepblue.cloudfunctions.net/annotations' + : '/assets/annotations.tsv', + minDuration: 14000, + maxDuration: 1000000000, + isCluster: true, + debug: queryParams.debug || false, + tileWidth: 512, + controlsHeight: 120, + similarityBreakpoint: 80000, + tileHeight: window.innerHeight > 800 && !Device.mobile ? 2048 : 1024, + tileScaleMin: 1 / 8, + tileScaleMax: 1 / 4, + spectrogramOffset: 25, + scalePadding: 5, + fullscreenAspect: 3 / 1, + apiPath: cache + ? 'https://ci-dot-gweb-deepblue.appspot.com/api/' + : 'https://us-central1-gweb-deepblue.cloudfunctions.net/', + time: parseFloat(queryParams.time) || null, + skipIntro: !!(queryParams.position || queryParams.duration || anchorParams.position || queryParams.skipIntro), + annotate: queryParams.liveAnnotations, + transitionTime: css`0.3s`, + bgColor: 0, + skipPeriods: [[1403884109820.9546, 1406553366476.4119]], + minStartTime: 1395705601000, + datgui: false, + authors: { + ann: { + id: 'ann', + name: 'Ann Allen', + image: '/assets/ann.jpg', + alt: 'Ann Allen', + title: 'Research Oceanographer', + blurb: + 'Hear firsthand from the research oceanographer who deploys HARPs and other types of underwater microphones as part of her work at NOAA Fisheries.', + }, + matt: { + id: 'matt', + name: 'Matt Harvey', + image: '/assets/matt.jpg', + alt: 'Matt Harvey', + title: 'Software Engineer', + blurb: + 'Get a look at the sounds from the viewpoint of Matt Harvey, whose collaboration with Ann Allen on a machine learning model to recognize humpback whale sounds is part of what makes this website possible.', + }, + students: { + id: 'students', + name: 'Class Workshop', + image: '/assets/class.jpg', + alt: 'Class Workshop', + title: '7th Grade Class', + blurb: + 'Explore some of the questions, curiosities, and observations sparked during a one-day workshop with seventh grade students.', + }, + chris: { + id: 'chris', + name: 'Chris Clark', + image: '/assets/chris.jpg', + alt: 'Chris posing for camera', + title: 'Acoustic Biologist', + blurb: + 'In this tour, bioacoustic pioneer Chris Clark takes a look at an acoustic scene and prods you to think about what you see at different perspective levels — from one week to two minutes at a time.', + }, + annie: { + id: 'annie', + name: 'Annie Lewandowski', + image: '/assets/annie.jpg', + alt: 'Annie wearing orange glasses', + title: 'Composer and Whale Song Researcher', + blurb: + 'Explore the evolving musical structure of a humpback whale song through the eyes of musical composer/performer Annie Lewandowski.', + }, + david: { + id: 'david', + name: 'David Rothenberg', + image: '/assets/david.jpg', + alt: 'David Rothenberg', + title: 'Musician and Philosopher', + blurb: + 'See a humpback whale song through the eyes of composer and jazz clarinetist David Rothenberg, whose work explores the relationship between humanity and nature through music.', + }, + yotam: { + id: 'yotam', + name: 'Yotam Mann', + image: '/assets/yotam.jpg', + alt: 'Yotam Mann', + title: 'Creative Technologist', + blurb: + 'Find some neat and unusual aspects of these recordings pointed out by Yotam Mann, who helped build the site. Note the effects of passing ships, mysterious mechanical sounds, and more.', + }, + }, +} + +export function updateGlobals() { + Device.mobile = window.innerWidth < 768 && isTouch + Device.tablet = window.innerWidth >= 768 && isTouch + Device.tabletLandscape = window.innerWidth >= 768 && window.innerHeight <= 768 && isTouch + Device.desktop = window.innerWidth >= 992 && !isTouch + Device.laptop = window.innerHeight <= 800 && !isTouch + + Globals.fullscreen = window.innerWidth / window.innerHeight > Config.fullscreenAspect || window.innerHeight < 500 + tickConfig.ticks[0].yOffset = Globals.fullscreen ? 22 : 32 + tickConfig.ticks[0].textStyle.fontSize = Globals.fullscreen ? 13 : 15 + tickConfig.stickyXOffset = Globals.fullscreen ? 6 : 8 + + Config.spectrogramOffset = Device.tabletLandscape || Device.mobile ? 0 : 25 + Config.tileScaleMin = 1 / 8 + Config.tileScaleMax = 1 / 4 + if (window.innerHeight > 800 && !Device.mobile) { + Config.tileScaleMax = window.innerHeight < 950 ? 1 / 5 : 1 / 4 + Config.tileHeight = 2048 + } else { + Config.tileHeight = 1024 + } + if (Device.mobile || (Device.tablet && !Device.tabletLandscape)) { + Config.tileScaleMin = 1 / 9 + Config.tileScaleMax = 1 / 5 + } + if (Device.tabletLandscape) { + Config.tileScaleMax = 1 / 3.25 + Config.tileScaleMin = 1 / 6 + } + if (window.innerHeight >= 736 && Device.mobile) { + Config.tileScaleMax = window.innerHeight > 800 ? 1 / 3.25 : 1 / 3.75 + Config.tileScaleMin = 1 / 6 + } + if (window.innerHeight <= 667) { + Config.tileScaleMax = 1 / 6 + Config.tileScaleMin = 1 / 10 + } +} + +/** + * Global state the app uses to + * keep track of various times + * + * */ +export const Times = { + scaleStartTimeMs: 0, + currentTimeMs: 0, + scaleEndTimeMs: 0, + scaleStartTimeDate: new Date(), + currentTimeDate: new Date(), + scaleEndTimeDate: new Date(), +} + +export const Globals = { + player: null, + spectrogram: null, + controls: null, + events: new EventEmitter(), + currentLocation: null, + isScrubbing: false, + timeManager: null, + fullscreen: window.innerWidth / window.innerHeight > Config.fullscreenAspect || window.innerHeight < 500, +} + +function getUrlParams(search) { + const hashes = search.slice(search.indexOf('?') + 1).split('&') + return hashes.reduce((params, hash) => { + const [key, val] = hash.split('=') + return Object.assign(params, { [key]: decodeURIComponent(val) }) + }, {}) +} + +function hashChanged(hash) { + const timeTransition = 750 + const parsedHash = parseUrlAnchor(hash) + if (parsedHash) { + Globals.controls.tweens.position + .stop() + .to( + { + position: parsedHash.position - Globals.currentLocation.startTime, + }, + timeTransition + ) + .start() + } +} +function parseUrlAnchor(hash) { + const epochMatch = hash.match(/^#(\d{10})(?:z(-?\d+))?/i) + const isoMatch = Date.parse(hash.replace('#', '')) + let ret = {} + try { + if (epochMatch) { + ret.position = parseInt(epochMatch[1]) * 1000 + + if (epochMatch[2]) { + ret.duration = TileDefinitions.zoomLevelDuration(parseInt(epochMatch[2])) + } + } else if (isoMatch) { + ret.position = isoMatch + } + } catch (e) { + console.error(e) + } + return ret +} + +if (anchorParams.position) { + gtag('event', 'load_anchor_time', { value: anchorParams.position }) +} diff --git a/client/src/index.html b/client/src/index.html new file mode 100644 index 0000000..f3ce358 --- /dev/null +++ b/client/src/index.html @@ -0,0 +1,84 @@ + + + + + + + + + + Pattern Radio: Whale Songs + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/index.js b/client/src/index.js new file mode 100644 index 0000000..bbabb6e --- /dev/null +++ b/client/src/index.js @@ -0,0 +1,67 @@ +// Copyright 2019 Google Inc. +// +// 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. + +// import './generate/Generator' +import { Controls } from './ui/Controls' +import { Canvas } from './ui/Canvas' +import { LocationPicker } from './ui/LocationPicker' +import { Loader } from './ui/Loader' +import { TimeSlider } from './ui/TimeSlider' +import { Intro } from './ui/Intro' +import { Config, Globals } from './globals' +import { LocationsModel } from './models/Locations' +import './export/Audio' +import './style.scss' +import { TimeManager } from './models/TimeManager' + +// import './network/Installation' + +customElements.define('controls-element', Controls) +customElements.define('canvas-element', Canvas) +customElements.define('location-picker-element', LocationPicker) +customElements.define('loader-element', Loader) +customElements.define('intro-element', Intro) + +//const Locations = {} + +async function main() { + const $$body = document.querySelector('body') + Globals.timeManager = new TimeManager() + const $$controls = (Globals.controls = document.createElement('controls-element')) + // const $$locationPicker = document.createElement('location-picker-element') + // $$locationPicker.default = Config.defaultLocation + $$body.appendChild($$controls) + //$$body.appendChild($$locationPicker) + // $$locationPicker.locations = await LocationsModel.getLocationsData() + $$controls.location = await LocationsModel.get(Config.defaultLocation) + // $$locationPicker.addEventListener('change', async event => { + // $$controls.location = await LocationsModel.get(event.detail.value) + // }) +} +var hasTouched = false +document.body.addEventListener('touchstart', () => { + if (!hasTouched) { + hasTouched = true + document.body.classList.add('touch-device') + } +}) + +main() + +window.addEventListener('keydown', (e) => { + // Prevent space bar from clicking any of the buttons + if (e.keyCode === 32) { + e.preventDefault() + } +}) diff --git a/client/src/models/ClassificationDataTile.js b/client/src/models/ClassificationDataTile.js new file mode 100644 index 0000000..2a01545 --- /dev/null +++ b/client/src/models/ClassificationDataTile.js @@ -0,0 +1,59 @@ +// Copyright 2019 Google Inc. +// +// 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. + +/** + * A tile containing classification data for a specific time range + */ + +import { Globals } from '../globals' +import { TileDefinitions } from './TileDefinitions' + +export class ClassificationDataTile { + constructor(startTime, endTime, zoomLevel) { + this.startTime = startTime + this.endTime = endTime + this.zoomLevel = zoomLevel + this.loaded = false + } + + async fetch() { + ClassificationDataTile.fetchCount++ + const data = await Globals.currentLocation.getClassifications( + this.startTime, + this.endTime, + this.zoomLevel, + true + ) + + ClassificationDataTile.fetchCount-- + + this.loaded = true + let barWidth = TileDefinitions.zoomLevelDuration(this.zoomLevel) + + this.data = data.map((d) => { + return { + time_start: d[0], + time_end: d[0] + barWidth, + score: d[1], + } + }) + + for (let i = 0; i < this.data.length; i++) { + this.data[i].index = i + this.data[i].duration = this.data[i].time_end - this.data[i].time_start + this.data[i].zoomLevel = this.zoomLevel + } + } +} +ClassificationDataTile.fetchCount = 0 diff --git a/client/src/models/Location.js b/client/src/models/Location.js new file mode 100644 index 0000000..af555de --- /dev/null +++ b/client/src/models/Location.js @@ -0,0 +1,161 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { fetchClassifications } from '../network/ClassificationsNetwork' +import { getFiles } from '../network/Files' + +const trackLen = 43750 + +export class Location { + constructor(data) { + this.latitude = data.latitude + this.longitude = data.longitude + this.startTime = data.range.min_time + this.endTime = data.range.max_time + this.duration = data.range.duration + this.depth = data.depth + this.name = data.location + this.experiment_name = data.experiment_name + this.gaps = data.gaps + this.files = [] + this.noFilesAtTime = null + } + + async getClassifications(startTime, endTime, zoom, compressed=false) { + return fetchClassifications( + this.name, + startTime, + endTime, + zoom, + compressed + ) + } + + /** + * Gets the audio file for the timecode, + * as well as the next file. + * + * @param {number} timestamp - time in ms + * + * @return {array} - files + * + */ + async getFiles(timestamp) { + let haveFile = false + let haveNext = false + const time = this.roundDownToMinute(timestamp) + // check to see if this time and next file is cached + this.files.forEach((file) => { + if (time >= file.time_start && time <= file.time_end) { + haveFile = true + } + if ( + time + trackLen >= file.time_start && + time + trackLen <= file.time_end + ) { + haveNext = true + } + }) + + if ((!haveFile || !haveNext) && this.timeHasAudio(time)) { + const files = await getFiles( + this.name, + time - 60000, + 60000 * 2 + ) + + // catch if there are no files for the given time + // so that we can prevent from requesting again + if (!files.length) { + this.noFilesAtTime = time + } + + files.forEach((item) => { + const overlappingClassifications = this.files.filter((c) => { + return c.time_start === item.time_start + }) + if (overlappingClassifications.length === 0) { + this.files.push(item) + } + }) + } + this.files.sort((a, b) => { + return a.time_start - b.time_start + }) + + return this.files.filter((item) => { + return ( + item.time_start >= time - trackLen && + item.time_start <= time - trackLen + trackLen * 3 + ) + }) + } + + roundDownToMinute(timestamp) { + const date = new Date(timestamp) + const p = 60 * 1000 // milliseconds in a minute + return Math.floor(date.getTime() / p ) * p + } + + merge(ranges) { + const result = [] + let last + ranges.sort(function(a, b) { + return a.startTime - b.startTime || a.endTime - b.endTime + }) + ranges.forEach(function(r) { + if (!last || r.startTime > last.endTime) result.push((last = r)) + else if (r.endTime > last.endTime) last.endTime = r.endTime + }) + return result + } + + checkIfRangeLoaded(rangeGroup, startTime, duration) { + const endTime = startTime + duration + const ranges = rangeGroup.filter((item) => { + return startTime >= item.startTime && endTime <= item.endTime + }) + const inGroup = ranges.length > 0 + if (!inGroup) { + rangeGroup.push({ + startTime: startTime, + endTime: startTime + duration, + }) + rangeGroup = this.merge(rangeGroup) + } + return inGroup + } + + timeInGap(time){ + if(!this.gaps) return false + const gap = this.gaps.find((g)=> g.time_start <= time && g.time_end > time) + return gap ? gap : false + } + + timerangeInGap(time_start, time_end){ + if(!this.gaps) return false + const gap = this.gaps.find((g)=> g.time_start <= time_start && g.time_end > time_end) + return gap ? gap : false + } + + timeHasAudio(time) { + return ( + this.noFilesAtTime === null || + ( + time < this.noFilesAtTime - trackLen || + time > this.noFilesAtTime + trackLen * 2 + ) && ! this.timeInGap(time) + ) + } +} diff --git a/client/src/models/Locations.js b/client/src/models/Locations.js new file mode 100644 index 0000000..e3028d0 --- /dev/null +++ b/client/src/models/Locations.js @@ -0,0 +1,59 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { fetchLocations, fetchRange, fetchGaps } from '../network/LocationsNetwork' +import { Location } from '../models/Location' + +let instance + +class Locations { + constructor() { + if (instance) { + return instance + } + this.locations = {} + instance = this + } + + async get(locationName) { + // const locationsData = await this.getLocations() + + // const locationData = locationsData.find((d)=> d['location'] == locationName) + // if(!locationData){ + // console.error(`Location ${locationName} does not exist`) + // return null + // } + const range = await fetchRange(locationName) + const locationData = { + location: locationName, + range, + gaps: await fetchGaps(locationName, range.min_time, range.max_time) + } + + return new Location( + locationData, + ) + } + + async getLocations() { + if (!this._locationsData) { + this._locationsData = await fetchLocations() + } + return this._locationsData + } + + async getDefaultLocation() {} +} + +export const LocationsModel = new Locations() diff --git a/client/src/models/SimilarityTileDefinitions.js b/client/src/models/SimilarityTileDefinitions.js new file mode 100644 index 0000000..9ec3ced --- /dev/null +++ b/client/src/models/SimilarityTileDefinitions.js @@ -0,0 +1,47 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { TileDefinitions } from './TileDefinitions' +import { Globals } from '../globals' + +const URL_BASE = 'https://storage.googleapis.com/deepblue-similarities/' + +export class SimilarityTileDefinitions extends TileDefinitions { + + getTile(time, tileZoomLevel){ + if(!Globals.currentLocation) return undefined + + let ret = super.getTile(time, tileZoomLevel) + + // Calculate filename for tile + const date = new Date(ret.time) + const dir = tileZoomLevel < 0 ? 'n'+(-tileZoomLevel) : tileZoomLevel + + ret.file = `${URL_BASE}tiles-${dir}/${ + Globals.currentLocation.name + }/${date.getUTCFullYear()}_${this.timeDigits( + date.getUTCMonth() + 1 + )}_${this.timeDigits(date.getUTCDate())}T${this.timeDigits( + this.timeDigits(date.getUTCHours()) + )}_${this.timeDigits(date.getUTCMinutes())}_${this.timeDigits( + date.getUTCSeconds() + )}.jpg` + + return ret + } + + timeDigits(number){ + return ('0' + number).slice(-2) + } +} diff --git a/client/src/models/SpectrogramTileDefinitions.js b/client/src/models/SpectrogramTileDefinitions.js new file mode 100644 index 0000000..475ea77 --- /dev/null +++ b/client/src/models/SpectrogramTileDefinitions.js @@ -0,0 +1,60 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { GUI } from '../ui/Dat' +import { TileDefinitions } from './TileDefinitions' + +const URL_BASE = 'https://storage.googleapis.com/deepblue-tiled-spectrograms/' + +const config = { + denoise: true, +} +export const SpectrogramTileDefitionsConfig = config + +if (GUI) GUI.add(config, 'denoise').name('denoised spectrogram') +export class SpectrogramTileDefinitions extends TileDefinitions { + constructor(location, startTime) { + super() + this.location = location + this.startTime = startTime + } + + getTile(time, tileZoomLevel, base = URL_BASE) { + let ret = super.getTile(time, tileZoomLevel) + + // Calculate filename for tile + const date = new Date(ret.time) + const dir = tileZoomLevel < 0 ? 'n' + -tileZoomLevel : tileZoomLevel + + let denoise = '-denoise' + if (!config.denoise || base != URL_BASE) { + denoise = '' + } + + ret.file = `${base}tiles-${dir}${denoise}/${this.location}/${date.getUTCFullYear()}_${this.timeDigits( + date.getUTCMonth() + 1 + )}_${this.timeDigits(date.getUTCDate())}T${this.timeDigits( + this.timeDigits(date.getUTCHours()) + )}_${this.timeDigits(date.getUTCMinutes())}_${this.timeDigits(date.getUTCSeconds())}.jpg` + + // Calculate index of tile + ret.index = (ret.time - this.startTime) / TileDefinitions.zoomLevelDuration(tileZoomLevel) + + return ret + } + + timeDigits(number) { + return ('0' + number).slice(-2) + } +} diff --git a/client/src/models/TileDefinitions.js b/client/src/models/TileDefinitions.js new file mode 100644 index 0000000..fdbe752 --- /dev/null +++ b/client/src/models/TileDefinitions.js @@ -0,0 +1,62 @@ +// Copyright 2019 Google Inc. +// +// 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. + +const hour = 3.6e6 +const log2 = Math.log(2) +export class TileDefinitions { + constructor(){} + + /** + * @param startTime - timestamp in ms + * @param endTime - timestamp in ms + * @param zoomLevel - 1 (min) is 1hr, 32 (max) is 112.5 seconds + * @param incr - tile duration in ms + * + * @return of tiles for the set location + * in a given timeframe. + * + */ + getRange(startTime, endTime, zoomLevel){ + const incr = TileDefinitions.zoomLevelDuration(zoomLevel) + const t = this.getClosestTileStartTime(startTime, incr) + const tiles = [] + for (let i = t; i < endTime; i+=incr){ + tiles.push(this.getTile(i, zoomLevel)) + } + return tiles + } + + getTile(time, tileZoomLevel){ + const incr = TileDefinitions.zoomLevelDuration(tileZoomLevel) + return { + tileZoomLevel, + time, + duration : incr, + } + } + + getClosestTileStartTime(time, incr){ + return ( + time - (time % incr) + ) + } + + static zoomLevelDuration(zoomLevel){ + return hour / Math.pow(2, zoomLevel) + } + + static getZoomLevel(duration){ + return Math.log(hour / duration) / log2 + } +} \ No newline at end of file diff --git a/client/src/models/TimeManager.js b/client/src/models/TimeManager.js new file mode 100644 index 0000000..2db1cdf --- /dev/null +++ b/client/src/models/TimeManager.js @@ -0,0 +1,358 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Globals, Config } from '../globals' +import { GUI } from '../ui/Dat' + +const removeGaps = true +// This is the global time manager +export class TimeManager { + constructor() { + this._time = 0 + + this.gapMaxDuration = 300 + if (GUI) GUI.add(this, 'gapMaxDuration') + } + + /** + * Get window start time + */ + get windowStartTime() { + // return this.currentTime - this._duration / 2 + if (!this._lookup) return this.currentTime - this._duration / 2 + // console.log(this._lookupPxTime(0)) + return this._lookupPxTime(0) + } + + /** Get window end time */ + get windowEndTime() { + // return this.currentTime + this._duration / 2 + if (!this._lookup) return this.currentTime + this._duration / 2 + // console.log(this._lookupPxTime(window.innerWidth)) + return this._lookupPxTime(window.innerWidth) + } + + /** + * Return current time of playhead + */ + get currentTime() { + return this._time + } + + setCurrentTime(time) { + this._time = time + this._calculateTimeLookup() + } + + /** + * Get window duration + */ + get windowDuration() { + // return this._duration + return this.windowEndTime - this.windowStartTime + } + + get idealWindowDuration() { + return this._duration + } + + static calcIdealWindowDuration(tileDuration) { + return (tileDuration * window.innerWidth) / Config.tileWidth + } + + setWindowDuration(duration) { + this._duration = duration + this._resolution = this._duration / window.innerWidth + this._calculateTimeLookup() + } + + /** + * Get pixel resolation (ms / px) + */ + get resolution() { + return this._resolution + } + + /** + * Get pixel position on x axis of time relative to window start (left side) + * @param {Number} time + */ + timeToPx(time) { + const lookup = this._lookupTimePx(time) + return lookup + } + + /** + * Return time at pixel value + * @param {Number} px + */ + pxToTime(px) { + return this._lookupPxTime(px) + } + + /** + * Get pixel duration between two times + * @param {Number} timeStart + * @param {Number} timeEnd + */ + durationToPx(timeStart, timeEnd) { + return this.timeToPx(timeEnd) - this.timeToPx(timeStart) + } + + /** + * Return duration between two pixel values + * @param {Number} x1 + * @param {Number} x2 + */ + pxToDuration(x1, x2) { + return Math.abs(this.pxToTime(x2) - this.pxToTime(x1)) + } + + /** + * Return if there is a gap at time + * @param {Number} time + */ + gapAtTime(time) { + const period = this._lookupTime(time) + if (!period) return false + return period.gap + } + + /** + * Get periods with gaps + * @param {*} timeStart + * @param {*} timeEnd + */ + getGaps(timeStart, timeEnd) { + return this._lookup.filter((p) => { + return p.timeEnd > timeStart && p.timeStart < timeEnd && p.gap + }) + } + + getGap(time) { + const period = this._lookupTime(time) + if (!period) return false + return period.gap ? period : false + } + + getAudioPeriods(timeStart, timeEnd) { + return this._lookup.filter((p) => { + return p.timeEnd > timeStart && p.timeStart < timeEnd && !p.gap + }) + } + + /** + * Returns if the window has changed in the past tick + * Useful to determine if a view needs to be re-rendered + */ + get updated() { + return this._updated + } + + tick() { + // check if window has changed + if (this._prevWindowStartTime != this.windowStartTime || this._prevWindowEndTime != this.windowEndTime) { + this._updated = true + this._prevWindowStartTime = this.windowStartTime + this._prevWindowEndTime = this.windowEndTime + + // this._calculateTimeLookup() + } else { + this._updated = false + } + } + + /** + * Lookup a time in lookup table, and return pixel position + * @param {Number} time + */ + _lookupTimePx(time) { + if (time > this._lookupReverse[0].timeEnd) return this._lookupReverse[0].pixelPositionEnd + if (time < this._lookup[0].timeStart) return this._lookup[0].pixelPosition + + if (!this._lookup) return + for (let l of this._lookup) { + if (l.timeStart <= time && l.timeEnd > time) { + const offset = time - l.timeStart + const p = l.pixelPosition + return p + (offset * l.timeScale) / this.resolution + } + } + } + + _lookupTime(time) { + if (time > this._lookupReverse[0].timeEnd) return this._lookupReverse[0] + if (time < this._lookup[0].timeStart) return this._lookup[0] + + if (!this._lookup) return + for (let l of this._lookup) { + if (l.timeStart <= time && l.timeEnd > time) { + return l + } + } + } + + /** + * Lookup a pixel value in lookup table, and return time + * @param {Number} px + */ + _lookupPxTime(px) { + if (!this._lookup) return + for (let l of this._lookupReverse) { + if (l.pixelPosition <= px) { + const offset = px - l.pixelPosition + const t = l.timeStart + return t + (offset / l.timeScale) * this.resolution + } + } + } + + /** + * Return max duration at current zoom level for a gap + */ + _gapMaxDuration() { + return this.gapMaxDuration * this._resolution + } + + /** + * This function finds the next period used for the time lookup table from a time + * @param {*} t + * @param {*} gaps + * @param {*} backwards + */ + _findLookupTimePeriod(t, gaps, backwards = false) { + let period = { + timeScale: 1, + gap: false, + timeStart: t, + timeEnd: t, + } + + // Check if in a gap + let gap + if (!backwards) { + gap = gaps.find((g) => g.time_start <= t && g.time_end > t) + } else { + gap = gaps.find((g) => g.time_start < t && g.time_end >= t) + } + if (gap) { + const gapDuration = gap.time_end - gap.time_start + if (removeGaps && gapDuration > this._gapMaxDuration()) { + period.timeScale = this._gapMaxDuration() / (gap.time_end - gap.time_start) + } + + period.timeStart = gap.time_start + period.timeEnd = gap.time_end + + period.gap = true + } else { + // If not in a gap, find next gap, and progress to it, or end + if (!backwards) { + const nextGap = gaps.find((g) => g.time_start > t) + if (nextGap) period.timeEnd = nextGap.time_start + else period.timeEnd = period.timeStart + 10e10 // TODO + } else { + const prevGap = gaps + .slice(0) + .reverse() + .find((g) => g.time_end < t) + if (prevGap) period.timeStart = prevGap.time_end + else period.timeStart = period.timeEnd - 10e10 // TODO + } + } + + return period + } + + _calculateTimeLookup() { + if (!Globals.currentLocation) return + + let gaps = Globals.currentLocation.gaps.slice(0) + // console.log(gaps.length) + + // Remove audio < 10 px + for (let i = 0; i < gaps.length - 1; i++) { + const gap1 = gaps[i] + const gap2 = gaps[i + 1] + if (gap2.time_start - gap1.time_end < 10 * this._resolution) { + gaps[i].time_end = gap2.time_end + gaps.splice(i + 1, 1) + } + } + + // Remove gaps < 5 px + gaps = gaps.filter((p) => p.time_end - p.time_start > 5 * this._resolution) + + // console.log(gaps.length) + const lookup = [] + const lookupReverse = [] + + // Search forward from current time + let timeCursor = this.currentTime + let pixelCursor = window.innerWidth / 2 + let c = 0 + while (pixelCursor < window.innerWidth * 5) { + let period = this._findLookupTimePeriod(timeCursor, gaps) + + const offset = period.timeStart - timeCursor + period.pixelPosition = pixelCursor + (offset * period.timeScale) / this.resolution + + // Move time cursor + timeCursor = period.timeEnd + // Move pixel cursor + pixelCursor = + period.pixelPosition + ((period.timeEnd - period.timeStart) * period.timeScale) / this.resolution + period.pixelPositionEnd = pixelCursor + + lookup.push(period) + lookupReverse.unshift(period) + + // Just to be safe... + if (c++ > 100) { + console.error('C > 100') + break + } + } + + // Search backwards from the first period + timeCursor = lookup[0].timeStart + pixelCursor = lookup[0].pixelPosition + c = 0 + while (pixelCursor > -window.innerWidth * 4) { + let period = this._findLookupTimePeriod(timeCursor, gaps, true) + + period.pixelPositionEnd = pixelCursor + + // Move time cursor + timeCursor = period.timeStart + // Move pixel cursor + pixelCursor -= ((period.timeEnd - period.timeStart) * period.timeScale) / this.resolution + period.pixelPosition = pixelCursor + + lookup.unshift(period) + lookupReverse.push(period) + + // Just to be safe... + if (c++ > 100) { + console.error('C reverse > 100') + break + } + } + + this._lookup = lookup + this._lookupReverse = lookupReverse + + this._lookupMinTime = lookup[0].timeStart + } +} diff --git a/client/src/network/Annotations.js b/client/src/network/Annotations.js new file mode 100644 index 0000000..8a99fee --- /dev/null +++ b/client/src/network/Annotations.js @@ -0,0 +1,85 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Config } from '../globals' + +let annotationsCache = undefined + +export async function getAnnotations() { + if (annotationsCache) return annotationsCache + // const response = await fetch(Config.apiPath + 'annotations') + const response = await fetch(Config.annotationUrl) + if (response.ok) { + const annotations = await response.text() + annotationsCache = parseAnnotationTSV(annotations) + return annotationsCache + } +} + +function parseAnnotationTSV(tsv) { + const lines = tsv.split('\r\n') + const headers = lines.slice(0, 1)[0].split('\t') + + const colIndex = { + location: headers.indexOf('location'), + timeStart: headers.indexOf('time_start'), + timeEnd: headers.indexOf('time_end'), + comment: headers.indexOf('comment'), + author: headers.indexOf('author'), + optional_anchor: headers.indexOf('optional_anchor'), + } + + for (let key in colIndex) { + if (colIndex[key] == -1) { + console.error('Could not parse annotations. Column ' + key + ' not found') + return [] + } + } + + const annotations = {} + lines.slice(1, lines.length).forEach((line, lineIndex) => { + const data = line.split('\t') + for (let i = 0; i < data.length; i++) { + data[i] = data[i].trim() + } + data[colIndex.location] = data[colIndex.location].trim() + const lineObj = { + timeStart: new Date(data[colIndex.timeStart]).getTime(), + timeEnd: new Date(data[colIndex.timeEnd]).getTime(), + comment: data[colIndex.comment], + author: data[colIndex.author], + anchor: data[colIndex.optional_anchor], + } + + if (!lineObj.timeStart || !lineObj.timeEnd || !lineObj.comment || !lineObj.author) { + console.warn('Could not parse annotation line ' + (lineIndex + 2), data) + return + } + + lineObj.duration = lineObj.timeEnd - lineObj.timeStart + + if (annotations.hasOwnProperty(data[colIndex.location])) { + annotations[data[colIndex.location]].push(lineObj) + } else { + annotations[data[colIndex.location]] = [lineObj] + } + }) + // Object.keys(annotations).forEach((location) => { + // annotations[location].sort((a, b) => { + // return a.timeStart - b.timeStart + // }) + // }) + + return annotations +} diff --git a/client/src/network/ClassificationsNetwork.js b/client/src/network/ClassificationsNetwork.js new file mode 100644 index 0000000..b478b8e --- /dev/null +++ b/client/src/network/ClassificationsNetwork.js @@ -0,0 +1,33 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Config } from '../globals' + +/* +the attributes are location, time_start, time_end and zoom +zoom is an integer between -7 and 6 which represents +the power of 2 divisor of 1 hour +oneHour / pow(2, zoom) +so zoom = 0 is 1 hour and zoom -2 is 4 hours and zoom 2 is 15 minutes +*/ +export async function fetchClassifications(location, timeStart, timeEnd, zoom, compressed = false) { + let classifications + const response = await fetch( + `${Config.apiPath}classifications_bq` + `?location=${location}&time_start=` + `${timeStart}&time_end=${timeEnd}&zoom=${zoom}&compressed=${compressed ? 'true' : 'false'}` + ) + if (response.ok) { + classifications = await response.json() + return classifications + } +} diff --git a/client/src/network/Files.js b/client/src/network/Files.js new file mode 100644 index 0000000..ce087a6 --- /dev/null +++ b/client/src/network/Files.js @@ -0,0 +1,37 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Config } from '../globals' + +export async function getFiles(location, time, duration) { + const response = await fetch( + `${Config.apiPath}files?location=${location}` + `&time=${time}&duration=${duration}&ms_timestamps=true` + ) + if (response.ok) { + const files = await response.json() + + return files.map((f) => { + return { + filename: f.filename, + startTime: f.time_start, + time_start: f.time_start, + endTime: f.time_end, + time_end: f.time_end, + location: f.location, + } + }) + } else { + return [] + } +} diff --git a/client/src/network/Installation.js b/client/src/network/Installation.js new file mode 100644 index 0000000..86ba332 --- /dev/null +++ b/client/src/network/Installation.js @@ -0,0 +1,123 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import io from 'socket.io-client' +import { Globals, Config } from '../globals' +import { getControlsInfo, setControlsDuration, setControlsPosition } from '../ui/Controls' +import LowpassFilter from '../util/LowpassFilter' +import { scaleToRange, logScale, clamp } from '../util/Math' + +/** + * The IP Address of the device + */ +const SENSOR_IP_PORT = '192.168.86.83:8080' + +/** + * The threshold that needs to be crossed before a movement is triggered + */ +const MOVEMENT_DELTA = 0.01 +// const MOVEMENT_DELTA = 22321 + +/** + * The start time of the strip in UNIX time + */ +const DATE_START = 1422755085000 + +/** + * The end time of the strip in UNIX time + */ +const DATE_END = 1425163823000 + +/** + * the sensor value at the start + */ +const SENSOR_START = 3.5 + +/** + * The sensor value at the end of the strip + */ +const SENSOR_END = 0.5 + +const MIN_ZOOM = Config.minDuration * 8 +const MAX_ZOOM = Config.maxDuration / 32 + +// BEGIN MOVEMENT CODE + +let loaded = false + +const loadedPromise = new Promise((done) => { + const interval = setInterval(() => { + if (Globals.player) { + clearInterval(interval) + done() + } + }, 100) +}) + +loadedPromise.then(() => (loaded = true)) + +let lastPosition = 0 +let lastZoom = MIN_ZOOM + +const positionFilter = new LowpassFilter(0.02, 0.6) +const zoomFilter = new LowpassFilter(0.015, 0.55) +let moveToTime = 0 + +setInterval(() => { + // const speed = Math.abs(v - lastPosition) + const speed = moveToTime - lastPosition + + // Calculate the max speed. Max speed is the width of the current view, so slow down when zoomed in + const maxSpeed = lastZoom * 4 + const clampedSpeed = clamp(speed, -maxSpeed, maxSpeed) + + // Move to the new position at the clamped speed + let pos = lastPosition + if (Math.abs(clampedSpeed) > 100000) { + pos = positionFilter.tick(lastPosition + clampedSpeed) + setControlsPosition(pos) + } + + // Calculate desired zoom level + let zoom = MAX_ZOOM + if (Math.abs(speed) < 100000) { + zoom = MIN_ZOOM + } + + const z = clamp(zoomFilter.tick(zoom), MIN_ZOOM, MAX_ZOOM) + setControlsDuration(z) + + lastPosition = pos + lastZoom = z +}, 30) + +const socket = io(`http://${SENSOR_IP_PORT}`) +socket.on('connect', (e) => { + console.log('connected!') +}) + +let lastSensorValue = 0 +socket.on('change', (sensor) => { + if (Math.abs(sensor - lastSensorValue) > MOVEMENT_DELTA) { + console.log('sensor', sensor) + const first = moveToTime == 0 + moveToTime = (sensor / (SENSOR_END - SENSOR_START)) * (DATE_END - DATE_START) + DATE_START + if (first) { + for (let i = 0; i < 1000; i++) { + lastPosition = positionFilter.tick(moveToTime) + } + } + lastSensorValue = sensor + } +}) diff --git a/client/src/network/LocationsNetwork.js b/client/src/network/LocationsNetwork.js new file mode 100644 index 0000000..a0374fe --- /dev/null +++ b/client/src/network/LocationsNetwork.js @@ -0,0 +1,69 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Config, Globals } from '../globals' + +let locationsCache +export async function fetchLocations() { + if (locationsCache) { + return locationsCache + } + const response = await fetch(`${Config.apiPath}locations`) + if (response.ok) { + locationsCache = await response.json() + return locationsCache + } +} + +let rangeCache = {} +export async function fetchRange(location) { + if (rangeCache[location]) { + return rangeCache[location] + } + const response = await fetch(`${Config.apiPath}range?location=${location}&ms_timestamps=true`) + if (response.ok) { + let range = await response.json() + range.min_time = Math.max(range.min_time, Config.minStartTime) + // range.max_time = Math.min(range.max_time, Config.maxEndTime) + range.duration = range.max_time - range.min_time + rangeCache[location] = range + return range + } +} + +let gapCache = {} +export async function fetchGaps(location, timeStart, timeEnd) { + const key = location + timeStart + timeEnd + if (gapCache[key]) { + return gapCache[key] + } + + const response = await fetch( + `${Config.apiPath}gaps?location=${location}&time_start=${timeStart}&time_end=${timeEnd}` + ) + if (response.ok) { + gapCache[key] = await response.json() + gapCache[key] = gapCache[key].filter((d) => d.time_start < d.time_end) + for (let period of Config.skipPeriods) { + gapCache[key] = gapCache[key].filter((g) => g.time_end < period[0] || g.time_start > period[1]) + gapCache[key].push({ + time_start: period[0], + time_end: period[1], + }) + } + + gapCache[key].sort((a, b) => (a.time_start > b.time_start ? 1 : -1)) + return gapCache[key] + } +} diff --git a/client/src/network/Similarity.js b/client/src/network/Similarity.js new file mode 100644 index 0000000..1b0d020 --- /dev/null +++ b/client/src/network/Similarity.js @@ -0,0 +1,116 @@ +// Copyright 2019 Google Inc. +// +// 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. + +export class Similarity { + constructor(location, startTime) { + this._imageDataMap = new Map() + // this._tiles = new Tiles(location, startTime) + } + + async getSimilarity(windowStart, windowEnd) { + const centerPoint = (windowStart + windowEnd) / 2 + const offset = (centerPoint - imageStartTime) / imageHeightDuration + const imageData = await this._getImageData() + const data = this._getTimeOffset(imageData, offset, centerPoint) + return data + } + + async _getImageData(url) { + // const url = '/assets/similarity-221-raw.png' + if (this._imageDataMap.has(url)) { + return this._imageDataMap.get(url) + } else { + const image = new Image() + image.src = url + await new Promise((done) => (image.onload = () => done())) + const canvas = document.createElement('canvas') + canvas.width = image.width + canvas.height = image.height + const context = canvas.getContext('2d') + context.drawImage(image, 0, 0, image.width, image.height) + const imageData = context.getImageData(0, 0, image.width, image.height) + //create the 2d matrix with all the data + const matrix = [] + for (let row = 0; row < imageData.height; row++) { + //get the image data for that row + const startIndex = row * imageData.width * 4 + const pixelRow = imageData.data.subarray(startIndex, startIndex + imageData.width * 4) + //just take the first value of the RGBA + // const subarray = new Float32Array(imageData.width) + const subarray = [] + for (let i = 0; i < pixelRow.length; i += 4) { + subarray.push(pixelRow[i] / 255) + } + matrix.push(subarray.reverse()) + } + + const matrixData = { + data: matrix, + width: imageData.width, + height: imageData.height, + } + + this._imageDataMap.set(url, matrixData) + + return matrixData + } + } + + _getTimeOffset(imageData, offset) { + //the y row as a percentage of the total time + const { width, height } = imageData + const percentageHeight = height * offset + const row = Math.floor(percentageHeight) + const pixelOffset = percentageHeight - row + + if (offset > 1 || offset < 0 || isNaN(row)) { + return [] + } + + const subarray = imageData.data[row] + + if (!this.currentRow) { + this.currentRow = subarray + } else { + //average the two together + const alpha = 0.8 + this.currentRow = this.currentRow.map((val, index) => val * alpha + subarray[index] * (1 - alpha)) + } + + const offsetTime = offset * imageHeightDuration + imageStartTime + const threshValue = config.threshold + + const retTimes = [] + let active = null + const rowTime = (row / height) * imageHeightDuration + subarray.forEach((value, index) => { + const indexTime = ((index - pixelOffset) / width - 0.5) * imageWidthDuration + offsetTime + if (value > threshValue && !active) { + active = { + startTime: indexTime, + duration: 0, + value, + } + retTimes.push(active) + } else if (value <= threshValue && active) { + active.duration = indexTime - active.startTime + active = null + } else if (active) { + //take the max value within the thresh'ed area + active.value = Math.max(active.value, value) + } + }) + return retTimes + } +} diff --git a/client/src/style.scss b/client/src/style.scss new file mode 100644 index 0000000..4e5dec7 --- /dev/null +++ b/client/src/style.scss @@ -0,0 +1,17 @@ +.circle { + border-radius: 40px; + width: 80px; + height: 80px; + border: 2px solid white; + transform: translate(-50%, -50%); + background-color: rgba(white, 0.1); + position: absolute; +} + +.dg.ac { + z-index: 3; +} + +controls-element { + transition: all 0.8 ease-out; +} \ No newline at end of file diff --git a/client/src/ui/Annotate.js b/client/src/ui/Annotate.js new file mode 100644 index 0000000..980f45a --- /dev/null +++ b/client/src/ui/Annotate.js @@ -0,0 +1,177 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' +import { Config, Device } from '../globals' + +export class Annotate extends LitElement { + static get properties() { + return { + isOn: { type: Boolean }, + } + } + static get styles() { + return css` + :host { + width: 100vh; + height: 100vh; + } + #container { + display: block; + width: 100%; + height: 100%; + z-index: 50; + position: absolute; + left: 0; + background: rgba(0,0,0,0.5); + top: 0; + cursor: text; + display: none; + } + #selection { + pointer-events: none; + background-color: #86bcbc; + opacity: 0.3; + position: absolute; + height: 400px; + opacity: 0; + top: 50%; + transform: translateY(-50%); + } + #annotate { + position: absolute; + top: 10px; + right: 10px; + color: #818181; + font-size: 12px; + padding: 15px; + z-index: 51; + } + #save { + cursor: pointer; + position: absolute; + top: 20px; + left: 20px; + color: #818181; + font-size: 12px; + padding: 10px 15px; + z-index: 51; + display: none; + background-color: transparent; + } + ` + } + constructor() { + super() + this.isOn = false + } + + off() { + this.isOn = false + this.shadowRoot.querySelector('#container').style.display = 'none' + this.$$save.style.display = 'none' + this.$$selection.style.opacity = 0 + } + + on() { + this.isOn = true + this.shadowRoot.querySelector('#container').style.display = 'block' + } + + toggle() { + this.isOn = !this.isOn + if (this.isOn) { + this.on() + } else { + this.off() + } + } + + firstUpdated() { + this.$$selection = this.shadowRoot.querySelector('#selection') + this.$$save = this.shadowRoot.querySelector('#save') + + this.resize() + window.addEventListener('resize', () => { + this.resize() + }) + } + + mouseDown(event) { + this.startingPoint = event.clientX + this.$$selection.style.left = `${event.clientX}px` + this.mousedown = true + this.$$save.style.display = 'none' + } + + mouseUp(event) { + this.endingPoint = event.clientX + this.mousedown = false + this.$$save.style.display = 'block' + } + + mouseMove(event) { + if (this.mousedown) { + this.$$selection.style.opacity = 0.5 + if (event.clientX - this.startingPoint >= 0) { + this.$$selection.style.width = `${event.clientX - this.startingPoint}px` + } else { + this.$$selection.style.left = `${event.clientX}px` + this.$$selection.style.width = `${this.startingPoint - event.clientX}px` + } + } + } + + save() { + let points = {} + if (this.startingPoint <= this.endingPoint) { + points = { + startX: this.startingPoint, + endX: this.endingPoint, + } + } else { + points = { + startX: this.endingPoint, + endX: this.startingPoint, + } + } + const event = new CustomEvent('annotate', { detail: points }) + this.dispatchEvent(event) + + const btn = this.shadowRoot.querySelector('#save') + const text = btn.innerHTML + btn.innerHTML = 'Copied!' + setTimeout(()=>{ + this.off() + btn.innerHTML = text + }, 1000) + } + + resize() { + this.annotateHeight = !Device.tabletLandscape ? (Config.tileHeight / 4) : window.innerHeight / 1.25 + } + + render() { + return html` + + ${(!this.isOn) ? html`Annotate` : html`Done`} + +
+
+
+ + ` + } +} +customElements.define('annotate-el', Annotate) diff --git a/client/src/ui/Canvas.js b/client/src/ui/Canvas.js new file mode 100644 index 0000000..12ab108 --- /dev/null +++ b/client/src/ui/Canvas.js @@ -0,0 +1,287 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' +import { Application, Container, ticker } from 'pixi.js' +import * as TWEEN from '@tweenjs/tween.js' +import { Globals, Device, Config, updateGlobals } from '../globals' +import { Spectrogram } from '../components/Spectrogram' +import { Player } from '../components/Player' +import { spectrogramFilters } from './filter/Filter' + +const playheadExtraHeight = 85 + +export class Canvas extends LitElement { + static get properties() { + return { + currentLocation: { + type: Object, + }, + classifications: { type: Object, value: [] }, + audioLines: { type: Object, value: [] }, + spectrogram: { type: Object }, + scale: { type: Number }, + duration: { type: Number }, + } + } + + static get styles() { + return css` + canvas { + width: 100%; + height: 100%; + z-index: 1; + transition: filter 0.5s ease-out; + } + + #playhead { + width: 2px; + background: #ffffff; + position: absolute; + left: 50%; + transform: translate(-50%, 0); + } + + #playhead::after { + content: ''; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid #ffffff; + position: absolute; + left: -4px; + bottom: 0; + display: block; + } + + #playhead::before { + content: ''; + display: block; + width: 8px; + position: absolute; + height: 1px; + background: #ffffff; + left: -3px; + } + /* Landscape */ + @media only screen and (max-height: 499px) and (orientation: landscape), (min-aspect-ratio: 3/1) { + #playhead { + display: none; + } + } + ` + } + + constructor() { + super() + this.prevLocation = 100000000000000 + this.width = document.body.offsetWidth + (!Device.desktop ? 1 : 0) + this.playheadHeight = 0 + this.playheadTop = 0 + this.playtime = 0 + this.playtimeMilestone = {} + // this.classificationLayerMinimap = new ClassificationLayer(false) + } + + update(changedProperties) { + super.update(changedProperties) + for (const key of changedProperties.keys()) { + switch (key) { + case 'currentLocation': + this._updateLocation() + break + case 'duration': + this._updateDuration() + break + } + } + } + + /** + * Inherited method from lit-el + * called when this module first renders + * + * Here's where we have all of our events + */ + async firstUpdated() { + this.canvas = this.shadowRoot.querySelector('canvas') + if (!Config.skipIntro) { + this.canvas.style.filter = 'blur(10px)' + } + this.spectrogramShowing = false + + this.pixiApp = new Application({ + autoResize: true, + resolution: devicePixelRatio, + view: this.canvas, + backgroundColor: Config.bgColor, + }) + PIXI.settings.MIPMAP_TEXTURES = false + + this.pixiApp.stage.visible = false + ticker.shared.autoStart = false + ticker.shared.stop() + + Globals.pixiApp = this.pixiApp + + this.resize() + window.addEventListener('resize', () => { + this.resize() + }) + } + + async _updateLocation() { + if (!Globals.player) { + this.spectroContainer = new Container() + this.spectroContainer.filters = spectrogramFilters + this.spectroContainer.interactiveChildren = true + + const spectroGroup = new Container() + spectroGroup.y = -Config.spectrogramOffset + this.pixiApp.stage.addChild(spectroGroup) + + spectroGroup.addChild(this.spectroContainer) + + this.spectrogram = new Spectrogram(this.spectroContainer) + this.spectrogram.location = this.currentLocation.name + Globals.spectrogram = this.spectrogram + this.spectrogram.duration = this.duration + + Globals.player = new Player( + this.currentLocation.name, + Globals.controls.position + this.currentLocation.startTime + ) + Globals.player.duration = this.duration + Globals.player.display = true + + this.draw() + } else { + // TODO: change spectrogram location + this.spectrogram.location = this.currentLocation.name + this.spectrogram.pause() + } + } + + _updateDuration() { + if (Globals.player) { + Globals.player.duration = this.duration + this.spectrogram.duration = this.duration + } + } + + // TODO: make sure this can't run multiple times, + // might a few conditions where it does + draw(timestamp) { + if (Globals.player && this.spectrogram) { + Globals.player.audioLoop() + + // Initial spectrogram motion when splash screen is showing + if (Globals.controls.$$intro.showing && !Globals.controls.$$intro.hasShown) { + Globals.controls.syncPositions(Globals.controls.position + Globals.controls.initialScrubIncrement) + if (Globals.controls.position >= Globals.controls.maxInitialScrub - Globals.currentLocation.startTime) { + Globals.controls.initialScrubIncrement = 0 + } + } else { + // Typically, sync the app position to the audio Player unless we're scrubbing + if (!this.spectrogram.tileWindowChanging) { + Globals.controls.syncPositions(Globals.player.time - Globals.currentLocation.startTime) + } + } + if (Device.isTouch) { + Globals.controls.scrub.handleMomentum() + } + } + ticker.shared.update(timestamp) + TWEEN.update(timestamp) + requestAnimationFrame(this.draw.bind(this)) + + Globals.timeManager.tick() + Globals.events.emit('draw') + + this.updatePlaytime() + } + + /** + * Function keeping track of total play time for analytics purpose + */ + updatePlaytime() { + const t = Globals.player.time + if (!this._lastPlayTime) this._lastPlayTime = t + + if (Globals.player.playing) { + const delta = t - this._lastPlayTime + this.playtime += delta + + const milestone = Math.floor(this.playtime / 5000) + if (milestone > 0 && !this.playtimeMilestone[milestone]) { + gtag('event', 'playtime', { + value: milestone * 5, + non_interaction: true, + }) + this.playtimeMilestone[milestone] = true + } + } + this._lastPlayTime = t + } + + resize() { + Globals.windowResizing = true + + updateGlobals() + + if (this.spectrogram) { + this.spectrogram.updateTileHeights() + this.spectrogram.update() + this.spectrogram.resize() + } + + // Tablet has 1px offset + this.width = document.body.offsetWidth + (!Device.desktop ? 1 : 0) + this.pixiApp.renderer.resize(this.width, document.body.offsetHeight) + + this.playheadHeight = Config.tileHeight * Config.tileScaleMax + playheadExtraHeight + this.playheadTop = + window.innerHeight / 2 - (Config.tileHeight * Config.tileScaleMax) / 2 - Config.spectrogramOffset - 1 + if (window.innerHeight < 680) { + this.playheadHeight -= 30 + } + + Globals.events.emit('resize') + Globals.windowResizing = false + this.requestUpdate() + } + + render() { + return html` + +
+ + ` + } +} + +PIXI.interaction.InteractionManager.prototype.mapPositionToPoint = function mapPositionToPoint(point, x, y) { + const rect = this.interactionDOMElement.getBoundingClientRect() + + const resolutionMultiplier = navigator.isCocoonJS ? this.resolution : 1.0 / this.resolution + + point.x = (x - rect.left) * (this.interactionDOMElement.width / rect.width) * resolutionMultiplier + point.y = (y - rect.top) * (this.interactionDOMElement.height / rect.height) * resolutionMultiplier +} diff --git a/client/src/ui/Comment.js b/client/src/ui/Comment.js new file mode 100644 index 0000000..c229213 --- /dev/null +++ b/client/src/ui/Comment.js @@ -0,0 +1,852 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' +import { Config, Globals } from '../globals' +import './CommentsModal' +import './CommentMarkdownElement' +const fadeTime = 160 + +export class Comment extends LitElement { + static get properties() { + return { + author: { type: String }, + open: { type: Boolean }, + disabled: { type: Boolean }, + authorObj: { type: Object }, + visibleAnnotation: { type: Object }, + firstCommentSelectable: { type: Boolean }, + lastCommentSelectable: { type: Boolean }, + showAuthorOverlay: { type: Boolean }, + showing: { type: Boolean }, + } + } + + constructor() { + super() + this.open = false + this._annotation = null + this.authorObj = null + this.visibleAnnotation = this._annotation + this.nextCommentEvent = new CustomEvent('nextComment') + this.prevCommentEvent = new CustomEvent('prevComment') + this.firstCommentSelectable = true + this.lastCommentSelectable = true + this.disabled = false + this.showAuthorOverlay = false + } + + set annotation(annotation) { + this._annotation = annotation + if (this.$$body) { + // empty annotation object closes comment + if (this.annotation !== null) { + this.visibleAnnotation = this.annotation + this.$$body.classList.remove('hidden') + clearTimeout(this.fadeoutTimeout) + } else { + this.fadeoutTimeout = setTimeout(() => { + this.visibleAnnotation = this.annotation + }, fadeTime) + this.$$body.classList.add('hidden') + } + } + } + + get annotation() { + return this._annotation + } + + firstUpdated() { + this.$$container = this.shadowRoot.querySelector('#container') + this.$$body = this.shadowRoot.querySelector('#body') + document.addEventListener('click', (event) => { + this.bgClickHandler(event) + }) + document.addEventListener('touchend', (event) => { + this.bgClickHandler(event) + }) + } + + bgClickHandler(event) { + const path = path || (event.composedPath && event.composedPath()) + if (this.open && path && path[0]) { + if (!this.shadowRoot.contains(path[0])) { + this.open = false + } + } + } + + set author(author) { + this._author = author + if (this.author !== null) { + this.authorObj = Config.authors[this.author] + } else { + this.authorObj = null + } + this.open = false + const event = new CustomEvent('authorChange', { detail: this.authorObj }) + this.dispatchEvent(event) + console.log('authorChange', { detail: this.authorObj }) + } + + get author() { + return this._author + } + + handleAuthorClick(e) { + const key = e.currentTarget.getAttribute('data-author') + this.shadowRoot.querySelector('comments-modal').author = Config.authors[key] + this.shadowRoot.querySelector('comments-modal').show = true + this.open = false + } + + startTour(author) { + this.shadowRoot.querySelector('comments-modal').author = Config.authors[author] + this.shadowRoot.querySelector('comments-modal').show = true + this.open = false + } + + beginTourHandler(e) { + this.author = e.detail.author.id + } + + nextComment() { + this.dispatchEvent(this.nextCommentEvent) + } + prevComment() { + this.dispatchEvent(this.prevCommentEvent) + } + + hide() { + this.showing = false + } + + show() { + this.showing = true + } + + render() { + if (!this.showing) { + return html`` + } + + return html` +
+
+
+ +
+ + + + +
+
+ ${this.open + ? html` + + ` + : html``} +
+
+ ${this.visibleAnnotation !== null && !this.open + ? html` + + ` + : html``} +
+
+ { + this.showAuthorOverlay = false + }} + @begin=${this.beginTourHandler} + > + ` + } + + static get styles() { + return css` + p { + margin: 0; + } + + .button-group { + position: relative; + display: flex; + justify-content: center; + align-items: center; + } + + #container { + display: block; + z-index: 3; + pointer-events: none; + position: absolute; + top: 35px; + text-align: center; + width: 100%; + background: black; + } + .hidden { + visibility: hidden; + opacity: 0; + } + + .header-wrap { + position: relative; + } + + .header { + display: inline-block; + margin-bottom: 5px; + position: relative; + } + + .tutorial-previous, + .tutorial-next { + background: none; + border: none; + width: 15px; + height: 15px; + position: relative; + cursor: pointer; + position: relative; + margin: -10px 10px 0; + opacity: 1; + transition: all 0.25s ease-out; + } + + .tutorial-next:active { + opacity: 0.6; + } + + .tutorial-previous:active { + opacity: 0.6; + } + + .open .tutorial-previous, + .open .tutorial-next { + display: none; + } + + .tutorial-previous { + transform: scale(-1, 1); + } + + .tutorial-previous.hide, + .tutorial-next.hide { + opacity: 0; + pointer-events: none; + } + + .tutorial-previous svg, + .tutorial-next svg { + height: 100%; + width: 15px; + height: 15px; + padding: 10px; + position: absolute; + left: -5px; + top: -5px; + } + + .author-toggle { + background: none; + border: 0; + color: #fff; + padding: 7px 10px; + border: 1px solid #fff; + display: flex; + align-items: center; + cursor: pointer; + transition: 0.2s ease-in; + width: 290px; + height: 42px; + position: relative; + } + + .author-toggle:hover { + background: rgb(47, 47, 47); + } + + .author-img-small { + height: 24px; + width: 24px; + overflow: hidden; + object-fit: cover; + border-radius: 50%; + } + + .icon-comments { + height: 20px; + width: 20px; + position: relative; + top: 2px; + } + + .icon-comments svg { + width: 100%; + height: 100%; + } + + .author-toggle-text { + margin-left: 16px; + font-weight: 500; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 14px; + } + + .author-toggle-arrow { + height: 9px; + width: 9px; + border-left: 1px solid #fff; + border-bottom: 1px solid #fff; + display: inline-block; + transform: rotate(-45deg); + position: absolute; + right: 20px; + top: 12px; + } + + .open .author-toggle-arrow { + top: 16px; + transform: rotate(135deg); + } + + .icon { + display: inline-block; + vertical-align: top; + } + + .authors-list { + display: flex; + flex-direction: column; + padding-left: 0; + margin-top: 5px; + max-height: calc(100vh - 100px); + overflow-y: auto; + pointer-events: auto; + border-bottom: 1px solid #fff; + border-top: 1px solid #fff; + width: 400px; + position: absolute; + margin-left: -145px; + left: 50%; + } + + .close-list { + background: none; + border: none; + color: #fff; + display: flex; + padding: 12px 18px; + align-items: center; + cursor: pointer; + width: 100%; + } + + .close { + height: 34px; + width: 34px; + border: 1px solid #fff; + border-radius: 50%; + position: relative; + transform: rotate(45deg); + } + + .close span { + background: #fff; + } + + .close span:first-of-type { + position: absolute; + width: 1px; + height: 18px; + top: 8px; + left: 16px; + } + + .close span:last-of-type { + position: absolute; + width: 18px; + height: 1px; + top: 16px; + left: 8px; + } + + .list-item { + background: #000; + position: relative; + border-left: 1px solid #fff; + border-right: 1px solid #fff; + list-style: none; + transition: 0.2s ease-in; + } + + .list-item::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 1px; + border: #fff; + z-index: 100; + background: rgba(255, 255, 255, 1); + } + + .list-item:first-of-type::after { + display: none; + } + + .list-item:hover { + background: #212121; + } + + .list-item:hover .author-wrap { + background: #212121; + } + + .author-wrap { + display: flex; + padding: 12px 18px; + align-items: center; + border-left: 0; + border-right: 0; + border-top: 0; + border-bottom: 0; + background: #000; + color: #fff; + display: flex; + border: 0; + align-items: center; + z-index: 0; + cursor: pointer; + width: 100%; + transition: 0.2s ease-in; + } + + .text { + font-weight: 500; + font-family: 'Roboto'; + display: flex; + flex-direction: column; + text-align: left; + margin-left: 15px; + } + + .name { + letter-spacing: 1px; + font-size: 14px; + font-weight: 500; + font-family: 'Roboto Mono', monospace; + } + + .title { + font-family: 'Roboto Mono', monospace; + margin-top: 2px; + font-size: 12px; + letter-spacing: 1px; + } + + .author { + display: inline-block; + color: #bbffff; + font-size: 12px; + line-height: 12px; + vertical-align: top; + margin-left: 8px; + } + + .body { + display: inline-block; + width: 70%; + max-width: 980px; + transition: visibility 0.11s, opacity 0.11s linear; + } + .comment { + display: inline-block; + font-size: 14px; + color: #fff; + margin-top: 5px; + font-family: 'Roboto'; + pointer-events: auto; + line-height: 1.55; + letter-spacing: 1.5px; + } + + button { + pointer-events: auto; + outline: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + } + .disabled { + pointer-events: none; + } + .-small { + min-height: 34px; + min-width: 34px; + height: 34px; + width: 34px; + object-fit: cover; + border-radius: 50%; + overflow: hidden; + } + + .mobile-authors-list-title { + display: none; + } + + .share-close { + display: none; + } + + @media only screen and (max-width: 957px) { + #container { + z-index: 53; + top: 60px; + } + + .body { + width: 90%; + } + + #container.scroll { + width: 100%; + height: 100%; + transform: none; + left: 0; + max-width: none; + } + + #container.scroll .author-toggle { + display: none; + } + + .author-toggle { + width: auto; + padding-right: 38px; + } + .author-toggle-arrow { + right: 12px; + } + + #container.scroll .header { + background: #000; + } + + .header-wrap { + width: 100%; + height: 100%; + } + + .header { + position: inherit; + left: 0; + top: 0; + transform: none; + width: 100%; + height: 100%; + } + + #container.scroll .header { + position: fixed; + } + + .share-close.hide { + display: none; + } + + .author-toggle-text { + font-size: 13px; + } + + .share-close { + position: absolute; + padding: 20px; + top: 0; + right: 0; + display: flex; + align-items: center; + background: none; + border: none; + cursor: pointer; + z-index: 10; + } + + .share-close-text { + font-size: 14px; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-weight: 700; + } + + .share-close-text { + margin-right: 10px; + color: #fff; + } + + .share-close-icon { + position: relative; + height: 11px; + width: 11px; + top: 2px; + } + + .share-close-icon span { + width: 11px; + height: 1px; + position: absolute; + top: 5px; + right: 0; + background: #fff; + } + + .share-close-icon span:first-of-type { + transform: rotate(45deg); + } + + .share-close-icon span:last-of-type { + transform: rotate(-45deg); + } + + .mobile-authors-list-title { + font-weight: 700; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 14px; + display: block; + color: white; + text-align: left; + margin: 30px 0 10px 18px; + z-index: 1; + } + + .authors-list { + max-height: none; + position: fixed; + background: #000; + width: 100%; + height: 100%; + border: none; + margin-top: 20px; + position: static; + margin: 5px auto 0; + } + + .close { + height: 44px; + width: 44px; + } + + .close span:first-of-type { + height: 20px; + left: 22px; + top: 13px; + } + + .close span:last-of-type { + width: 20px; + left: 13px; + top: 22px; + } + + .-small { + min-height: 34px; + height: 34px; + min-width: 34px; + width: 34px; + } + + .list-item { + border: none; + } + + .list-item:hover { + background: #000; + } + + .list-item:hover .author-wrap { + background: #000; + } + + .list-item:last-of-type { + padding-bottom: 50px; + margin-bottom: 50px; + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + } + } + + .disabled, + .disabled * { + pointer-events: none; + } + ` + } +} +customElements.define('comment-el', Comment) diff --git a/client/src/ui/CommentMarkdownElement.js b/client/src/ui/CommentMarkdownElement.js new file mode 100644 index 0000000..39d7850 --- /dev/null +++ b/client/src/ui/CommentMarkdownElement.js @@ -0,0 +1,168 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' +import { unsafeHTML } from 'lit-html/directives/unsafe-html.js' +import { Parser, HtmlRenderer } from 'commonmark' +import { Globals } from '../globals' + +class CommentMarkdownElement extends LitElement { + render() { + return html` +
+ ${unsafeHTML(this.renderedMarkdown)} + ${!this.expanded && this.truncatedState === 'truncated' + ? html` + { + this.expanded = true + }} + >read more + ` + : ''} + ${this.expanded + ? html` + { + this.expanded = false + }} + >collapse + ` + : ''} +
+ ` + } + + updated() { + const anchors = this.shadowRoot.querySelectorAll('a') + for (let a of anchors) { + const dateMatch = a.href.match(/#(\d{4}-\d{2}-\d{2}T.+)$/i) + if (!dateMatch) { + const anchorMatch = a.href.match(/#(.+$)/i) + if (anchorMatch) { + a.href = 'javascript: void(0)' + const key = anchorMatch[1] + a.onclick = () => { + Globals.spectrogram.annotationLayer.navigateToCommentAnchor(key) + } + } else { + a.target = '_blank' + } + } + } + } + + constructor() { + super() + } + + static get properties() { + return { + markdown: String, + renderedMarkdown: String, + truncatedMarkdown: String, + expanded: Boolean, + truncatedState: String, + } + } + + // // render the markdown using the `markdown` attribute + // // `markdown` is set either by the user or the component + set markdown(markdown) { + this.truncatedState = 'reset' + clearTimeout(this.markdownTimeout) + this.markdownTimeout = setTimeout(() => { + const p = this.shadowRoot.querySelector('p') + if (p && p.offsetHeight > 63) { + this.truncatedState = 'truncated' + } else { + this.truncatedState = 'not-truncated' + } + }, 10) + this.renderMarkdown(markdown).then((r) => (this.renderedMarkdown = r)) + } + + async renderMarkdown(markdown) { + // parse and render Markdown + const reader = new Parser() + const writer = new HtmlRenderer() + this.expanded = false + return writer.render(reader.parse(markdown)) + } + static get styles() { + return css` + .container { + visibility: hidden; + max-height: 4.5em; + overflow: hidden; + position: relative; + } + + .container.not-truncated, + .container.truncated { + max-height: none; + overflow: visible; + visibility: visible; + } + + a { + color: #bbffff; + } + + p { + margin: 0; + position: relative; + } + + .not-truncated p { + visibility: visible; + position: relative; + } + + .truncated > p { + overflow: hidden; + height: 3em; + visibility: visible; + line-height: 1.5em; + position: relative; + display: -webkit-box; + -webkit-line-clamp: 2; + word-wrap: break-word; + -webkit-box-orient: vertical; + } + + .expanded > p { + height: auto; + overflow: visible; + -webkit-line-clamp: initial; + } + .expanded > a { + padding-bottom: 15px; + display: block; + } + + .read-more { + position: relative; + text-decoration: underline; + cursor: pointer; + } + ` + } +} + +customElements.define('comment-markdown-element', CommentMarkdownElement) diff --git a/client/src/ui/CommentsModal.js b/client/src/ui/CommentsModal.js new file mode 100644 index 0000000..58f7411 --- /dev/null +++ b/client/src/ui/CommentsModal.js @@ -0,0 +1,282 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' + +export class CommentsModal extends LitElement { + static get properties() { + return { + author: { type: Object }, + show: { type: Boolean }, + } + } + + static get styles() { + return css` + @keyframes appear { + from { + opacity: 0; + transform: scale(1.1, 1.1); + } + to { + opacity: 1; + transform: scale(1, 1); + } + } + + .author-overlay { + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100vw; + z-index: 55; + max-width: initial; + background: rgba(0, 0, 0, 0.7); + display: fixed; + align-items: center; + justify-content: center; + display: flex; + color: #fff; + animation: appear 0.5s ease; + } + + .author-overlay.closing { + opacity: 0; + transition: opacity 0.25s ease-out; + } + + .author-box { + border: 1px solid #fff; + padding: 24px; + width: 50%; + background: #000; + position: relative; + text-align: center; + max-width: 600px; + box-sizing: border-box; + } + + .author-box-content { + margin-top: 26px; + text-align: center; + } + + .author-close-container { + position: absolute; + top: 0; + right: 0; + } + + .author-close-wrap { + background: none; + border: none; + color: #fff; + display: flex; + padding: 24px; + align-items: center; + cursor: pointer; + width: 100%; + } + + .author-close { + height: 34px; + width: 34px; + border: 1px solid #fff; + border-radius: 50%; + position: relative; + transform: rotate(45deg); + } + + .author-close span { + background: #fff; + } + + .author-close span:first-of-type { + position: absolute; + width: 1px; + top: 8px; + height: 17px; + left: 16px; + } + + .author-close span:last-of-type { + position: absolute; + height: 1px; + width: 17px; + top: 16px; + left: 8px; + } + + #beginCommentTour { + background: none; + border: 1px solid #fff; + color: #fff; + padding: 12px 14px; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1.41px; + font-size: 15px; + text-transform: uppercase; + margin-top: 60px; + cursor: pointer; + font-weight: 500; + transition: 0.2s ease-in; + } + + #beginCommentTour:hover { + background: #212121; + } + + .-small { + height: 115px; + width: 115px; + object-fit: cover; + border-radius: 50%; + overflow: hidden; + } + + .text { + display: flex; + flex-direction: column; + text-align: left; + margin-left: 15px; + align-items: center; + } + + .name { + font-weight: 500; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 18px; + margin-bottom: 3px; + } + + .title { + font-family: 'Roboto Mono', monospace; + text-align: center; + margin-top: 2px; + font-size: 12px; + letter-spacing: 1px; + } + + .author-tutorial-text { + font-family: 'Roboto'; + letter-spacing: 1.4px; + font-size: 16px; + margin: 22px auto 0; + max-width: 480px; + line-height: 1.625; + } + + @media only screen and (max-width: 768px) { + .author-box { + width: 80%; + } + + .author-close { + height: 25px; + width: 25px; + } + + .author-close span:first-of-type { + height: 14px; + top: 5px; + left: 12px; + } + + .author-close span:last-of-type { + width: 14px; + top: 11px; + left: 5px; + } + + #beginCommentTour { + margin-top: 20px; + } + + .-small { + height: 65px; + width: 65px; + } + + .author-tutorial-text { + font-size: 14px; + } + } + ` + } + + constructor() { + super() + } + + set show(show) { + if (this.show && !show) { + this.shadowRoot.querySelector('.author-overlay').classList.add('closing') + setTimeout(() => { + this._show = false + this.requestUpdate() + }, 150) + } else { + this._show = show + this.requestUpdate() + } + } + + get show() { + return this._show + } + + begin() { + this.show = false + const event = new CustomEvent('begin', { detail: { author: this.author } }) + this.dispatchEvent(event) + gtag('event', 'begin_tour', { event_label: this.author.name }) + } + + close() { + this.show = false + } + + render() { + return this.show && this.author && this.author.name + ? html` +
+
+
+ +
+
+ ${this.author.alt} +
+

${this.author.name}

+

${this.author.title}

+
+
+ ${this.author.blurb} +
+ +
+
+
+ ` + : html`` + } +} +customElements.define('comments-modal', CommentsModal) diff --git a/client/src/ui/Controls.js b/client/src/ui/Controls.js new file mode 100644 index 0000000..12080cd --- /dev/null +++ b/client/src/ui/Controls.js @@ -0,0 +1,1806 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' +import { start } from '../util/StartAudio' +import { Times, Config, Globals, Device } from '../globals' +import { throttle } from '../util/Throttle' +import { Scrub } from './Scrub' +import { WindowFocus } from '../util/WindowFocus' +import * as TWEEN from '@tweenjs/tween.js' +import { logScale, getLogPosition, clamp, scaleToRange, distance } from '../util/Math' +import { dateToUTCDateTimeString, msToTime } from '../util/Date' +import { TimeManager } from '../models/TimeManager' +import Tone, { Synth } from 'tone' +import { audioOutput } from '../components/AudioOutput' +import './Annotate' +import './Comment' +import './InfoBubble' +import './Tutorial' +import './SettingsModal' +import './ShareModal' + +export class Controls extends LitElement { + static get properties() { + return { + containerState: { type: String }, + showiOSNotice: { type: Boolean }, + } + } + + static get styles() { + return css` + #logo-small { + position: absolute; + top: 44px; + left: 20px; + border: none; + background: none; + z-index: 4; + cursor: pointer; + color: #fff; + transition: visibility ${Config.transitionTime}, opacity ${Config.transitionTime} linear; + text-transform: uppercase; + width: 24%; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + + /* + Introduced in IE 10. + See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/ + */ + -ms-user-select: none; + user-select: none; + } + } + + #logo-small svg { + height: 100%; + width: 100%; + } + + .about-wrap { + position: absolute; + top: 40px; + right: 20px; + z-index: 4; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + + /* + Introduced in IE 10. + See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/ + */ + -ms-user-select: none; + user-select: none; + } + + .about-button { + font-family: 'Roboto Mono', monospace; + font-weight: 500; + display: flex; + align-items: center; + color: #fff; + text-decoration: none; + font-size: 12px; + transition: 0.2s ease-in all; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + + /* + Introduced in IE 10. + See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/ + */ + -ms-user-select: none; + user-select: none; + } + + .about-button svg { + height: 27px; + width: 27px; + fill: #fff; + margin-left: 10px; + } + + .about-button:hover { + color: #bbffff; + text-shadow: 0 0 5px #bbffff; + background: none; + } + + .pattern { + font-weight: 700; + font-family: 'Roboto Mono', monospace; + letter-spacing: 4px; + font-size: 18px; + } + + .whale { + font-weight: 100; + font-family: 'Roboto Mono', monospace; + letter-spacing: 4px; + font-size: 18px; + } + + tutorial-element { + z-index: 54; + position: relative; + } + + comment-el { + transition: visibility ${Config.transitionTime}, opacity ${Config.transitionTime} linear; + } + + time-slider-element { + transition: visibility ${Config.transitionTime}, opacity ${Config.transitionTime} linear; + } + + share-modal.hide { + opacity: 0; + visibility: hidden; + pointer-events: none; + } + + #play { + transition: visibility ${Config.transitionTime}, opacity ${Config.transitionTime} linear; + } + + #play, + #pause { + cursor: pointer; + position: absolute; + left: 50%; + transform: translateX(-50%); + bottom: 30px; + z-index: 2; + display: block; + background: transparent; + height: 50px; + width: 50px; + transition: opacity 0.15s ease-out; + padding: 0; + border: 0; + } + + #play svg, + #pause svg { + display: block; + width: 100%; + height: 100%; + fill: #fff; + } + + #play:hover svg, + #pause:hover svg { + fill: #bbffff; + -webkit-filter: drop-shadow(0 0 5px #bbffff); + filter: drop-shadow(0 0 5px #bbffff); + } + + #canvas-container { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + } + + #label-start-time { + display: none; + position: absolute; + left: 10px; + bottom: 10px; + z-index: 2; + } + + #label-end-time { + display: none; + position: absolute; + right: 10px; + bottom: 10px; + z-index: 2; + text-align: right; + } + + #label-current-time { + position: absolute; + left: 50%; + padding-left: 2px; + z-index: 2; + top: 20px; + /*pointer-events: none;*/ + } + + #time-slider { + position: absolute; + width: 100%; + z-index: 2; + left: 50%; + bottom: 147px; + transform: translate(-50%, 0); + } + + @media only screen and (max-height: 731px) and (max-width: 411px) { + #time-slider { + bottom: 18vh; + } + } + + @media only screen and (max-height: 612px) and (max-width: 411px) { + #time-slider { + bottom: 21vh; + } + } + + #scale { + position: absolute; + z-index: 2; + left: 1; + top: 1; + } + + #cluster-ui { + position: absolute; + top: 20px; + left: 20px; + z-index: 2; + display: none; + } + + #looking-at { + z-index: 2; + position: absolute; + top: 60px; + left: 20px; + } + + #display-current-time { + position: absolute; + right: 50%; + top: 50%; + padding-right: 14px; + z-index: 2; + font-size: 13px; + text-align: right; + transform: translateY(-50%); + margin-top: -225px; + } + + #display-current-confidence { + position: absolute; + left: 50%; + top: 50%; + padding-left: 14px; + z-index: 2; + font-size: 13px; + transform: translateY(-50%); + margin-top: -225px; + } + + #get-time { + position: absolute; + z-index: 4; + bottom: 10px; + right: 10px; + color: #818181; + font-size: 12px; + padding: 15px; + display: none; + } + + #tutorialBtn { + position: absolute; + z-index: 3; + left: 10px; + top: 10px; + } + + .controls { + display: flex; + position: absolute; + bottom: 30px; + left: 30px; + transition: visibility ${Config.transitionTime}, opacity ${Config.transitionTime} linear; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + + /* + Introduced in IE 10. + See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/ + */ + -ms-user-select: none; + user-select: none; + } + + .control-link-item { + cursor: pointer; + display: flex; + flex-direction: column; + color: #fff; + flex-direction: column; + align-items: center; + justify-content: flex-end; + background: none; + border: none; + text-decoration: none; + padding: 0; + transition: 0.2s ease-in; + outline: none; + } + + .control-link-item.disabled { + opacity: 0.4; + } + + .control-link-item svg { + fill: #fff; + transition: 0.2s ease-in; + } + + .control-link-item:focus, + .control-link-item:active, + .control-link-item:hover { + color: #bbffff; + text-shadow: 0 0 5px #bbffff; + } + + .control-link-item:focus svg, + .control-link-item:active svg, + .control-link-item:hover svg { + fill: #bbffff; + -webkit-filter: drop-shadow(0 0 5px #bbffff); + filter: drop-shadow(0 0 5px #bbffff); + } + + .control-link-item.external { + margin-left: 25px; + } + + .control-link-item.help-mobile { + display: none; + } + + .control-link-item:not(:first-of-type) { + margin-left: 25px; + } + + .control-link { + position: relative; + top: -20px; + } + + .control-link svg { + height: 100%; + width: 100%; + } + + .settings { + height: 15px; + width: 25px; + } + + .share-link { + height: 22px; + width: 22px; + top: -14px; + } + + .about { + height: 25px; + width: 25px; + top: -14px; + } + + .control-text { + font-size: 12px; + font-family: 'Roboto Mono', monospace; + } + + .control-arrows { + transform-origin: right; + position: absolute; + bottom: 30px; + right: 30px; + display: flex; + flex-direction: row; + align-items: center; + z-index: 4; + transition: visibility ${Config.transitionTime}, opacity ${Config.transitionTime} linear; + } + + .ios-silent { + padding: 12px 16px; + color: #000; + font-size: 14px; + font-family: 'Roboto'; + letter-spacing: 1.55px; + line-height: 1.714; + position: absolute; + background: #bbffff; + text-align: center; + border-radius: 4px; + z-index: 100; + width: 280px; + bottom: 180px; + left: 50%; + transform: translateX(-50%); + } + + .up, + .down { + padding: 6px; + background: none; + border: 1px solid rgba(255, 255, 255, 1); + display: block; + height: 25px; + width: 25px; + cursor: pointer; + transition: 0.15s ease-in; + -webkit-tap-highlight-color: rgba(0,0,0,0); + outline: 0; + } + + .up.disabled, + .down.disabled { + pointer-events: none; + opacity: 0.3; + } + + .up:hover, + .down:hover { + background: rgb(47, 47, 47); + } + + .up svg, + .down svg { + height: 100%; + width: 100%; + pointer-events: none; + fill: #fff; + transition: opacity 0.15s ease-out; + } + + .vertical { + display: flex; + flex-direction: column; + margin-left: 30px; + width: 25px; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + + /* + Introduced in IE 10. + See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/ + */ + -ms-user-select: none; + user-select: none; + } + + .down { + position: relative; + top: -1px; + } + + ui.disabled, + .disabled { + opacity: 0.5; + pointer-events: none !important; + } + + .hidden { + visibility: hidden; + opacity: 0; + } + + .ui { + visibility: visible; + opacity: 1; + pointer-events: auto; + } + + intro-element { + transition: visibility 2s, opacity 2s ease-out, filter 2s ease-out; + filter: blur(0px); + } + + intro-element.hidden { + pointer-events: none; + filter: blur(60px); + opacity: 0; + visibility: hidden; + } + + .intro .ui, + .intro .ui.show { + visibility: hidden; + opacity: 0; + filter: blur(20px); + } + + .tutorial .ui { + visibility: visible; + opacity: 0.4; + pointer-events: none; + } + + .tutorial .ui.show { + visibility: visible; + opacity: 1; + pointer-events: auto; + } + + .settings-section-container { + color: #fff; + background: #000; + position: absolute; + top: -390px; + left: 0; + padding: 33px 13%; + width: 20vw; + max-width: 550px; + z-index: 10; + border: 1px solid white; + } + + .settings-title { + font-weight: 500; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 12px; + } + + .settings-section-title { + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 14px; + margin-bottom: 20px; + } + + .settings-section { + border-top: 1px solid #4c4c4c; + padding: 24px 0; + } + + .settings-section-radio-title { + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 13px; + } + + .settings-section--radio { + display: flex; + border-top: none; + flex-direction: row; + align-items: center; + padding: 24px 0; + } + + .settings-section--audio { + padding-bottom: 0; + } + + .settings-section-input { + margin-right: 10px; + } + + /* styled fields */ + .container { + display: inline-block; + position: relative; + padding-left: 30px; + margin-bottom: 12px; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .radio-wrap { + display: flex; + border: 1px solid white; + height: 23px; + position: relative; + width: 52px; + padding: 0px; + border-radius: 14px; + } + + .container input { + position: absolute; + opacity: 0; + cursor: pointer; + } + + .checkmark { + position: absolute; + top: 0; + left: 0; + height: 21px; + width: 21px; + background-color: #000; + border-radius: 50%; + border: 1px solid #fff; + } + + .container:hover input ~ .checkmark { + background-color: rgba(187, 255, 255, 0.4); + } + + .container input:checked ~ .checkmark:before { + content: ' '; + position: absolute; + z-index: 1; + top: 5px; + left: 5px; + right: 5px; + bottom: 5px; + border: 5px solid #bbffff; + border-radius: 50%; + } + + .checkmark:after { + content: ''; + position: absolute; + display: none; + } + + .container input:checked ~ .checkmark:after { + display: block; + } + + .radio-wrap .container .checkmark { + border: none; + } + + .radio-wrap .container input:checked ~ .checkmark:before { + content: ' '; + position: absolute; + z-index: 1; + top: 3px; + left: 3px; + right: 3px; + bottom: 3px; + border: 8px solid #bbffff; + border-radius: 50%; + } + + #audio-loading { + position: absolute; + bottom: 90px; + right: 50%; + transform: translateX(50%); + font-size: 12px; + color: #fff; + font-weight: bold; + pointer-events: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + visibility: hidden; + opacity: 0; + transition: visibility 0.8s, opacity 0.8s linear; + } + + #audio-loading span { + display: inline-block; + vertical-align: middle; + } + + #audio-loading.show { + visibility: visible !important; + opacity: 1 !important; + } + + .circle-container { + display: inline-block; + vertical-align: middle; + width: 16px; + height: 16px; + margin-right: 2px; + } + + .circle { + width: 100%; + height: 100%; + position: relative; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + } + + .circular-loader { + height: 100%; + width: 100%; + position: absolute; + top: 0; + left: 0; + transition: all 0.1s linear; + } + + .loader-path { + stroke: #ffffff; + stroke-dasharray: 120; + animation: dash 1.5s linear 0s infinite; + animation-fill-mode: both; + stroke-linecap: round; + transform-origin: center center; + } + + @keyframes dash { + 0% { + stroke-dashoffset: 120; + } + 50% { + stroke-dashoffset: 30; + transform: rotate(240deg); + } + 100% { + stroke-dashoffset: 120; + transform: rotate(720deg); + } + } + + @media only screen and (max-width: 1065px) { + #logo-small { + width: 263px; + } + } + + @media only screen and (max-width: 957px) { + #logo-small { + top: 20px; + left: 10px; + width: 215px; + } + + .about-wrap { + z-index: 54px; + top: 17px; + right: 20px; + } + + .about-wrap span { + visibility: hidden; + } + + .about-wrap span::after { + content: 'About'; + visibility: visible; + } + + .outer { + position: relative; + left: 4px; + top: -1px; + } + + .controls { + left: 18px; + } + + .external, + .control-text { + display: none; + } + + .control-link-item.help-mobile { + display: block; + position: absolute; + bottom: 28px; + right: 83px; + z-index: 5; + } + } + + /* Landscape */ + @media only screen and (max-height: 499px) and (orientation: landscape), (min-aspect-ratio: 3/1) { + #logo-small, + .ui, + .control-link-item.help-mobile, + time-slider-element { + display: none; + } + + #play, + #pause { + bottom: 140px; + } + + #audio-loading { + bottom: 200px; + } + } + + + @media (hover: none) { + .up:hover, + .down:hover { background: transparent; } + } + ` + } + + constructor() { + super() + this.settingsOpen = false + this.shareUrl + this.iOS = !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform) + this.showiOSNotice = false + this.hasShowniOSNotice = false + Times.scaleStartTimeDate = new Date() + Times.currentTimeDate = new Date() + Times.scaleEndTimeDate = new Date() + this._location = null + // Duration of a tile within the window + this.duration = parseFloat(Config.defaultDuration) + if (!Config.skipIntro) { + this.duration /= Config.initialZoom + } + const fps = 60 + const pxPerSecond = 20 + const initialScrubTime = 120 + this.initialScrubIncrement = + (TimeManager.calcIdealWindowDuration(this.duration) * pxPerSecond) / fps / window.innerWidth + this.maxInitialScrub = Config.defaultPosition + this.initialScrubIncrement * fps * initialScrubTime + // The length of the location's audio + this.length = 0 + this.position = 0 + this.firstLocation = true + this.showAudioLoading = false + this.disableIntro = Config.skipIntro + this.disableAnnotate = !Config.annotate + this.containerState = this.disableIntro ? 'active' : 'intro' + this.tweens = { + position: new TWEEN.Tween(this), + duration: new TWEEN.Tween(this), + linearDuration: new TWEEN.Tween(this), + } + this.tweens.position + .easing(TWEEN.Easing.Quadratic.InOut) + .onStart(() => { + this.pauseWhileScrubbing() + }) + .onUpdate(() => { + this.pauseWhileScrubbing() + }) + .onComplete(() => { + if (this.scrub.moving) { + this.scrub.dragStop() + } + Globals.events.emit('positionTweenComplete') + }) + this.tweens.duration.easing(TWEEN.Easing.Quadratic.Out).onComplete((controls) => { + this.linearScale = getLogPosition( + controls.duration, + parseFloat(Config.minDuration), + parseFloat(Config.maxDuration) + ) + this.linearScaleSnapshot = this.linearScale + Globals.events.emit('durationTweenComplete') + }) + this.tweens.linearDuration.onComplete((controls) => { + this.linearScale = getLogPosition( + controls.duration, + parseFloat(Config.minDuration), + parseFloat(Config.maxDuration) + ) + this.linearScaleSnapshot = this.linearScale + }) + this.uiVisibilityState = Config.skipIntro ? 10 : 0 + } + + set location(location) { + this._location = location + Globals.currentLocation = location + if (this.firstLocation) { + this.position = parseFloat(Config.defaultPosition) - this.location.startTime + if (parseFloat(Config.defaultPosition) < this.location.startTime) { + this.position = parseFloat(Config.defaultPosition) + } + if (Config.time) { + this.position = Config.time - this.location.startTime + } + this.firstLocation = false + } else { + this.position = 0 + } + Globals.timeManager.setCurrentTime(this.location.startTime + this.position) + + this.$$canvas.currentLocation = location + this.length = this._location.duration + + // TODO: get rid of this timeout + setTimeout(() => { + this.$$timeSlider.value = this.position + this.$$timeSlider.date = this.position + Globals.currentLocation.startTime + }, 1) + this.linearScale = getLogPosition(this.duration, parseFloat(Config.minDuration), parseFloat(Config.maxDuration)) + // used for pinch zoom + this.linearScaleSnapshot = this.linearScale + this.requestUpdate() + } + + get location() { + return this._location + } + + set duration(duration) { + this.debounceTileLoading() + this._duration = duration + this.requestUpdate() + } + + get duration() { + return this._duration + } + + // Setting app position debounces tile and audio loading by default + // use syncPositions on its own to set app position without debouncing + set position(position) { + this.debounceAudioLoading() + this.debounceTileLoading() + this.syncPositions(position) + } + + get position() { + return this._position + } + + syncPositions(position) { + this._position = position + if (Globals.player) { + const time = this.location.startTime + this.position + this.$$timeSlider.value = position + this.$$timeSlider.date = time + Globals.timeManager.setCurrentTime(time) + if (!Globals.player.playing) { + Globals.player.time = time + } + Globals.spectrogram.time = time + } + } + + // Read only prop, use duration to adjust scale + get scale() { + return this.duration / this.length + } + + saveAnnotation(event) { + const start = new Date(Globals.timeManager.pxToTime(event.detail.startX)) + const end = new Date(Globals.timeManager.pxToTime(event.detail.endX)) + + const text = `${this.location.name}\t${start.toISOString()}\t${end.toISOString()}` + navigator.clipboard.writeText(text) + } + + navigateTo(position, _duration, _callback) { + const duration = clamp(_duration, Config.minDuration, Config.maxDuration) + + const durationDiff = this.duration - duration + const posDiff = Globals.timeManager.currentTime - position + // Calculate how many screen widths its moving + const screenMoveFactor = Math.abs(posDiff / Math.max(_duration, this.duration)) + + const dur = 750 + let durDelay = 0 + let posDelay = 0 + + // Determine if one of the tweens should be delayed (let it zoom out first for example) + if (durationDiff > this.duration * 0.1) { + durDelay = dur * 0.7 + } else if (-durationDiff > this.duration * 0.1) { + posDelay = dur * 0.7 + } + + // If we need to move far, do an initial zoom out + let initialTween + if (screenMoveFactor > 5 && this.duration != Config.maxDuration) { + let moveDuration = (Math.max(_duration, this.duration) * screenMoveFactor) / 5 + if (moveDuration > this.duration) { + initialTween = new TWEEN.Tween(this).to({ duration: moveDuration }, dur) + durDelay = dur * 0.7 + posDelay = 0 + } + } + + // Do the tweening + let posTween = this.tweens.position + .stop() + .delay(posDelay) + .to({ position: position - Globals.currentLocation.startTime }, dur) + let durTween = this.tweens.duration + .stop() + .delay(durDelay) + .to({ duration }, dur) + + // Start the tweening + if (!initialTween) { + durTween.start() + posTween.start() + } else { + // If there is an initial tween, delay the others + initialTween.chain(durTween, posTween).start() + } + + const callback = () => { + _callback() + // We need to set the duration for updates to happen + this.duration = duration + } + + if (posDelay < posDelay) { + Globals.events.once('durationTweenComplete', callback) + } else { + Globals.events.once('positionTweenComplete', callback) + } + } + + /** + * Inherited method from lit-el + * called when this module first renders + * + * Here's where we have all of our events + */ + async firstUpdated() { + this.$$canvas = this.shadowRoot.querySelector('canvas-element') + this.$$timeSlider = this.shadowRoot.querySelector('#time-slider') + this.$$playButton = this.shadowRoot.querySelector('#play') + this.$$pause = this.shadowRoot.querySelector('#pause') + this.$$comment = this.shadowRoot.querySelector('comment-el') + this.$$infoBubble = this.shadowRoot.querySelector('info-bubble') + this.$$settingsModal = this.shadowRoot.querySelector('settings-modal') + this.$$shareModal = this.shadowRoot.querySelector('share-modal') + this.$$intro = this.shadowRoot.querySelector('intro-element') + this.$$tutorial = this.shadowRoot.querySelector('tutorial-element') + + this.$$intro.addEventListener('close', () => { + this.disableIntro = true + if (!this.hasShowniOSNotice && this.iOS) { + this.showiOSNotice = true + this.hasShowniOSNotice = true + const noticeCloseHandler = () => { + this.showiOSNotice = false + document.removeEventListener('touchstart', noticeCloseHandler) + } + document.addEventListener('touchstart', noticeCloseHandler) + } + this.containerState = 'active' + this.shadowRoot.querySelector('comment-el').disabled = false + setTimeout(() => { + this.$$canvas.canvas.style.filter = 'none' + }, 1000) + + if (!this.$$intro.hasShown && Globals.spectrogram) { + const initialTweenTime = 750 + this.tweens.position + .stop() + .to( + { + position: parseFloat(Config.defaultPosition) - Globals.currentLocation.startTime, + }, + initialTweenTime + ) + .start() + this.tweens.duration + .stop() + .to( + { + duration: Config.defaultDuration, + }, + initialTweenTime + ) + .start() + } + this.requestUpdate() + }) + this.$$comment.hide() + + this.$$classifications = this.shadowRoot.querySelector('#classifications') + this.$$timeSlider.addEventListener('input', (_event) => { + this.position = parseFloat(this.$$timeSlider.value, 10) + }) + this.$$playButton.addEventListener('click', (_event) => { + gtag('event', 'click_play') + this.playAudio() + }) + this.$$pause.addEventListener('click', (_event) => { + gtag('event', 'click_pause') + this.pauseAudio() + }) + const handleDraw = throttle(() => { + this._updateTimecodes() + }, 100) + Globals.events.on('draw', (_event) => { + handleDraw() + }) + document.body.addEventListener('keyup', (e) => { + if (!this.$$intro.showing && e.keyCode === 32) { + if (this.playing) { + this.pauseAudio() + } else { + this.playAudio() + } + } + }) + Globals.events.on('load', () => { + this.requestUpdate() + this.$$comment.requestUpdate() + Globals.pixiApp.stage.visible = true + }) + + this.scrub = new Scrub(this.$$canvas) + Globals.events.on('scrub', (event) => { + if (this.tweens.position.isPlaying()) return + this.pauseWhileScrubbing() + // Time they start dragging + const targetMoveDuration = this.location.startTime + event.detail.duration + + let position = event.detail.dragStartTime - targetMoveDuration + + // Set Bounds + position = clamp(position, 0, this.location.duration) + // Update the slider pos and the position propertiy + this.position = position + }) + Globals.events.on('move', (event) => { + if (this.tweens.position.isPlaying()) return + this.pauseWhileScrubbing() + let position = this.position + event.detail.distance + position = clamp(position, 0, this.location.duration) + this.position = position + }) + + Globals.events.on('pinchStart', () => { + this.linearScaleSnapshot = this.linearScale + }) + Globals.events.on('wheel', () => { + this.maxIn = false + this.maxOut = false + this.clearZoomInterval() + }) + Globals.events.on('pinch', (event) => { + if (this.tweens.duration.isPlaying()) return + const screenDistance = distance(0, window.innerHeight, window.innerWidth, 0) + const zoomMagnitude = 60 + const zoom = scaleToRange(event.detail.distance, 0, screenDistance, 0, zoomMagnitude) + this.linearScale = clamp(this.linearScaleSnapshot - zoom, 0, 100) + this.duration = logScale(this.linearScale, parseFloat(Config.minDuration), parseFloat(Config.maxDuration)) + }) + + Globals.events.on('verticalScroll', (event) => { + if (this.tweens.duration.isPlaying()) return + const speed = 0.03 * event.detail.deltaY + this.linearScale = clamp(this.linearScale + speed, 0, 100) + this.duration = logScale(this.linearScale, parseFloat(Config.minDuration), parseFloat(Config.maxDuration)) + }) + + this.$$classifications.addEventListener('input', async (_event) => { + Config.isCluster = this.$$classifications.checked + }) + + Globals.events.on('tutorial', (event) => { + let step = event.step + + this.shadowRoot.querySelector('.control-arrows').classList.remove('show') + this.shadowRoot.querySelector('.controls').classList.remove('show') + this.shadowRoot.querySelector('#time-slider').classList.remove('show') + this.shadowRoot.querySelector('comment-el').classList.remove('show') + this.shadowRoot.querySelector('comment-el').disabled = true + switch (step) { + case 1: + this.playAudio() + break + case 4: + this.shadowRoot.querySelector('.control-arrows').classList.add('show') + break + case 9: + this.shadowRoot.querySelector('#time-slider').classList.add('show') + case 10: + this.shadowRoot.querySelector('.controls').classList.add('show') + break + case 11: + this.shadowRoot.querySelector('comment-el').classList.add('show') + this.shadowRoot.querySelector('comment-el').disabled = false + break + } + }) + + Globals.events.on('resize', () => { + if (!Globals.fullscreen) { + this.$$comment.show() + } else { + this.$$comment.hide() + } + }) + + const windowFocus = new WindowFocus() + windowFocus.on('focus-change', (bool) => { + if (!bool && document.body.classList.contains('touch-device')) { + this.pauseAudio() + } + }) + } + + async playAudio() { + // activate Tone + new Tone.Synth().connect(audioOutput).toMaster() + await start() + Globals.player.start() + this.$$playButton.style.display = 'none' + this.$$pause.style.display = 'flex' + this.playing = true + this.pausedForScrubbing = false + } + + pauseAudio() { + Globals.player.pause() + this.$$playButton.style.display = 'flex' + this.$$pause.style.display = 'none' + this.playing = false + } + + pauseWhileScrubbing() { + if (this.playing) { + this.pausedForScrubbing = true + this.showAudioLoading = true + this.pauseAudio() + } else { + if (this.pausedForScrubbing) { + clearTimeout(this.replayTimeout) + this.replayTimeout = setTimeout(() => { + this.playAudio() + }, 100) + } + } + } + + debounceAudioLoading() { + Globals.isScrubbing = true + clearTimeout(this.mouseMoveTimeout) + this.mouseMoveTimeout = setTimeout(() => { + Globals.isScrubbing = false + }, 500) + } + + debounceTileLoading() { + const debounceTime = 20 + if (Globals.spectrogram && !this.scrub.handlingPinchMomentum) { + Globals.spectrogram.tileWindowChanging = true + clearTimeout(this.tileWindowTimeout) + this.tileWindowTimeout = setTimeout(() => { + Globals.spectrogram.tileWindowChanging = false + }, debounceTime) + } + } + + _updateTimecodes() { + if (!Globals.player) return + const position = Globals.player.time + Times.scaleStartTimeMs = position - this.duration / 2 + Times.currentTimeMs = position + Times.scaleEndTimeMs = position + this.duration / 2 + + Times.scaleStartTimeDate = new Date(Times.scaleStartTimeMs) + Times.currentTimeDate = new Date(Times.currentTimeMs) + Times.scaleEndTimeDate = new Date(Times.scaleEndTimeMs) + this.requestUpdate() + } + + currentTimeClick() { + this.$$shareModal.open = !this.$$shareModal.open + let url = location.protocol + '//' + location.host + location.pathname + + let anchor = '#' + Math.round(Globals.player.time / 1000) + anchor += 'z' + Globals.spectrogram.getCurrentZoomLevel() + + if (Globals.currentLocation.name && Globals.currentLocation.name != Config.defaultLocation) { + url += `?location=${Globals.currentLocation.name}` + } + + url += anchor + this.$$shareModal.url = url + this.$$shareModal.timestamp = Globals.player.time + } + + showIntro() { + this.disableIntro = false + this.containerState = 'intro' + if (Globals.player.playing) { + this.pauseAudio() + } + // if (this.$$tutorial.showing) { + // this.$$tutorial.hide() + // } + this.$$intro.show() + this.$$canvas.canvas.style.filter = 'blur(10px)' + } + + handleZoomButton(e) { + const direction = e.currentTarget.classList.contains('up') ? -1 : 1 + const zoomRepeat = 90 + this.doZoom(direction) + this.zoomInterval = setInterval(() => { + this.doZoom(direction) + }, zoomRepeat) + } + + doZoom(direction) { + if ( + (direction === 1 && this.duration === Config.maxDuration) || + (direction === -1 && this.duration === Config.minDuration) + ) { + return + } + this.maxIn = false + this.maxOut = false + const zoomTime = 100 + const zoomMagnitude = 0.5 + const speed = this.duration * zoomMagnitude * direction + this.tweens.linearDuration + .stop() + .to( + { + duration: clamp(this.duration + speed, Config.minDuration, Config.maxDuration), + }, + zoomTime + ) + .start() + } + + clearZoomInterval() { + if (this.duration <= Config.minDuration + 1000) { + this.maxIn = true + } + if (this.duration >= Config.maxDuration - 1000) { + this.maxOut = true + } + clearInterval(this.zoomInterval) + } + + get loaded() { + if (Globals.spectrogram) { + return Globals.spectrogram.loaded && Globals.spectrogram.classificationLayer.loaded + } else { + return false + } + } + + get audioLoading() { + return ( + (this.playing || this.pausedForScrubbing) && + (this.showAudioLoading || + (Globals.player && (Globals.player.buffering || Globals.player.playingWithoutAudio))) + ) + } + + render() { + return html` + +
+ +
+ + About the project + +
+ + + + + +
+ +

+ Start Position
+ + ${msToTime(Times.currentTimeMs - this.duration / 2)} + +

+ ${this.location && Config.debug + ? html` +

+ Current Position
+ ${msToTime(Times.currentTimeMs)}
+ Seconds: ${Times.currentTimeMs - this.location.startTime} | Time: + ${Times.currentTimeMs}
+ ${dateToUTCDateTimeString(Times.currentTimeDate)} | Duration: ${this.duration} +
+
${Globals.player + ? html` + Filename: ${Globals.player.currentFilename} + ` + : ''} +

+ ` + : ''} +

+ End Position
+ ${msToTime(Times.currentTimeMs + this.duration / 2)} +

+ ${this.lookingAt + ? html` +
+

Looking at:

+

+ classifier: ${this.lookingAt.classifier} +

+

label: ${this.lookingAt.label}

+

score: ${this.lookingAt.score}

+

+ time_start: ${this.lookingAt.time_start} + ${dateToUTCDateTimeString(new Date(this.lookingAt.time_start))} +

+

+ time_end: ${this.lookingAt.time_end}
+ ${dateToUTCDateTimeString(new Date(this.lookingAt.time_end))} +

+
+ ` + : html``} + +
+ + ${!this.loaded && (Config.skipIntro || (this.$$intro && !this.$$intro.showing)) + ? html` + + ` + : html``} +
+ + + + + +
+ +
+
+ + +
+
+
+
+
+ + + +
+
+ Loading audio +
+ + + ${this.showiOSNotice + ? html` +
+ Heads up — if you have your iOS device in Silent Mode, audio playback is affected. +
+ ` + : ''} + + +
+ ` + } +} + +export function setControlsPosition(time) { + if (Globals.controls && Globals.currentLocation && time >= 0) { + let position = Globals.controls.position + // set position based on a % of current location total duration + if (time <= 1) { + position = time * Globals.currentLocation.duration + } else { + // set position based on epoch timestamp + position = time - Globals.currentLocation.startTime + } + Globals.controls.position = position + } +} +window.setControlsPosition = setControlsPosition + +export function setControlsDuration(duration) { + if (Globals.controls && Globals.currentLocation && duration >= 0) { + // const duration = Globals.controls.duration + // console.log(duration) + Globals.controls.linearScale = getLogPosition( + duration, + parseFloat(Config.minDuration), + parseFloat(Config.maxDuration) + ) + Globals.controls.duration = duration + } +} + +export function getControlsInfo() { + return { + time_stamp: Globals.player.time, + position: Globals.player.time - Globals.player.startTime, + duration: Globals.spectrogram.duration, + location: Globals.spectrogram.location, + zoom: 1 - Globals.controls.duration / parseFloat(Config.maxDuration), + } +} diff --git a/client/src/ui/Dat.js b/client/src/ui/Dat.js new file mode 100644 index 0000000..09ce221 --- /dev/null +++ b/client/src/ui/Dat.js @@ -0,0 +1,22 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import * as dat from 'dat.gui' +import { Config } from '../globals' + +export let GUI +if (Config.datgui) { + GUI = new dat.GUI() + GUI.close() +} diff --git a/client/src/ui/InfoBubble.js b/client/src/ui/InfoBubble.js new file mode 100644 index 0000000..29ba725 --- /dev/null +++ b/client/src/ui/InfoBubble.js @@ -0,0 +1,154 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' +import { Config } from '../globals' + +export class InfoBubble extends LitElement { + static get properties() { + return { + author: { type: String }, + authorObj: { type: Object }, + open: { type: Boolean }, + text: { type: String }, + } + } + + static get styles() { + return css` + #container { + pointer-events: none; + position: absolute; + z-index: 55; + display: flex; + justify-content: row; + border: 1px solid #fff; + padding: 12px 18px; + color: #fff; + height: fit-content; + align-items: center; + background: black; + max-width: 450px; + opacity: 0; + margin-top: 0px; + transition: opacity 0.15s ease-out 0, margin 0.25s ease-out 0; + } + + #container.visible { + margin-top: -5px; + opacity: 1; + visibility: visible; + transition: opacity 0.25s ease-out, margin 0.25s ease-out 0s; + } + + .info-bubble-image { + width: 34px; + height: 34px; + object-fit: cover; + border-radius: 50%; + border: 1px solid #bbffff; + margin-right: 14px; + } + + .info-bubble-name { + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 14px; + margin: 0; + font-weight: 500; + } + + .info-bubble-title { + font-family: 'Roboto Mono', monospace; + margin-top: 2px; + font-size: 12px; + letter-spacing: 1.67; + margin: 0 0 7px 0; + } + ` + } + + constructor() { + super() + this._coords = { + x: 0, + y: 0, + } + + this.containerHeight = 68 + } + + set author(author) { + this._author = author + if (this.author !== null) { + this.authorObj = Config.authors[this.author] + this.open = true + } else { + this.authorObj = null + this.open = false + } + } + + set text(text) { + this.authorObj = null + this._text = text + this.open = true + } + + firstUpdated() { + this.$$container = this.shadowRoot.querySelector('#container') + } + + get author() { + return this._author + } + + set coords(coords) { + this._coords = coords + this.requestUpdate() + } + + get coords() { + return this._coords + } + + render() { + let content = html`` + if (this.authorObj) { + content = html` + +
+

${this.authorObj.name}

+

${this.authorObj.title}

+
+ ` + } else if (this._text) { + content = html` +
+

${this._text}

+
+ ` + } + return html` +
+ ${content} +
+ ` + } +} +customElements.define('info-bubble', InfoBubble) diff --git a/client/src/ui/Intro.js b/client/src/ui/Intro.js new file mode 100644 index 0000000..868bc09 --- /dev/null +++ b/client/src/ui/Intro.js @@ -0,0 +1,695 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' +import { Config } from '../globals' + +export class Intro extends LitElement { + static get properties() { + return { + tutorial: { type: Number }, + } + } + + static get styles() { + return css` + :host { + display: flex; + width: 100%; + background: rgba(0, 0, 0, 0.5); + position: fixed; + z-index: 4; + height: 100%; + top: 0; + left: 0; + align-items: center; + justify-content: center; + text-align: center; + color: #fff; + filter: blur(0); + transition: visibility 1s, opacity 1s linear, filter 1s linear; + } + .hidden { + filter: blur(10px); + } + + .intro { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + } + + .intro-content { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + } + + .logo { + font-family: 'Roboto Mono', 'monospace'; + letter-spacing: 0.2em; + font-size: 60px; + text-transform: uppercase; + padding: 0 20px; + font-weight: 100; + } + + .logo span { + font-family: 'Roboto Mono'; + font-weight: 500; + } + + .excerpt { + margin-top: 1em; + font-weight: 400; + font-family: 'Roboto Mono', monospace; + max-width: 800px; + line-height: 1.81; + letter-spacing: 0.12em; + font-size: 18px; + max-width: 900px; + width: 80%; + font-style: inherit; + } + + .buttons { + display: flex; + flex-direction: column; + margin-top: 50px; + } + + .cursor, + .play { + display: inline-block; + position: relative; + margin-right: 12px; + top: 4px; + } + + .cursor { + width: 16px; + } + + .play { + width: 20px; + } + + .cursor svg, + .play svg { + height: 100%; + width: 100%; + } + + .intro-button { + border: 1px solid #fff; + padding: 0 16px; + color: #fff; + text-decoration: none; + font-family: 'Roboto Mono', monospace; + font-size: 16px; + text-transform: uppercase; + letter-spacing: 0.12em; + font-weight: 500; + transition: 0.2s ease-in all; + width: 220px; + height: 70px; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + } + + .intro-button--about { + margin-top: 47px; + border: none; + font-family: 'Roboto'; + font-weight: 500; + font-size: 12px; + text-transform: initial; + display: flex; + align-items: center; + } + + .intro-button--about svg { + height: 30px; + width: 30px; + fill: #fff; + margin-left: 10px; + } + + .intro-button:hover { + background: rgba(255, 255, 255, 0.13); + } + + .intro-button--about:hover { + opacity: 0.6; + background: none; + } + + .footer { + position: absolute; + bottom: 20px; + left: 20px; + display: flex; + align-items: center; + } + + .intro-credit { + display: flex; + align-items: center; + } + + .noaa { + height: 50px; + width: 50px; + margin-right: 18px; + } + + .google { + width: 68px; + height: 44px; + margin-left: 18px; + } + + .divider { + background: #fff; + height: 48px; + width: 1px; + } + + .experiment { + width: 82px; + height: 56px; + margin: 0 18px; + } + + .experiment svg, + .google svg, + .noaa svg { + fill: #fff; + height: 100%; + width: 100%; + } + + @media only screen and (max-width: 768px) { + .logo { + font-size: 28px; + } + + .intro { + margin: -19% 18px 0; + } + + .noaa { + height: 42px; + width: 42px; + margin-right: 10px; + } + + .experiment { + width: 74px; + height: 42px; + margin: 0 10px; + } + + .divider { + height: 42px; + } + + .google { + width: 62px; + height: 42px; + margin-left: 10px; + } + + .excerpt { + width: 90%; + } + + .intro-button { + padding: 20px 22px 18px; + } + + .intro-button--about { + margin-top: 24px; + } + + .footer { + width: 100%; + left: 0; + justify-content: center; + } + } + + @media only screen and (max-width: 400px) { + .buttons { + margin-top: 40px; + } + + .intro-button { + padding: 7% 10% 8%; + } + + .logo { + font-size: 23px; + } + } + /* Landscape */ + @media only screen and (max-height: 499px) and (orientation: landscape) { + .intro { + margin-top: -40px; + } + .logo { + font-size: 24px; + } + .excerpt { + font-size: 14px; + } + .intro-button { + font-size: 14px; + padding: 2px; + height: 60px; + } + .buttons { + height: 50px; + margin-top: 10px; + } + } + ` + } + + constructor() { + super() + this.showing = !Config.skipIntro + this.hasShown = Config.skipIntro + + if (this.showing) { + gtag('event', 'screen_view', { screen_name: 'Splashscreen' }) + } + } + + hide() { + this.classList.add('hidden') + this.showing = false + const event = new CustomEvent('close') + this.dispatchEvent(event) + this.hasShown = true + + gtag('event', 'screen_view', { screen_name: 'Main' }) + } + + show() { + this.classList.remove('hidden') + this.showing = true + + gtag('event', 'screen_view', { screen_name: 'Splashscreen' }) + } + + tutorial() { + this.hide() + const event = new CustomEvent('tutorial') + this.dispatchEvent(event) + } + + explore() { + this.hide() + } + + render() { + return html` +
+ + +

+ Use AI to explore thousands of hours of humpback whale songs and make your own discoveries. +

+
+ Start Exploring + +
+ +
+ ` + } +} diff --git a/client/src/ui/Loader.js b/client/src/ui/Loader.js new file mode 100644 index 0000000..d695cc0 --- /dev/null +++ b/client/src/ui/Loader.js @@ -0,0 +1,119 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' + +export class Loader extends LitElement { + static get properties() { + return { + background: { type: Boolean }, + } + } + static get styles() { + return css` + #container { + width: 100vw; + height: 100vh; + position: fixed; + left: 0; + top: 0; + color: white; + text-align: center; + transition: opacity 0.5s ease-out; + display: none; + } + + .bg { + background: black; + } + + #container.active { + display: block; + } + + .circle { + width: 10vw; + height: 10vw; + position: relative; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + min-height: 60px; + min-width: 60px; + max-height: 120px; + max-width: 120px; + } + + .circular-loader { + height: 100%; + width: 100%; + position: absolute; + top: 0; + left: 0; + transition: all 0.1s linear; + } + + .loader-path { + stroke: #808080; + stroke-dasharray: 120; + animation: dash 1.5s linear 0s infinite; + animation-fill-mode: both; + stroke-linecap: round; + transform-origin: center center; + } + + @keyframes dash { + 0% { + stroke-dashoffset: 120; + } + 50% { + stroke-dashoffset: 30; + transform: rotate(240deg); + } + 100% { + stroke-dashoffset: 120; + transform: rotate(720deg); + } + } + ` + } + + constructor() { + super() + this.showing = true + } + show() { + this.showing = true + this.requestUpdate() + } + + hide() { + this.showing = false + this.requestUpdate() + } + + render() { + console.log(this.background) + + return html` +
+
+ + + +
+
+ ` + } +} diff --git a/client/src/ui/LocationPicker.js b/client/src/ui/LocationPicker.js new file mode 100644 index 0000000..b4ad1b5 --- /dev/null +++ b/client/src/ui/LocationPicker.js @@ -0,0 +1,78 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' + +export class LocationPicker extends LitElement { + static get styles() { + return css` + #location-group { + color: white; + } + + .offscreen { + position: absolute; + left: -10000px; + top: auto; + width: 1px; + height: 1px; + overflow: hidden; + } + ` + } + + constructor() { + super() + this._locations = [] + } + + set locations(locations) { + this._locations = locations + this.requestUpdate() + } + + get locations() { + return this._locations + } + + firstUpdated() { + this.$$location = this.shadowRoot.querySelector('#location') + this.$$location.addEventListener('change', (event) => { + const value = this.$$location[this.$$location.selectedIndex].value + this.dispatchEvent( + new CustomEvent('change', { detail: { value: value } }) + ) + }) + } + + render() { + return html` +
+ + +
+ ` + } +} diff --git a/client/src/ui/Scrub.js b/client/src/ui/Scrub.js new file mode 100644 index 0000000..2dd6889 --- /dev/null +++ b/client/src/ui/Scrub.js @@ -0,0 +1,271 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Globals, Config } from '../globals' +import { distance, clamp } from '../util/Math' + +const easing = 0.075 + +export class Scrub { + constructor($$element) { + this.$$canvas = $$element + this.$$canvas.addEventListener('mousedown', this.mouseDown.bind(this)) + this.$$canvas.addEventListener('mouseup', this.mouseUp.bind(this)) + this.$$canvas.addEventListener('mousemove', this.mouseMove.bind(this)) + this.$$canvas.addEventListener('touchstart', this.touchStart.bind(this)) + this.$$canvas.addEventListener('touchend', this.touchEnd.bind(this)) + this.$$canvas.addEventListener('touchmove', this.touchMove.bind(this)) + this.$$canvas.addEventListener('dblclick', this.dblclick.bind(this)) + + this.$$canvas.addEventListener('wheel', (event) => { + Globals.events.emit('wheel') + if (this.touchStarted) return + event.preventDefault() + if (this.pinching) return + + if (event.deltaY) { + this.mouseWheelVertical(event) + } + if (event.deltaX) { + this.mouseWheelHorizontal(event) + } + }) + + // This makes safari work.... + window.addEventListener('wheel', () => { + return true + }) + + this.zoomAmt = this.prevZoomAmt = 0 + this.scrubAmt = this.prevScrubAmt = 0 + this.minScrubDuration = 1 + this.minZoomDuration = 2 + this.handlingPinchMomentum = false + this.handlingScrubMomentum = false + } + + touchStart(event) { + this.touchStarted = true + if (this.pinching || this.handlingPinchMomentum) { + this.pinchStop() + } + if (this.moving || this.handlingScrubMomentum) { + this.dragStop() + } + if (event.touches && event.touches.length === 1) { + this.dragStart(event.touches[0].clientX) + } else if (event.touches && event.touches.length > 1) { + this.pinching = true + this.initialDistance = distance( + event.touches[0].clientX, + event.touches[0].clientY, + event.touches[1].clientX, + event.touches[1].clientY + ) + Globals.events.emit('pinchStart') + } + } + + touchEnd(event) { + this.touchStarted = false + if (this.pinching) { + this.zoomMomentum = this.zoomAmt - this.prevZoomAmt + if (Math.abs(this.zoomMomentum) <= this.minZoomDuration) { + this.pinchStop() + } + } + if (this.moving) { + this.scrubMomentum = this.scrubAmt - this.prevScrubAmt + if (this.scrubMomentum === 0) { + this.dragStop() + } + } + } + + touchMove(event) { + if (this.pinching && event.touches && event.touches[0] && event.touches[1]) { + const touchDistance = distance( + event.touches[0].clientX, + event.touches[0].clientY, + event.touches[1].clientX, + event.touches[1].clientY + ) + const differentDistance = touchDistance - this.initialDistance + + this.prevZoomAmt = this.zoomAmt + this.zoomAmt = differentDistance + if (!this.initializePinching) { + this.prevZoomAmt = this.zoomAmt + this.initializePinching = true + } + if (!Number.isNaN(differentDistance)) { + this.doPinch(differentDistance) + } + return + } + if (this.moving && event.touches && event.touches[0] && this.startPos) { + this.scrub(event.touches[0].clientX, this.startPos) + } + } + + dblclick(event) { + if (Globals.spectrogram.mouseHover) { + const duration = clamp(Globals.controls.duration * 0.5, Config.minDuration, Config.maxDuration) + Globals.controls.tweens.linearDuration + .stop() + .to({ duration }, 200) + .start() + + this.dbclickHappened = true + } + } + + dragStart(x) { + this.startPos = x + this.dragStartTime = Globals.player.time + this.moving = true + Globals.events.emit('dragStart') + } + + dragStop() { + this.moving = false + this.handlingScrubMomentum = false + this.initializeScrubbing = false + this.scrubMomentum = 0 + Globals.events.emit('dragStop') + } + + doPinch(distance) { + Globals.events.emit('pinch', { + detail: { + distance, + }, + }) + } + + pinchStop() { + this.pinching = false + this.handlingPinchMomentum = false + this.initializePinching = false + this.zoomMomentum = 0 + } + + mouseDown(event) { + this.scrubbing = false + Globals.spectrogram.container.cursor = 'grab' + this.dragStart(event.clientX) + } + + mouseUp(event) { + this.dbclickHappened = false + + if (!this.scrubbing && Globals.spectrogram.mouseHover) { + setTimeout(() => { + if (!this.dbclickHappened) { + const time = Globals.timeManager.pxToTime(event.clientX) + Globals.controls.tweens.position + .stop() + .to({ position: time - Globals.currentLocation.startTime }, 200) + .start() + } + }, 1) + } + + Globals.spectrogram.container.cursor = 'pointer' + this.dragStop() + } + + mouseMove(event) { + if (this.moving && !this.touchStarted) { + this.scrubbing = true + this.scrub(event.clientX, this.startPos) + } + } + + scrub(end, start) { + const distance = -(end - start) + + const curTime = Globals.timeManager.currentTime + const nextTime = Globals.timeManager.pxToTime(window.innerWidth / 2 + distance) + let duration = nextTime - curTime + + // for handling touch momentum + if (this.touchStarted) { + this.prevScrubAmt = this.scrubAmt + this.scrubAmt = distance + if (!this.initializeScrubbing) { + this.prevScrubAmt = this.scrubAmt + this.initializeScrubbing = true + } + } + + Globals.events.emit('move', { + detail: { distance: duration }, + }) + + this.startPos = end + } + + mouseWheelHorizontal(event) { + const distance = event.deltaX + + const curTime = Globals.timeManager.currentTime + const nextTime = Globals.timeManager.pxToTime(window.innerWidth / 2 + distance) + let duration = nextTime - curTime + + Globals.events.emit('move', { + detail: { distance: duration }, + }) + } + + mouseWheelVertical(event) { + Globals.events.emit('verticalScroll', { + detail: { deltaY: event.deltaY }, + }) + } + + handleMomentum() { + if (this.touchStarted) { + if (this.pinching) { + this.prevZoomAmt = this.zoomAmt + } + if (this.moving) { + this.prevScrubAmt = this.scrubAmt + } + } else { + if (this.zoomMomentum) { + this.handlingPinchMomentum = true + this.zoomAmt += this.zoomMomentum + this.doPinch(this.zoomAmt) + this.zoomMomentum = + Math.abs(this.zoomMomentum) < this.minZoomDuration + ? 0 + : this.zoomMomentum - this.zoomMomentum * easing + } else { + if (this.pinching && this.handlingPinchMomentum) { + this.pinchStop() + } + } + if (this.scrubMomentum) { + this.handlingScrubMomentum = true + this.scrubAmt -= this.scrubAmt * easing + if (Math.abs(this.scrubAmt) < this.minScrubDuration) { + this.dragStop() + } else { + this.scrub(this.startPos - this.scrubAmt, this.startPos) + } + } + } + } +} diff --git a/client/src/ui/SettingsModal.js b/client/src/ui/SettingsModal.js new file mode 100644 index 0000000..1a12cf8 --- /dev/null +++ b/client/src/ui/SettingsModal.js @@ -0,0 +1,565 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' +import { SimilarityLayerConfig } from '../components/SimilarityLayer' +import { SpectrogramTileDefitionsConfig } from '../models/SpectrogramTileDefinitions' +import { setNoiseCancelling } from '../components/AudioNoiseCancelling' +import { FrequenciesConfig } from '../components/Frequencies' +import { setGain } from '../components/AudioNoiseCancelling' +export class SettingsModal extends LitElement { + static get properties() { + return { + open: { type: Boolean, reflect: true, attribute: true }, + } + } + + constructor() { + super() + this.open = false + this._open = false + this._volume = 0.3 + this._useSimilarityHighlighting = true + this._useVisualNoiseReduction = true + this._showFrequencies = false + } + + set open(open) { + if (this.open && !open) { + this.shadowRoot.querySelector('.settings-section-container').classList.add('closing') + setTimeout(() => { + this._open = false + this.requestUpdate() + }, 150) + } else { + this._open = open + this.requestUpdate() + gtag('event', 'click_settings') + } + } + + get open() { + return this._open + } + + firstUpdated() { + document.addEventListener('click', (event) => { + this.bgClickhanlder(event) + }) + document.addEventListener('touchend', (event) => { + this.bgClickhanlder(event) + }) + } + + bgClickhanlder(event) { + let isSettingsButton = false + const path = event.path || (event.composedPath && event.composedPath()) + if (path) { + path.forEach((item) => { + if (item && item.classList && item.classList.contains('settings-wrap')) { + isSettingsButton = true + } + }) + } + if (!isSettingsButton && this.open && path && path[0]) { + if (!this.shadowRoot.contains(path[0])) { + this.open = false + } + } + } + + useSimilarityHighlighting(event) { + SimilarityLayerConfig.alpha = event.currentTarget.checked ? 0.5 : 0 + this._useSimilarityHighlighting = event.currentTarget.checked + } + + showFrequency(event) { + FrequenciesConfig.showFrequencies = event.currentTarget.checked + this._showFrequencies = event.currentTarget.checked + } + + useAudioNoiseReduction(event) { + setNoiseCancelling(!event.currentTarget.checked) + } + + useVisualNoiseReduction(event) { + SpectrogramTileDefitionsConfig.denoise = event.currentTarget.checked + this._useVisualNoiseReduction = event.currentTarget.checked + } + + setVolume(event) { + const value = event.currentTarget.value + this._volume = value + setGain(value) + } + + render() { + return this.open + ? html` +
+ +
Settings
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ ` + : html`` + } + + static get styles() { + return css` + :host { + position: relative; + } + + @keyframes appear { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0px); + } + } + + .settings-section-container { + box-sizing: border-box; + animation: appear 0.5s ease; + color: #fff; + background: #000; + border: 1px solid #fff; + position: absolute; + bottom: 70px; + left: 0; + padding: 30px; + width: 33vw; + max-width: 400px; + z-index: 10; + opacity: 1; + } + + .settings-section-container.closing { + opacity: 0; + transition: opacity 0.15s ease-out; + } + + .settings-close { + display: none; + } + + .settings-title { + font-weight: 500; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 14px; + margin-bottom: 20px; + } + + .settings-section-title, + .settings-close-text { + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 14px; + } + + .settings-section-title { + margin-bottom: 20px; + } + + .settings-section { + border-top: 1px solid #4c4c4c; + } + + .settings-section:last-of-type { + border-bottom: 1px solid #4c4c4c; + } + + .settings-section .container:not(:first-of-type) { + margin-left: 20px; + } + + .settings-section .radio-wrap .container:not(:first-of-type) { + margin-left: 0; + } + + .settings-section-radio-title { + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 14px; + position: relative; + top: 2px; + } + + .settings-section--radio { + display: flex; + border-top: none; + flex-direction: row; + align-items: center; + padding: 24px 0; + justify-content: space-between; + } + + .settings-section-input { + margin-right: 10px; + } + + /* styled fields */ + .container { + display: inline-block; + position: relative; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + width: 100%; + display: flex; + justify-content: space-between; + padding: 20px 0; + } + + /* Hide the browser's default radio button */ + .container input { + position: absolute; + opacity: 0; + cursor: pointer; + } + + /* Create a custom radio button */ + .checkmark { + position: absolute; + top: 0; + left: 0; + height: 21px; + width: 21px; + background-color: #000; + border-radius: 50%; + border: 1px solid #fff; + } + + .container:hover input ~ .checkmark { + background-color: rgba(187, 255, 255, 0.4); + } + + .container:hover input ~ .checkmark.off { + background-color: rgba(151, 151, 151, 0.4); + } + + .container input:checked ~ .checkmark:before { + content: ' '; + position: absolute; + z-index: 1; + top: 5px; + left: 5px; + right: 5px; + bottom: 5px; + background: #bbffff; + border-radius: 50%; + } + + .checkmark:after { + content: ''; + position: absolute; + display: none; + } + + .container input:checked ~ .checkmark:after { + display: block; + } + + .switch { + position: relative; + display: inline-block; + width: 46px; + height: 24px; + margin-left: 20px; + } + + .switch input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + border: 1px solid #fff; + background-color: #000; + -webkit-transition: 0.2s; + transition: 0.2s; + } + + .slider:before { + position: absolute; + content: ''; + height: 18px; + width: 18px; + left: 3px; + bottom: 2px; + background-color: #333333; + -webkit-transition: 0.2s; + transition: 0.2s; + } + + input:checked + .slider { + background-color: #000; + } + + input:focus + .slider { + box-shadow: 0 0 1px #2196f3; + } + + input:checked + .slider:before { + -webkit-transform: translateX(20px); + -ms-transform: translateX(20px); + transform: translateX(20px); + background-color: #bbffff; + } + + /* Rounded sliders */ + .slider.round { + border-radius: 34px; + } + + .slider.round:before { + border-radius: 50%; + } + + @media only screen and (max-width: 1100px) { + .settings-section-container { + width: 60vw; + } + } + + @media only screen and (max-width: 768px) { + .settings-section-container { + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100vw; + z-index: 54; + max-width: initial; + padding: 15% 0; + border: none; + } + + .settings-title { + margin-left: 20px; + font-size: 14px; + } + + .settings-close-text { + font-size: 14px; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-weight: 700; + } + + .settings-close { + position: absolute; + padding: 20px; + top: 0; + right: 0; + display: flex; + align-items: center; + background: none; + border: none; + cursor: pointer; + } + + .settings-close-text { + margin-right: 10px; + color: #fff; + } + + .settings-close-icon { + position: relative; + height: 11px; + width: 11px; + top: 2px; + } + + .settings-close-icon span { + width: 11px; + height: 1px; + position: absolute; + top: 5px; + right: 0; + background: #fff; + } + + .settings-close-icon span:first-of-type { + transform: rotate(45deg); + } + + .settings-close-icon span:last-of-type { + transform: rotate(-45deg); + } + + .settings-section { + padding: 0 20px; + } + + input:checked + .slider:before { + -webkit-transform: translateX(17px); + -ms-transform: translateX(17px); + transform: translateX(17px); + } + + .settings-section-radio-title { + top: 3px; + } + } + + .switch { + max-width: 46px; + } + + .range-ui-container { + display: flex; + } + + .range-label { + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 14px; + width: 30%; + padding-right: 20px; + } + + #volume { + width: 70%; + } + + input[type='range'] { + -webkit-appearance: none; + height: 1px; + margin-top: 31px; + background: rgba(255, 255, 255, 0.6); + outline: none; + -webkit-transition: 0.2s; + transition: opacity 0.2s; + } + + input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 19px; + height: 19px; + cursor: pointer; + background: #bbffff; + border-radius: 12.5px; + } + + input[type='range']::-moz-range-thumb { + width: 19px; + height: 19px; + cursor: pointer; + background: #bbffff; + border-radius: 12.5px; + } + + @media only screen and (max-width: 340px) { + input:checked + .slider:before { + -webkit-transform: translateX(10px); + -ms-transform: translateX(10px); + transform: translateX(10px); + } + } + ` + } +} +customElements.define('settings-modal', SettingsModal) diff --git a/client/src/ui/ShareModal.js b/client/src/ui/ShareModal.js new file mode 100644 index 0000000..990e411 --- /dev/null +++ b/client/src/ui/ShareModal.js @@ -0,0 +1,575 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' +import { dateToUTCDateTimeString, msToDate } from '../util/Date' + +export class ShareModal extends LitElement { + static get properties() { + return { + url: { type: String }, + timestamp: { type: String }, + linkWithTime: { type: Boolean }, + open: { type: Boolean }, + } + } + + static get styles() { + return css` + :host { + position: relative; + } + + @keyframes appear { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + #container { + animation: appear 0.5s ease; + color: #fff; + background: #000; + border: 1px solid #fff; + position: absolute; + bottom: 70px; + left: 80px; + padding: 33px; + width: 470px; + z-index: 10; + opacity: 1; + } + + #container.closing { + opacity: 0; + transition: opacity 0.15s ease-out; + } + + .share-content { + display: flex; + flex-direction: column; + margin-top: 24px; + } + + .share-close { + display: none; + } + + .share-title { + font-weight: 500; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-size: 14px; + } + + .link-wrap { + position: relative; + } + + .link-input { + width: 78%; + border: none; + background: none; + color: #fff; + letter-spacing: 1.79px; + font-size: 15px; + font-family: 'Roboto'; + padding: 10px; + border: 1px solid #fff; + } + + #copy-button { + position: absolute; + top: 0; + right: 0; + color: #43c1f9; + background: none; + border: none; + line-height: 1px; + font-size: 14px; + font-family: 'Roboto Mono', monospace; + padding: 20px; + right: -25px; + min-width: 100px; + text-align: center; + } + + .timestamp-wrap { + position: relative; + margin-top: 20px; + font-size: 14px; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-weight: 400; + } + + .timestamp-time { + opacity: 0.7; + } + + .social { + display: flex; + } + + .post-twitter, + .post-facebook { + border: none; + background: none; + height: 15px; + padding-left: 144px; + left: 0px; + color: #43c1f9; + position: relative; + left: 0; + margin-top: 15px; + cursor: pointer; + } + + .post-facebook { + margin-left: 30px; + width: 165px; + } + + .post-twitter svg, + .post-facebook svg { + height: 15px; + width: 15px; + display: inline-block; + fill: #43c1f9; + position: absolute; + left: 0; + top: 0; + } + + .post-twitter-text, + .post-facebook-text { + position: absolute; + left: 20px; + top: -3px; + font-size: 15px; + font-family: 'Roboto'; + line-height: 1.55; + letter-spacing: 1.5px; + width: 125px; + } + + .post-facebook-text { + width: 141px; + } + + .container { + display: inline-block; + position: relative; + padding-left: 30px; + margin-bottom: 12px; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + /* Hide the browser's default radio button */ + .container input { + position: absolute; + opacity: 0; + cursor: pointer; + } + + .checkmark { + position: absolute; + top: 0; + left: 0; + height: 15px; + width: 15px; + border: 1px solid #fff; + } + + .container:hover input ~ .checkmark { + background-color: rgba(187, 255, 255, 0.4); + } + + .container:hover input ~ .checkmark.off { + background-color: rgba(151, 151, 151, 0.4); + } + + .container input:checked ~ .checkmark { + background: #bbffff; + border: 1px solid #bbffff; + } + + .container input:checked ~ .checkmark:before { + content: ' '; + position: absolute; + z-index: 1; + top: 7px; + left: 0; + width: 15px; + height: 1px; + background: #000; + transform: rotate(45deg); + transform-origin: center; + } + + .container input:checked ~ .checkmark:after { + content: ' '; + position: absolute; + z-index: 1; + top: 7px; + left: 0; + width: 15px; + height: 1px; + background: #000; + transform: rotate(-45deg); + transform-origin: center; + } + + .container input:checked ~ .checkmark:after { + display: block; + } + + @media only screen and (max-width: 1160px) { + .social { + flex-direction: column; + } + + .post-facebook { + margin-left: 0; + margin-top: 20px; + } + + #copy-button { + padding: 20px 10px; + } + } + + @media only screen and (max-width: 768px) { + #container { + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100vw; + z-index: 54; + max-width: initial; + padding: 15% 0; + border: none; + } + + .share-close { + position: absolute; + padding: 20px; + top: 0; + right: 0; + display: flex; + align-items: center; + background: none; + border: none; + cursor: pointer; + } + + .share-close-text { + font-size: 14px; + font-family: 'Roboto Mono', monospace; + letter-spacing: 1px; + font-weight: 700; + } + + .share-close-text { + margin-right: 10px; + color: #fff; + } + + .share-close-icon { + position: relative; + height: 11px; + width: 11px; + top: 2px; + } + + .share-close-icon span { + width: 11px; + height: 1px; + position: absolute; + top: 5px; + right: 0; + background: #fff; + } + + .share-close-icon span:first-of-type { + transform: rotate(45deg); + } + + .share-close-icon span:last-of-type { + transform: rotate(-45deg); + } + + .share-title { + margin: 0 18px; + } + + .link-input { + width: 65%; + } + + .share-content { + margin: 24px 18px 0; + } + + .share-section { + padding: 20px; + } + } + ` + } + + constructor() { + super() + this.linkWithTime = true + this.extraParams + } + + set open(open) { + if (this.open && !open) { + this.shadowRoot.querySelector('#container').classList.add('closing') + setTimeout(() => { + this._open = false + this.requestUpdate() + }, 150) + } else { + this._open = open + this.requestUpdate() + gtag('event', 'click_share') + } + } + + get open() { + return this._open + } + + handleTimestamp() { + if (this.linkWithTime) { + this.extraParams = '#' + this.url.split('#')[1] + this.url = this.url.split('#')[0] + } else { + this.url = this.url + this.extraParams + } + this.linkWithTime = !this.linkWithTime + } + + isOs() { + return navigator.userAgent.match(/ipad|iphone/i) + } + + copy() { + this.$$urlField = this.shadowRoot.querySelector('#url') + this.$$copyButton = this.shadowRoot.querySelector('#copy-button') + this.$$urlField.select() + if (this.isOs()) { + this.$$urlField.contentEditable = true + let range = document.createRange() + range.selectNodeContents(this.$$urlField) + let selection = window.getSelection() + selection.removeAllRanges() + selection.addRange(range) + this.$$urlField.setSelectionRange(0, 999999) + document.execCommand('copy') + } else { + document.execCommand('copy') + } + this.$$copyButton.innerHTML = 'Copied!' + setTimeout(() => { + this.$$copyButton.innerHTML = 'Copy' + }, 700) + } + + twitterShare() { + this.share('twitter') + } + + facebookShare() { + this.share('facebook') + } + + firstUpdated() { + document.addEventListener('click', (event) => { + this.bgClickHandler(event) + }) + document.addEventListener('touchend', (event) => { + this.bgClickHandler(event) + }) + } + + bgClickHandler(event) { + let isSettingsButton = false + const path = event.path || (event.composedPath && event.composedPath()) + path.forEach((item) => { + if (item && item.classList && item.classList.contains('share-wrap')) { + isSettingsButton = true + } + }) + if (!isSettingsButton && this.open && path && path[0]) { + if (!this.shadowRoot.contains(path[0])) { + this.open = false + } + } + } + + share(platform) { + const text = this.linkWithTime + ? encodeURIComponent( + `Listen to this underwater sound I found from ` + + msToDate(this.timestamp) + + ` ➝ ` + + this.url + + ` 🐳 ` + + `#patternradio #whalesongs #HumpbackWhales #ocean` + ) + : `Explore thousands of hours of humpback whale songs and make your own discoveries ` + + ` ➝ ` + + this.url + + ` #patternradio #whalesongs #HumpbackWhales #ocean` + if (platform === 'twitter') { + const twitURL = `https://twitter.com/intent/tweet?text=${text}` + this.popup(twitURL, 253, 600) + } else if (platform === 'facebook') { + const fbURL = `https://www.facebook.com/sharer.php?u=${encodeURIComponent(this.url)}` + this.popup(fbURL, 570, 520) + } + } + + popup(url, height, width) { + const wLeft = window.screenLeft ? window.screenLeft : window.screenX + const wTop = window.screenTop ? window.screenTop : window.screenY + const left = wLeft + window.innerWidth / 2 - width / 2 + const top = wTop + window.innerHeight / 2 - height / 2 + window + .open( + url, + '_blank', + 'location=yes,height=' + + height + + ',width=' + + width + + ',top=' + + top + + ',left=' + + left + + ',scrollbars=yes,status=no,toolbar=no,menubar=no,location=no' + ) + .focus() + return false + } + + render() { + return this.open + ? html` +
+ +
Share
+
+ +
+ ${html` + + `} +
+ +
+
+ ` + : html`` + } +} +customElements.define('share-modal', ShareModal) diff --git a/client/src/ui/TimeSlider.js b/client/src/ui/TimeSlider.js new file mode 100644 index 0000000..21155ea --- /dev/null +++ b/client/src/ui/TimeSlider.js @@ -0,0 +1,150 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' +import { clamp, scaleToRange } from '../util/Math' +import * as d3TimeFormat from 'd3-time-format' + +const timeFormat = d3TimeFormat.utcFormat('%b %d %Y %H:%M') +export class TimeSlider extends LitElement { + static get properties() { + return { + value: { type: Number }, + min: { type: Number }, + scale: { type: Number }, + max: { type: Number }, + date: { type: Number }, + } + } + + static get styles() { + return css` + :host { + display: block; + width: 100%; + } + + #position-container { + margin-top: 8px; + margin: 0 26px; + position: absolute; + width: calc(100% - 26px - 26px); + /* box-sizing: border-box; */ + } + + #position { + -webkit-appearance: none; + height: 1px; + background: white; + outline: none; + width: 100%; + margin: 0; + cursor: pointer; + } + + #position::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + min-width: 40px; + height: 10px; + background: white; + } + #position::-moz-range-thumb { + min-width: 40px; + height: 10px; + background: white; + border-radius: 0; + } + + #position::-moz-focus-outer { + border: 0; + } + + #position-tooltip { + position: absolute; + width: 200px; + height: 30px; + /* background-color: red; */ + margin-left: -100px; + text-align: center; + color: white; + padding-top: 4px; + font-size: 0.7em; + opacity: 0; + transition: opacity 0.25s ease-in-out; + } + + #position-container:hover #position-tooltip { + opacity: 1; + } + ` + } + + constructor() { + super() + } + + update(changedProperties) { + super.update(changedProperties) + for (const key of changedProperties.keys()) { + switch (key) { + case 'value': + if (this.$$position) { + this.$$position.value = this.value + } + break + } + } + + if (this.$$tooltip) { + let pct = (this.value - this.min) / (this.max - this.min) + + let thumbWidth = this.$$position.offsetWidth * this.scale * 10 + thumbWidth = clamp(thumbWidth, 20, thumbWidth) + thumbWidth = thumbWidth / this.$$position.offsetWidth + pct = scaleToRange(pct, 0, 1, thumbWidth / 2, 1 - thumbWidth / 2) + this.$$tooltip.style.left = `${100 * pct}%` + + const date = new Date(this.date) + this.$$tooltip.innerText = timeFormat(date) + } + } + + firstUpdated() { + this.$$tooltip = this.shadowRoot.getElementById('position-tooltip') + this.$$position = this.shadowRoot.querySelector('#position') + this.$$position.addEventListener('input', (event) => { + this.value = this.$$position.value + this.dispatchEvent(new CustomEvent('input', event)) + }) + } + + render() { + return html` + +
+ +
+
+ ` + } +} +customElements.define('time-slider-element', TimeSlider) diff --git a/client/src/ui/Tutorial.js b/client/src/ui/Tutorial.js new file mode 100644 index 0000000..5e31bc9 --- /dev/null +++ b/client/src/ui/Tutorial.js @@ -0,0 +1,421 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { LitElement, html, css } from 'lit-element' + +export class Tutorial extends LitElement { + static get properties() { + return { + show: { type: Boolean }, + fadingOut: { type: Boolean }, + } + } + + constructor() { + super() + this.hasShown = false + } + + set show(show) { + this.fadingOut = false + clearTimeout(this.fadeTimeout) + if (this._show && !show) { + this.fadingOut = true + this.fadeTimeout = setTimeout(() => { + this._show = false + this.fadingOut = false + this.requestUpdate() + }, 500) + gtag('event', 'show_help') + } else { + this._show = show + this.hasShown = true + this.requestUpdate() + } + } + + get show() { + return this._show + } + + render() { + return html` + ${this.show + ? html` +
{ + this.show = false + }} + class="container ${this.fadingOut ? 'fadeout' : ''}" + > +
+ This spectrogram visualizes the underwater recordings. +
+
+
+ This heat map uses AI to help you navigate the data. + Brighter spots indicate where the algorithm is more confident there are whale songs. +
+
+
+ Share a link directly to moments you find interesting. +
+
+
+ If you have a trackpad, scroll vertically to zoom in and out, and horizontally to move + left/right. Or, use the scroll bar and + +/- buttons. +
+
+
+ Take a tour of moments whale researchers find interesting. +
+
+
+ ` + : ''} + ` + } + + static get styles() { + return css` + @keyframes appear { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + .tip { + animation-name: appear; + animation-duration: 0.5s; + animation-timing-function: ease; + animation-fill-mode: forwards; + } + + .tip.tour { + animation-delay: 0.2s; + } + + .tip.spectrogram { + animation-delay: 0.4s; + } + + .tip.heat { + animation-delay: 0.6s; + } + + .tip.share { + animation-delay: 0.8s; + } + + .tip.scroll { + animation-delay: 1s; + } + + .tip:nth-child(6) { + animation-delay: 0s; + } + + .container.fadeout { + pointer-events: none; + opacity: 0; + } + + .container { + position: fixed; + top: 0; + left: 0; + opacity: 1; + transition: opacity 0.25s ease-out; + height: 100%; + width: 100%; + color: #fff; + cursor: pointer; + box-sizing: border-box; + } + + .tip { + padding: 12px 16px; + color: #000; + font-size: 14px; + font-family: 'Roboto'; + letter-spacing: 1.55px; + line-height: 1.714; + position: absolute; + width: 100%; + opacity: 0; + background: #bbffff; + text-align: center; + border-radius: 4px; + } + + .highlight { + font-weight: 700; + } + + .bar { + position: absolute; + background: #bbffff; + height: 1px; + width: 70px; + z-index: -1; + } + + .spectrogram { + left: 50vw; + top: 35vh; + transform: translate(-50%, -50%); + max-width: 277px; + } + + .spectrogram .bar { + position: absolute; + left: -60px; + display: block; + top: 50%; + } + + .heat { + max-width: 427px; + top: 50%; + transform: translateY(-50%); + right: 20vw; + margin-top: 290px; + } + + .heat .bar { + position: absolute; + display: block; + width: 1px; + height: 70px; + top: -50px; + right: 50%; + } + + .share { + left: 30px; + bottom: 170px; + max-width: 282px; + } + + .share .bar { + position: absolute; + bottom: -72px; + display: block; + height: 70px; + width: 1px; + left: 120px; + } + + .scroll { + transform: translate(-50%, -50%); + bottom: 40px; + max-width: 351px; + right: -140px; + } + + .scroll .bar { + display: none; + } + + .tour { + right: unset; + left: 50vw; + transform: translateX(65%); + top: 24px; + max-width: 309px; + } + + .tour .bar { + position: absolute; + left: -53px; + top: 50%; + display: block; + } + + .guide { + box-shadow: none; + background: black; + bottom: 0; + left: 50%; + transform: translateX(-50%); + height: 99px; + width: 30%; + display: flex; + flex-direction: column; + justify-content: center; + color: #fff; + z-index: -1; + } + + .title { + font-size: 21px; + font-family: 'Roboto'; + font-weight: 500; + line-height: 1.1; + margin-bottom: 7px; + } + + .desc { + font-size: 14px; + font-family: 'Roboto'; + letter-spacing: 1.55px; + line-height: 1.714; + } + + @media only screen and (max-height: 1070px) { + .heat { + margin-top: 90px; + } + + .heat .bar { + top: unset; + bottom: -50px; + right: 50%; + } + } + + @media only screen and (max-height: 949px) { + .heat { + margin-top: 90px; + } + } + + @media only screen and (max-height: 800px) { + .heat { + right: 24vw; + margin-top: -2px; + } + + .spectrogram { + left: 35vw; + top: 36vh; + } + } + + @media only screen and (max-height: 768px) { + .heat { + top: 56%; + } + } + + @media only screen and (max-height: 667px) { + .heat { + top: 50vh; + right: 10vw; + } + + .heat .bar { + bottom: -25px; + } + } + + @media only screen and (max-height: 618px) { + .guide { + display: none; + } + } + + @media only screen and (max-width: 1165px) { + .tour { + max-width: 250px; + } + + .tour .bar { + left: -33px; + } + } + + @media only screen and (max-width: 958px) { + .tour { + max-width: 200px; + left: 48vw; + top: 38px; + } + } + + @media only screen and (max-width: 768px) { + .tour, + .scroll, + .share, + .guide { + display: none; + } + + .spectrogram { + max-width: unset; + box-sizing: border-box; + left: 20%; + width: 60%; + top: 38%; + transform: translateY(-100%); + margin-top: -90px; + } + + .spectrogram .bar { + top: unset; + left: 50%; + width: 1px; + bottom: -10vh; + height: 10vh; + } + + .heat { + max-width: unset; + box-sizing: border-box; + width: 80%; + left: 10%; + bottom: 37%; + transform: translateY(100%); + margin-bottom: -90px; + top: unset; + } + + .heat .bar { + transform: scale(-1, 1); + top: -10vh; + height: 10vh; + } + + .heat .bar, + .spectrogram .bar { + left: 20%; + } + + .title { + display: none; + } + } + + @media only screen and (max-width: 374px) { + .spectrogram { + margin-top: 0; + } + + .heat { + margin-bottom: 0; + } + + .heat .bar, + .spectrogram .bar { + display: none; + } + } + ` + } +} +customElements.define('tutorial-element', Tutorial) diff --git a/client/src/ui/filter/ColorMap.js b/client/src/ui/filter/ColorMap.js new file mode 100644 index 0000000..d8304d8 --- /dev/null +++ b/client/src/ui/filter/ColorMap.js @@ -0,0 +1,63 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Filter } from 'pixi.js' + +export class ColorMapFilter extends Filter { + constructor(options) { + const fragment = ` + uniform vec3 darkColor; + uniform vec3 lightColor; + varying vec2 vTextureCoord; + uniform sampler2D uSampler; + void main(){ + vec4 color = texture2D(uSampler, vTextureCoord.xy); + gl_FragColor = vec4(mix(darkColor, lightColor, color.r), 1.0); + } + ` + + const darkColor = [23/256, 36/256, 67/256] + const lightColor = [0, 240/256, 233/256] + + const uniformsData = { + darkColor: { + type: 'vec3', + value: darkColor, + }, + lightColor: { + type: 'vec3', + value: lightColor, + }, + } + super(null, fragment, uniformsData) + + this.uniformData = uniformsData + } + + set darkColor(c) { + this.uniforms.darkColor = c.map((x) => x / 256) + } + + get darkColor() { + return this.uniforms.darkColor.map((x) => x * 256) + } + + set lightColor(c) { + this.uniforms.lightColor = c.map((x) => x / 256) + } + + get lightColor() { + return this.uniforms.lightColor.map((x) => x * 256) + } +} diff --git a/client/src/ui/filter/Filter.js b/client/src/ui/filter/Filter.js new file mode 100644 index 0000000..f0bc9ed --- /dev/null +++ b/client/src/ui/filter/Filter.js @@ -0,0 +1,37 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { GUI } from '../Dat' +import { AdjustmentFilter } from '@pixi/filter-adjustment' +import { ColorMapFilter } from './ColorMap' + +export const adjustmentFilter = new AdjustmentFilter() +// some baseline props +adjustmentFilter.gamma = 0.9 +adjustmentFilter.brightness = 3.2 +adjustmentFilter.contrast = 1.5 +if (GUI) { + GUI.add(adjustmentFilter, 'brightness', 0, 10) + GUI.add(adjustmentFilter, 'contrast', 0, 2) + GUI.add(adjustmentFilter, 'gamma', 0, 1) +} + +export const colorMapFilter = new ColorMapFilter() + +if (GUI) { + GUI.addColor(colorMapFilter, 'darkColor') + GUI.addColor(colorMapFilter, 'lightColor') +} + +export const spectrogramFilters = [adjustmentFilter, colorMapFilter] diff --git a/client/src/ui/filter/SubtractBg.js b/client/src/ui/filter/SubtractBg.js new file mode 100644 index 0000000..baf0c68 --- /dev/null +++ b/client/src/ui/filter/SubtractBg.js @@ -0,0 +1,65 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { Filter } from 'pixi.js'; + +const vertex = ` + attribute vec2 aVertexPosition; + attribute vec2 aTextureCoord; + + uniform mat3 projectionMatrix; + + varying vec2 vTextureCoord; + + void main(void) + { + gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); + vTextureCoord = aTextureCoord; + }` + +const fragment = ` + varying vec2 vTextureCoord; + uniform sampler2D uSampler; + + uniform float min; + uniform float exponent; + + void main(void) + { + vec4 c = texture2D(uSampler, vTextureCoord); + + float max = 1.0; + vec4 scaled = (max - min) * (c - min); + + vec4 exp = vec4(exponent); + + gl_FragColor = clamp(pow(scaled, exp), 0.0, 1.0); + } +` + +export class SubtractBackgroundFilter extends Filter { + constructor(options) { + super(vertex, fragment); + + this.min = 0.1 + this.exponent = 1 + } + + apply(filterManager, input, output, clear) { + this.uniforms.min = this.min; + this.uniforms.exponent = this.exponent; + + filterManager.applyFilter(this, input, output, clear); + } +} \ No newline at end of file diff --git a/client/src/util/Date.js b/client/src/util/Date.js new file mode 100644 index 0000000..2347f45 --- /dev/null +++ b/client/src/util/Date.js @@ -0,0 +1,30 @@ +// Copyright 2019 Google Inc. +// +// 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. + +export function msToTime(ms) { + return new Date(ms).toUTCString() +} + +export function dateToUTCDateTimeString(date) { + return date.toUTCString().slice(5, date.toUTCString().length) +} + +export function msToDate(ms) { + const date = new Date(ms) + const monthTitles = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + const day = date.getUTCDate() + const month = monthTitles[date.getUTCMonth()] + const year = date.getUTCFullYear() + return `${month} ${day}, ${year}` +} diff --git a/client/src/util/LowpassFilter.js b/client/src/util/LowpassFilter.js new file mode 100644 index 0000000..d4baf22 --- /dev/null +++ b/client/src/util/LowpassFilter.js @@ -0,0 +1,31 @@ +// Copyright 2019 Google Inc. +// +// 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. + +export default function LowpassFilter(Fc, Q = 0.707) { + const K = Math.tan(Math.PI * Fc) + const norm = 1 / (1 + K / Q + K * K) + this.a0 = K * K * norm + this.a1 = 2 * this.a0 + this.a2 = this.a0 + this.b1 = 2 * (K * K - 1) * norm + this.b2 = (1 - K / Q + K * K) * norm + this.z1 = this.z2 = 0 + this.value = 0 + this.tick = function(value) { + const out = value * this.a0 + this.z1 + this.z1 = value * this.a1 + this.z2 - this.b1 * out + this.z2 = value * this.a2 - this.b2 * out + return out + } +} diff --git a/client/src/util/Math.js b/client/src/util/Math.js new file mode 100644 index 0000000..95c75b9 --- /dev/null +++ b/client/src/util/Math.js @@ -0,0 +1,105 @@ +// Copyright 2019 Google Inc. +// +// 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. + +export function scaleToRange(num, inMin, inMax, outMin, outMax) { + return ((num - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin +} + +export function logScale(position, resultMin, resultMax, minp = 0, maxp = 100) { + // The result should be between 100 an 10000000 + const minv = Math.log(resultMin) + const maxv = Math.log(resultMax) + + // calculate adjustment factor + const scale = (maxv - minv) / (maxp - minp) + + return Math.exp(minv + scale * (position - minp)) +} + +// resolve linearScale from a known duration +export function getLogPosition( + duration, + resultMin, + resultMax, + minp = 0, + maxp = 100 +) { + const minv = Math.log(resultMin) + const maxv = Math.log(resultMax) + + // calculate adjustment factor + const scale = (maxv - minv) / (maxp - minp) + + return (Math.log(duration) - minv) / scale + minp +} +export function distance(x1, y1, x2, y2) { + const a = x1 - x2 + const b = y1 - y2 + return Math.sqrt(a*a + b*b) +} + +export function clamp(val, min, max) { + if (val > max) return max + if (val < min) return min + return val +} + +export function interpolateColor(hex1, hex2, factor) { + const color1 = hexToRGB(hex1) + const color2 = hexToRGB(hex2) + const result = color1.slice() + for (let i = 0; i < 3; i++) { + result[i] = Math.round(result[i] + factor * (color2[i] - color1[i])) + } + return result +} + +export function interpolateMultiColor(colors) { + return (t) => { + t = clamp(t, 0, 1) + + const idx = (colors.length - 1) * t + const lIdx = Math.floor(idx) + const rIdx = Math.ceil(idx) + + t = idx - lIdx + + const lColor = colors[lIdx] + const rColor = colors[rIdx] + + const result = lColor.slice() + for (let i = 0; i < 3; i++) { + result[i] = Math.round(result[i] + t * (rColor[i] - lColor[i])) + } + return result + } +} + +export function hexToRGB(hex) { + const r = hex >> 16 + const g = (hex >> 8) & 0xff + const b = hex & 0xff + return [r, g, b] +} + +export function rgbToHex(r, g, b) { + const bin = (r << 16) | (g << 8) | b + return (function(h) { + return new Array(7 - h.length).join('0') + h + })(bin.toString(16).toUpperCase()) +} + +export function rgbToNum(r, g, b) { + return (r << 16) + (g << 8) + b +} diff --git a/client/src/util/StartAudio.js b/client/src/util/StartAudio.js new file mode 100644 index 0000000..3d006c0 --- /dev/null +++ b/client/src/util/StartAudio.js @@ -0,0 +1,42 @@ +// Copyright 2019 Google Inc. +// +// 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. + +const silentAudio = 'data:audio/mp3;base64,//MkxAAHiAICWABElBeKPL/RANb2w+yiT1g/gTok//lP/W/l3h8QO/OCdCqCW2Cw//MkxAQHkAIWUAhEmAQXWUOFW2dxPu//9mr60ElY5sseQ+xxesmHKtZr7bsqqX2L//MkxAgFwAYiQAhEAC2hq22d3///9FTV6tA36JdgBJoOGgc+7qvqej5Zu7/7uI9l//MkxBQHAAYi8AhEAO193vt9KGOq+6qcT7hhfN5FTInmwk8RkqKImTM55pRQHQSq//MkxBsGkgoIAABHhTACIJLf99nVI///yuW1uBqWfEu7CgNPWGpUadBmZ////4sL//MkxCMHMAH9iABEmAsKioqKigsLCwtVTEFNRTMuOTkuNVVVVVVVVVVVVVVVVVVV//MkxCkECAUYCAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV' // eslint-disable-line +import Tone from 'tone' + +/** + * Start hte audio context if it's not already + * must be called from a click event + */ +export async function start() { + if (Tone.context.state === 'suspended') { + const contextPromise = Tone.context.resume() + + // also play a silent audio file which unmutes iOS + const audioElement = document.createElement('audio') + audioElement.controls = false + audioElement.preload = 'auto' + audioElement.loop = false + audioElement.src = silentAudio + audioElement.title = 'Pattern Radio' + let elementPromise = Promise.resolve() + try { + elementPromise = await audioElement.play() + } catch (e) { + elementPromise = Promise.resolve() + console.log('did not start audio') + } + await Promise.all([elementPromise, contextPromise]) + } +} diff --git a/client/src/util/Throttle.js b/client/src/util/Throttle.js new file mode 100644 index 0000000..5e65a47 --- /dev/null +++ b/client/src/util/Throttle.js @@ -0,0 +1,40 @@ +// Copyright 2019 Google Inc. +// +// 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. + +export function throttle(callback, delay) { + let isThrottled = false + let args + let context + + // TODO: find the es6 way of doing this. + function wrapper() { + if (isThrottled) { + args = arguments // eslint-disable-line + context = this // eslint-disable-line + return + } + + isThrottled = true + callback.apply(this, arguments) // eslint-disable-line + + setTimeout(() => { + isThrottled = false + if (args) { + wrapper.apply(context, args) + args = context = null + } + }, delay) + } + return wrapper +} diff --git a/client/src/util/WindowFocus.js b/client/src/util/WindowFocus.js new file mode 100644 index 0000000..837edbd --- /dev/null +++ b/client/src/util/WindowFocus.js @@ -0,0 +1,45 @@ +// Copyright 2019 Google Inc. +// +// 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. + +import { EventEmitter } from 'events' + +export class WindowFocus extends EventEmitter { + constructor() { + super() + this.pageHasFocus = true + + this.timeoutFunction = () => { + clearTimeout(this.focusTimeout) + let newFocus = this.checkPageFocus() + if (this.pageHasFocus != newFocus) { + this.pageHasFocus = newFocus + this.emit('focus-change', newFocus) + } + this.focusTimeout = setTimeout(this.timeoutFunction, 500) + } + + this.focusTimeout = setTimeout(this.timeoutFunction, 500) + + window.addEventListener('blur', () => { + if (this.pageHasFocus == true) { + this.emit('focus-change', false) + this.pageHasFocus = false + } + }) + } + + checkPageFocus() { + return !document.hidden + } +} diff --git a/client/third_party/bundles/webcomponents-ce.js b/client/third_party/bundles/webcomponents-ce.js new file mode 100644 index 0000000..912be67 --- /dev/null +++ b/client/third_party/bundles/webcomponents-ce.js @@ -0,0 +1,56 @@ +/** +@license @nocompile +Copyright (c) 2018 The Polymer Project Authors. All rights reserved. +This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt +The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt +Code distributed by Google as part of the polymer project is also +subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt +*/ +(function(){'use strict';var aa=new Set("annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" "));function g(b){var a=aa.has(b);b=/^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(b);return!a&&b}function l(b){var a=b.isConnected;if(void 0!==a)return a;for(;b&&!(b.__CE_isImportDocument||b instanceof Document);)b=b.parentNode||(window.ShadowRoot&&b instanceof ShadowRoot?b.host:void 0);return!(!b||!(b.__CE_isImportDocument||b instanceof Document))} +function p(b,a){for(;a&&a!==b&&!a.nextSibling;)a=a.parentNode;return a&&a!==b?a.nextSibling:null} +function q(b,a,d){d=void 0===d?new Set:d;for(var c=b;c;){if(c.nodeType===Node.ELEMENT_NODE){var e=c;a(e);var f=e.localName;if("link"===f&&"import"===e.getAttribute("rel")){c=e.import;if(c instanceof Node&&!d.has(c))for(d.add(c),c=c.firstChild;c;c=c.nextSibling)q(c,a,d);c=p(b,e);continue}else if("template"===f){c=p(b,e);continue}if(e=e.__CE_shadowRoot)for(e=e.firstChild;e;e=e.nextSibling)q(e,a,d)}c=c.firstChild?c.firstChild:p(b,c)}}function t(b,a,d){b[a]=d};function u(){this.a=new Map;this.f=new Map;this.c=[];this.b=!1}function ba(b,a,d){b.a.set(a,d);b.f.set(d.constructor,d)}function v(b,a){b.b=!0;b.c.push(a)}function w(b,a){b.b&&q(a,function(a){return x(b,a)})}function x(b,a){if(b.b&&!a.__CE_patched){a.__CE_patched=!0;for(var d=0;d=} visitedImports\n */\nexport function walkDeepDescendantElements(root, callback, visitedImports = new Set()) {\n let node = root;\n while (node) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = /** @type {!Element} */(node);\n\n callback(element);\n\n const localName = element.localName;\n if (localName === 'link' && element.getAttribute('rel') === 'import') {\n // If this import (polyfilled or not) has it's root node available,\n // walk it.\n const importNode = /** @type {!Node} */ (element.import);\n if (importNode instanceof Node && !visitedImports.has(importNode)) {\n // Prevent multiple walks of the same import root.\n visitedImports.add(importNode);\n\n for (let child = importNode.firstChild; child; child = child.nextSibling) {\n walkDeepDescendantElements(child, callback, visitedImports);\n }\n }\n\n // Ignore descendants of import links to prevent attempting to walk the\n // elements created by the HTML Imports polyfill that we just walked\n // above.\n node = nextSiblingOrAncestorSibling(root, element);\n continue;\n } else if (localName === 'template') {\n // Ignore descendants of templates. There shouldn't be any descendants\n // because they will be moved into `.content` during construction in\n // browsers that support template but, in case they exist and are still\n // waiting to be moved by a polyfill, they will be ignored.\n node = nextSiblingOrAncestorSibling(root, element);\n continue;\n }\n\n // Walk shadow roots.\n const shadowRoot = element.__CE_shadowRoot;\n if (shadowRoot) {\n for (let child = shadowRoot.firstChild; child; child = child.nextSibling) {\n walkDeepDescendantElements(child, callback, visitedImports);\n }\n }\n }\n\n node = nextNode(root, node);\n }\n}\n\n/**\n * Used to suppress Closure's \"Modifying the prototype is only allowed if the\n * constructor is in the same scope\" warning without using\n * `@suppress {newCheckTypes, duplicate}` because `newCheckTypes` is too broad.\n *\n * @param {!Object} destination\n * @param {string} name\n * @param {*} value\n */\nexport function setPropertyUnchecked(destination, name, value) {\n destination[name] = value;\n}\n","import * as Utilities from './Utilities.js';\nimport CEState from './CustomElementState.js';\n\nexport default class CustomElementInternals {\n constructor() {\n /** @type {!Map} */\n this._localNameToDefinition = new Map();\n\n /** @type {!Map} */\n this._constructorToDefinition = new Map();\n\n /** @type {!Array} */\n this._patches = [];\n\n /** @type {boolean} */\n this._hasPatches = false;\n }\n\n /**\n * @param {string} localName\n * @param {!CustomElementDefinition} definition\n */\n setDefinition(localName, definition) {\n this._localNameToDefinition.set(localName, definition);\n this._constructorToDefinition.set(definition.constructor, definition);\n }\n\n /**\n * @param {string} localName\n * @return {!CustomElementDefinition|undefined}\n */\n localNameToDefinition(localName) {\n return this._localNameToDefinition.get(localName);\n }\n\n /**\n * @param {!Function} constructor\n * @return {!CustomElementDefinition|undefined}\n */\n constructorToDefinition(constructor) {\n return this._constructorToDefinition.get(constructor);\n }\n\n /**\n * @param {!function(!Node)} listener\n */\n addPatch(listener) {\n this._hasPatches = true;\n this._patches.push(listener);\n }\n\n /**\n * @param {!Node} node\n */\n patchTree(node) {\n if (!this._hasPatches) return;\n\n Utilities.walkDeepDescendantElements(node, element => this.patch(element));\n }\n\n /**\n * @param {!Node} node\n */\n patch(node) {\n if (!this._hasPatches) return;\n\n if (node.__CE_patched) return;\n node.__CE_patched = true;\n\n for (let i = 0; i < this._patches.length; i++) {\n this._patches[i](node);\n }\n }\n\n /**\n * @param {!Node} root\n */\n connectTree(root) {\n const elements = [];\n\n Utilities.walkDeepDescendantElements(root, element => elements.push(element));\n\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n if (element.__CE_state === CEState.custom) {\n this.connectedCallback(element);\n } else {\n this.upgradeElement(element);\n }\n }\n }\n\n /**\n * @param {!Node} root\n */\n disconnectTree(root) {\n const elements = [];\n\n Utilities.walkDeepDescendantElements(root, element => elements.push(element));\n\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n if (element.__CE_state === CEState.custom) {\n this.disconnectedCallback(element);\n }\n }\n }\n\n /**\n * Upgrades all uncustomized custom elements at and below a root node for\n * which there is a definition. When custom element reaction callbacks are\n * assumed to be called synchronously (which, by the current DOM / HTML spec\n * definitions, they are *not*), callbacks for both elements customized\n * synchronously by the parser and elements being upgraded occur in the same\n * relative order.\n *\n * NOTE: This function, when used to simulate the construction of a tree that\n * is already created but not customized (i.e. by the parser), does *not*\n * prevent the element from reading the 'final' (true) state of the tree. For\n * example, the element, during truly synchronous parsing / construction would\n * see that it contains no children as they have not yet been inserted.\n * However, this function does not modify the tree, the element will\n * (incorrectly) have children. Additionally, self-modification restrictions\n * for custom element constructors imposed by the DOM spec are *not* enforced.\n *\n *\n * The following nested list shows the steps extending down from the HTML\n * spec's parsing section that cause elements to be synchronously created and\n * upgraded:\n *\n * The \"in body\" insertion mode:\n * https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody\n * - Switch on token:\n * .. other cases ..\n * -> Any other start tag\n * - [Insert an HTML element](below) for the token.\n *\n * Insert an HTML element:\n * https://html.spec.whatwg.org/multipage/syntax.html#insert-an-html-element\n * - Insert a foreign element for the token in the HTML namespace:\n * https://html.spec.whatwg.org/multipage/syntax.html#insert-a-foreign-element\n * - Create an element for a token:\n * https://html.spec.whatwg.org/multipage/syntax.html#create-an-element-for-the-token\n * - Will execute script flag is true?\n * - (Element queue pushed to the custom element reactions stack.)\n * - Create an element:\n * https://dom.spec.whatwg.org/#concept-create-element\n * - Sync CE flag is true?\n * - Constructor called.\n * - Self-modification restrictions enforced.\n * - Sync CE flag is false?\n * - (Upgrade reaction enqueued.)\n * - Attributes appended to element.\n * (`attributeChangedCallback` reactions enqueued.)\n * - Will execute script flag is true?\n * - (Element queue popped from the custom element reactions stack.\n * Reactions in the popped stack are invoked.)\n * - (Element queue pushed to the custom element reactions stack.)\n * - Insert the element:\n * https://dom.spec.whatwg.org/#concept-node-insert\n * - Shadow-including descendants are connected. During parsing\n * construction, there are no shadow-*excluding* descendants.\n * However, the constructor may have validly attached a shadow\n * tree to itself and added descendants to that shadow tree.\n * (`connectedCallback` reactions enqueued.)\n * - (Element queue popped from the custom element reactions stack.\n * Reactions in the popped stack are invoked.)\n *\n * @param {!Node} root\n * @param {{\n * visitedImports: (!Set|undefined),\n * upgrade: (!function(!Element)|undefined),\n * }=} options\n */\n patchAndUpgradeTree(root, options = {}) {\n const visitedImports = options.visitedImports || new Set();\n const upgrade = options.upgrade || (element => this.upgradeElement(element));\n\n const elements = [];\n\n const gatherElements = element => {\n if (element.localName === 'link' && element.getAttribute('rel') === 'import') {\n // The HTML Imports polyfill sets a descendant element of the link to\n // the `import` property, specifically this is *not* a Document.\n const importNode = /** @type {?Node} */ (element.import);\n\n if (importNode instanceof Node) {\n importNode.__CE_isImportDocument = true;\n // Connected links are associated with the registry.\n importNode.__CE_hasRegistry = true;\n }\n\n if (importNode && importNode.readyState === 'complete') {\n importNode.__CE_documentLoadHandled = true;\n } else {\n // If this link's import root is not available, its contents can't be\n // walked. Wait for 'load' and walk it when it's ready.\n element.addEventListener('load', () => {\n const importNode = /** @type {!Node} */ (element.import);\n\n if (importNode.__CE_documentLoadHandled) return;\n importNode.__CE_documentLoadHandled = true;\n\n // Clone the `visitedImports` set that was populated sync during\n // the `patchAndUpgradeTree` call that caused this 'load' handler to\n // be added. Then, remove *this* link's import node so that we can\n // walk that import again, even if it was partially walked later\n // during the same `patchAndUpgradeTree` call.\n const clonedVisitedImports = new Set(visitedImports);\n clonedVisitedImports.delete(importNode);\n\n this.patchAndUpgradeTree(importNode, {visitedImports: clonedVisitedImports, upgrade});\n });\n }\n } else {\n elements.push(element);\n }\n };\n\n // `walkDeepDescendantElements` populates (and internally checks against)\n // `visitedImports` when traversing a loaded import.\n Utilities.walkDeepDescendantElements(root, gatherElements, visitedImports);\n\n if (this._hasPatches) {\n for (let i = 0; i < elements.length; i++) {\n this.patch(elements[i]);\n }\n }\n\n for (let i = 0; i < elements.length; i++) {\n upgrade(elements[i]);\n }\n }\n\n /**\n * @param {!Element} element\n */\n upgradeElement(element) {\n const currentState = element.__CE_state;\n if (currentState !== undefined) return;\n\n // Prevent elements created in documents without a browsing context from\n // upgrading.\n //\n // https://html.spec.whatwg.org/multipage/custom-elements.html#look-up-a-custom-element-definition\n // \"If document does not have a browsing context, return null.\"\n //\n // https://html.spec.whatwg.org/multipage/window-object.html#dom-document-defaultview\n // \"The defaultView IDL attribute of the Document interface, on getting,\n // must return this Document's browsing context's WindowProxy object, if\n // this Document has an associated browsing context, or null otherwise.\"\n const ownerDocument = element.ownerDocument;\n if (\n !ownerDocument.defaultView &&\n !(ownerDocument.__CE_isImportDocument && ownerDocument.__CE_hasRegistry)\n ) return;\n\n const definition = this.localNameToDefinition(element.localName);\n if (!definition) return;\n\n definition.constructionStack.push(element);\n\n const constructor = definition.constructor;\n try {\n try {\n let result = new (constructor)();\n if (result !== element) {\n throw new Error('The custom element constructor did not produce the element being upgraded.');\n }\n } finally {\n definition.constructionStack.pop();\n }\n } catch (e) {\n element.__CE_state = CEState.failed;\n throw e;\n }\n\n element.__CE_state = CEState.custom;\n element.__CE_definition = definition;\n\n if (definition.attributeChangedCallback) {\n const observedAttributes = definition.observedAttributes;\n for (let i = 0; i < observedAttributes.length; i++) {\n const name = observedAttributes[i];\n const value = element.getAttribute(name);\n if (value !== null) {\n this.attributeChangedCallback(element, name, null, value, null);\n }\n }\n }\n\n if (Utilities.isConnected(element)) {\n this.connectedCallback(element);\n }\n }\n\n /**\n * @param {!Element} element\n */\n connectedCallback(element) {\n const definition = element.__CE_definition;\n if (definition.connectedCallback) {\n definition.connectedCallback.call(element);\n }\n }\n\n /**\n * @param {!Element} element\n */\n disconnectedCallback(element) {\n const definition = element.__CE_definition;\n if (definition.disconnectedCallback) {\n definition.disconnectedCallback.call(element);\n }\n }\n\n /**\n * @param {!Element} element\n * @param {string} name\n * @param {?string} oldValue\n * @param {?string} newValue\n * @param {?string} namespace\n */\n attributeChangedCallback(element, name, oldValue, newValue, namespace) {\n const definition = element.__CE_definition;\n if (\n definition.attributeChangedCallback &&\n definition.observedAttributes.indexOf(name) > -1\n ) {\n definition.attributeChangedCallback.call(element, name, oldValue, newValue, namespace);\n }\n }\n}\n","/**\n * @enum {number}\n */\nconst CustomElementState = {\n custom: 1,\n failed: 2,\n};\n\nexport default CustomElementState;\n","import CustomElementInternals from './CustomElementInternals.js';\n\nexport default class DocumentConstructionObserver {\n constructor(internals, doc) {\n /**\n * @type {!CustomElementInternals}\n */\n this._internals = internals;\n\n /**\n * @type {!Document}\n */\n this._document = doc;\n\n /**\n * @type {MutationObserver|undefined}\n */\n this._observer = undefined;\n\n\n // Simulate tree construction for all currently accessible nodes in the\n // document.\n this._internals.patchAndUpgradeTree(this._document);\n\n if (this._document.readyState === 'loading') {\n this._observer = new MutationObserver(this._handleMutations.bind(this));\n\n // Nodes created by the parser are given to the observer *before* the next\n // task runs. Inline scripts are run in a new task. This means that the\n // observer will be able to handle the newly parsed nodes before the inline\n // script is run.\n this._observer.observe(this._document, {\n childList: true,\n subtree: true,\n });\n }\n }\n\n disconnect() {\n if (this._observer) {\n this._observer.disconnect();\n }\n }\n\n /**\n * @param {!Array} mutations\n */\n _handleMutations(mutations) {\n // Once the document's `readyState` is 'interactive' or 'complete', all new\n // nodes created within that document will be the result of script and\n // should be handled by patching.\n const readyState = this._document.readyState;\n if (readyState === 'interactive' || readyState === 'complete') {\n this.disconnect();\n }\n\n for (let i = 0; i < mutations.length; i++) {\n const addedNodes = mutations[i].addedNodes;\n for (let j = 0; j < addedNodes.length; j++) {\n const node = addedNodes[j];\n this._internals.patchAndUpgradeTree(node);\n }\n }\n }\n}\n","import CustomElementInternals from './CustomElementInternals.js';\nimport DocumentConstructionObserver from './DocumentConstructionObserver.js';\nimport Deferred from './Deferred.js';\nimport * as Utilities from './Utilities.js';\n\n/**\n * @unrestricted\n */\nexport default class CustomElementRegistry {\n\n /**\n * @param {!CustomElementInternals} internals\n */\n constructor(internals) {\n /**\n * @private\n * @type {boolean}\n */\n this._elementDefinitionIsRunning = false;\n\n /**\n * @private\n * @type {!CustomElementInternals}\n */\n this._internals = internals;\n\n /**\n * @private\n * @type {!Map>}\n */\n this._whenDefinedDeferred = new Map();\n\n /**\n * The default flush callback triggers the document walk synchronously.\n * @private\n * @type {!Function}\n */\n this._flushCallback = fn => fn();\n\n /**\n * @private\n * @type {boolean}\n */\n this._flushPending = false;\n\n /**\n * @private\n * @type {!Array}\n */\n this._pendingDefinitions = [];\n\n /**\n * @private\n * @type {!DocumentConstructionObserver}\n */\n this._documentConstructionObserver = new DocumentConstructionObserver(internals, document);\n }\n\n /**\n * @param {string} localName\n * @param {!Function} constructor\n */\n define(localName, constructor) {\n if (!(constructor instanceof Function)) {\n throw new TypeError('Custom element constructors must be functions.');\n }\n\n if (!Utilities.isValidCustomElementName(localName)) {\n throw new SyntaxError(`The element name '${localName}' is not valid.`);\n }\n\n if (this._internals.localNameToDefinition(localName)) {\n throw new Error(`A custom element with name '${localName}' has already been defined.`);\n }\n\n if (this._elementDefinitionIsRunning) {\n throw new Error('A custom element is already being defined.');\n }\n this._elementDefinitionIsRunning = true;\n\n let connectedCallback;\n let disconnectedCallback;\n let adoptedCallback;\n let attributeChangedCallback;\n let observedAttributes;\n try {\n /** @type {!Object} */\n const prototype = constructor.prototype;\n if (!(prototype instanceof Object)) {\n throw new TypeError('The custom element constructor\\'s prototype is not an object.');\n }\n\n function getCallback(name) {\n const callbackValue = prototype[name];\n if (callbackValue !== undefined && !(callbackValue instanceof Function)) {\n throw new Error(`The '${name}' callback must be a function.`);\n }\n return callbackValue;\n }\n\n connectedCallback = getCallback('connectedCallback');\n disconnectedCallback = getCallback('disconnectedCallback');\n adoptedCallback = getCallback('adoptedCallback');\n attributeChangedCallback = getCallback('attributeChangedCallback');\n observedAttributes = constructor['observedAttributes'] || [];\n } catch (e) {\n return;\n } finally {\n this._elementDefinitionIsRunning = false;\n }\n\n const definition = {\n localName,\n constructor,\n connectedCallback,\n disconnectedCallback,\n adoptedCallback,\n attributeChangedCallback,\n observedAttributes,\n constructionStack: [],\n };\n\n this._internals.setDefinition(localName, definition);\n this._pendingDefinitions.push(definition);\n\n // If we've already called the flush callback and it hasn't called back yet,\n // don't call it again.\n if (!this._flushPending) {\n this._flushPending = true;\n this._flushCallback(() => this._flush());\n }\n }\n\n upgrade(element) {\n this._internals.patchAndUpgradeTree(element);\n }\n\n _flush() {\n // If no new definitions were defined, don't attempt to flush. This could\n // happen if a flush callback keeps the function it is given and calls it\n // multiple times.\n if (this._flushPending === false) return;\n this._flushPending = false;\n\n const pendingDefinitions = this._pendingDefinitions;\n\n /**\n * Unupgraded elements with definitions that were defined *before* the last\n * flush, in document order.\n * @type {!Array}\n */\n const elementsWithStableDefinitions = [];\n\n /**\n * A map from `localName`s of definitions that were defined *after* the last\n * flush to unupgraded elements matching that definition, in document order.\n * @type {!Map>}\n */\n const elementsWithPendingDefinitions = new Map();\n for (let i = 0; i < pendingDefinitions.length; i++) {\n elementsWithPendingDefinitions.set(pendingDefinitions[i].localName, []);\n }\n\n this._internals.patchAndUpgradeTree(document, {\n upgrade: element => {\n // Ignore the element if it has already upgraded or failed to upgrade.\n if (element.__CE_state !== undefined) return;\n\n const localName = element.localName;\n\n // If there is an applicable pending definition for the element, add the\n // element to the list of elements to be upgraded with that definition.\n const pendingElements = elementsWithPendingDefinitions.get(localName);\n if (pendingElements) {\n pendingElements.push(element);\n // If there is *any other* applicable definition for the element, add it\n // to the list of elements with stable definitions that need to be upgraded.\n } else if (this._internals.localNameToDefinition(localName)) {\n elementsWithStableDefinitions.push(element);\n }\n },\n });\n\n // Upgrade elements with 'stable' definitions first.\n for (let i = 0; i < elementsWithStableDefinitions.length; i++) {\n this._internals.upgradeElement(elementsWithStableDefinitions[i]);\n }\n\n // Upgrade elements with 'pending' definitions in the order they were defined.\n while (pendingDefinitions.length > 0) {\n const definition = pendingDefinitions.shift();\n const localName = definition.localName;\n\n // Attempt to upgrade all applicable elements.\n const pendingUpgradableElements = elementsWithPendingDefinitions.get(definition.localName);\n for (let i = 0; i < pendingUpgradableElements.length; i++) {\n this._internals.upgradeElement(pendingUpgradableElements[i]);\n }\n\n // Resolve any promises created by `whenDefined` for the definition.\n const deferred = this._whenDefinedDeferred.get(localName);\n if (deferred) {\n deferred.resolve(undefined);\n }\n }\n }\n\n /**\n * @param {string} localName\n * @return {Function|undefined}\n */\n get(localName) {\n const definition = this._internals.localNameToDefinition(localName);\n if (definition) {\n return definition.constructor;\n }\n\n return undefined;\n }\n\n /**\n * @param {string} localName\n * @return {!Promise}\n */\n whenDefined(localName) {\n if (!Utilities.isValidCustomElementName(localName)) {\n return Promise.reject(new SyntaxError(`'${localName}' is not a valid custom element name.`));\n }\n\n const prior = this._whenDefinedDeferred.get(localName);\n if (prior) {\n return prior.toPromise();\n }\n\n const deferred = new Deferred();\n this._whenDefinedDeferred.set(localName, deferred);\n\n const definition = this._internals.localNameToDefinition(localName);\n // Resolve immediately only if the given local name has a definition *and*\n // the full document walk to upgrade elements with that local name has\n // already happened.\n if (definition && !this._pendingDefinitions.some(d => d.localName === localName)) {\n deferred.resolve(undefined);\n }\n\n return deferred.toPromise();\n }\n\n polyfillWrapFlushCallback(outer) {\n this._documentConstructionObserver.disconnect();\n const inner = this._flushCallback;\n this._flushCallback = flush => outer(() => inner(flush));\n }\n}\n\n// Closure compiler exports.\nwindow['CustomElementRegistry'] = CustomElementRegistry;\nCustomElementRegistry.prototype['define'] = CustomElementRegistry.prototype.define;\nCustomElementRegistry.prototype['upgrade'] = CustomElementRegistry.prototype.upgrade;\nCustomElementRegistry.prototype['get'] = CustomElementRegistry.prototype.get;\nCustomElementRegistry.prototype['whenDefined'] = CustomElementRegistry.prototype.whenDefined;\nCustomElementRegistry.prototype['polyfillWrapFlushCallback'] = CustomElementRegistry.prototype.polyfillWrapFlushCallback;\n","/**\n * @template T\n */\nexport default class Deferred {\n constructor() {\n /**\n * @private\n * @type {T|undefined}\n */\n this._value = undefined;\n\n /**\n * @private\n * @type {Function|undefined}\n */\n this._resolve = undefined;\n\n /**\n * @private\n * @type {!Promise}\n */\n this._promise = new Promise(resolve => {\n this._resolve = resolve;\n\n if (this._value) {\n resolve(this._value);\n }\n });\n }\n\n /**\n * @param {T} value\n */\n resolve(value) {\n if (this._value) {\n throw new Error('Already resolved.');\n }\n\n this._value = value;\n\n if (this._resolve) {\n this._resolve(value);\n }\n }\n\n /**\n * @return {!Promise}\n */\n toPromise() {\n return this._promise;\n }\n}\n","export default {\n Document_createElement: window.Document.prototype.createElement,\n Document_createElementNS: window.Document.prototype.createElementNS,\n Document_importNode: window.Document.prototype.importNode,\n Document_prepend: window.Document.prototype['prepend'],\n Document_append: window.Document.prototype['append'],\n DocumentFragment_prepend: window.DocumentFragment.prototype['prepend'],\n DocumentFragment_append: window.DocumentFragment.prototype['append'],\n Node_cloneNode: window.Node.prototype.cloneNode,\n Node_appendChild: window.Node.prototype.appendChild,\n Node_insertBefore: window.Node.prototype.insertBefore,\n Node_removeChild: window.Node.prototype.removeChild,\n Node_replaceChild: window.Node.prototype.replaceChild,\n Node_textContent: Object.getOwnPropertyDescriptor(window.Node.prototype, 'textContent'),\n Element_attachShadow: window.Element.prototype['attachShadow'],\n Element_innerHTML: Object.getOwnPropertyDescriptor(window.Element.prototype, 'innerHTML'),\n Element_getAttribute: window.Element.prototype.getAttribute,\n Element_setAttribute: window.Element.prototype.setAttribute,\n Element_removeAttribute: window.Element.prototype.removeAttribute,\n Element_getAttributeNS: window.Element.prototype.getAttributeNS,\n Element_setAttributeNS: window.Element.prototype.setAttributeNS,\n Element_removeAttributeNS: window.Element.prototype.removeAttributeNS,\n Element_insertAdjacentElement: window.Element.prototype['insertAdjacentElement'],\n Element_insertAdjacentHTML: window.Element.prototype['insertAdjacentHTML'],\n Element_prepend: window.Element.prototype['prepend'],\n Element_append: window.Element.prototype['append'],\n Element_before: window.Element.prototype['before'],\n Element_after: window.Element.prototype['after'],\n Element_replaceWith: window.Element.prototype['replaceWith'],\n Element_remove: window.Element.prototype['remove'],\n HTMLElement: window.HTMLElement,\n HTMLElement_innerHTML: Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, 'innerHTML'),\n HTMLElement_insertAdjacentElement: window.HTMLElement.prototype['insertAdjacentElement'],\n HTMLElement_insertAdjacentHTML: window.HTMLElement.prototype['insertAdjacentHTML'],\n};\n","/**\n * This class exists only to work around Closure's lack of a way to describe\n * singletons. It represents the 'already constructed marker' used in custom\n * element construction stacks.\n *\n * https://html.spec.whatwg.org/#concept-already-constructed-marker\n */\nclass AlreadyConstructedMarker {}\n\nexport default new AlreadyConstructedMarker();\n","import Native from './Native.js';\nimport CustomElementInternals from '../CustomElementInternals.js';\nimport CEState from '../CustomElementState.js';\nimport AlreadyConstructedMarker from '../AlreadyConstructedMarker.js';\n\n/**\n * @param {!CustomElementInternals} internals\n */\nexport default function(internals) {\n window['HTMLElement'] = (function() {\n /**\n * @type {function(new: HTMLElement): !HTMLElement}\n */\n function HTMLElement() {\n // This should really be `new.target` but `new.target` can't be emulated\n // in ES5. Assuming the user keeps the default value of the constructor's\n // prototype's `constructor` property, this is equivalent.\n /** @type {!Function} */\n const constructor = this.constructor;\n\n const definition = internals.constructorToDefinition(constructor);\n if (!definition) {\n throw new Error('The custom element being constructed was not registered with `customElements`.');\n }\n\n const constructionStack = definition.constructionStack;\n\n if (constructionStack.length === 0) {\n const element = Native.Document_createElement.call(document, definition.localName);\n Object.setPrototypeOf(element, constructor.prototype);\n element.__CE_state = CEState.custom;\n element.__CE_definition = definition;\n internals.patch(element);\n return element;\n }\n\n const lastIndex = constructionStack.length - 1;\n const element = constructionStack[lastIndex];\n if (element === AlreadyConstructedMarker) {\n throw new Error('The HTMLElement constructor was either called reentrantly for this constructor or called multiple times.');\n }\n constructionStack[lastIndex] = AlreadyConstructedMarker;\n\n Object.setPrototypeOf(element, constructor.prototype);\n internals.patch(/** @type {!HTMLElement} */ (element));\n\n return element;\n }\n\n HTMLElement.prototype = Native.HTMLElement.prototype;\n // Safari 9 has `writable: false` on the propertyDescriptor\n // Make it writable so that TypeScript can patch up the\n // constructor in the ES5 compiled code.\n Object.defineProperty(HTMLElement.prototype, 'constructor', {\n writable: true,\n configurable: true,\n enumerable: false,\n value: HTMLElement\n });\n\n return HTMLElement;\n })();\n};\n","/**\n * @license\n * Copyright (c) 2016 The Polymer Project Authors. All rights reserved.\n * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt\n * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt\n * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt\n * Code distributed by Google as part of the polymer project is also\n * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt\n */\n\nimport CustomElementInternals from './CustomElementInternals.js';\nimport CustomElementRegistry from './CustomElementRegistry.js';\n\nimport PatchHTMLElement from './Patch/HTMLElement.js';\nimport PatchDocument from './Patch/Document.js';\nimport PatchDocumentFragment from './Patch/DocumentFragment.js';\nimport PatchNode from './Patch/Node.js';\nimport PatchElement from './Patch/Element.js';\n\nconst priorCustomElements = window['customElements'];\n\nif (!priorCustomElements ||\n priorCustomElements['forcePolyfill'] ||\n (typeof priorCustomElements['define'] != 'function') ||\n (typeof priorCustomElements['get'] != 'function')) {\n /** @type {!CustomElementInternals} */\n const internals = new CustomElementInternals();\n\n PatchHTMLElement(internals);\n PatchDocument(internals);\n PatchDocumentFragment(internals);\n PatchNode(internals);\n PatchElement(internals);\n\n // The main document is always associated with the registry.\n document.__CE_hasRegistry = true;\n\n /** @type {!CustomElementRegistry} */\n const customElements = new CustomElementRegistry(internals);\n\n Object.defineProperty(window, 'customElements', {\n configurable: true,\n enumerable: true,\n value: customElements,\n });\n}\n","import CustomElementInternals from '../../CustomElementInternals.js';\nimport * as Utilities from '../../Utilities.js';\n\n/**\n * @typedef {{\n * prepend: !function(...(!Node|string)),\n * append: !function(...(!Node|string)),\n * }}\n */\nlet ParentNodeNativeMethods;\n\n/**\n * @param {!CustomElementInternals} internals\n * @param {!Object} destination\n * @param {!ParentNodeNativeMethods} builtIn\n */\nexport default function(internals, destination, builtIn) {\n /**\n * @param {!function(...(!Node|string))} builtInMethod\n * @return {!function(...(!Node|string))}\n */\n function appendPrependPatch(builtInMethod) {\n return function(...nodes) {\n /**\n * A copy of `nodes`, with any DocumentFragment replaced by its children.\n * @type {!Array}\n */\n const flattenedNodes = [];\n\n /**\n * Elements in `nodes` that were connected before this call.\n * @type {!Array}\n */\n const connectedElements = [];\n\n for (var i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n\n if (node instanceof Element && Utilities.isConnected(node)) {\n connectedElements.push(node);\n }\n\n if (node instanceof DocumentFragment) {\n for (let child = node.firstChild; child; child = child.nextSibling) {\n flattenedNodes.push(child);\n }\n } else {\n flattenedNodes.push(node);\n }\n }\n\n builtInMethod.apply(this, nodes);\n\n for (let i = 0; i < connectedElements.length; i++) {\n internals.disconnectTree(connectedElements[i]);\n }\n\n if (Utilities.isConnected(this)) {\n for (let i = 0; i < flattenedNodes.length; i++) {\n const node = flattenedNodes[i];\n if (node instanceof Element) {\n internals.connectTree(node);\n }\n }\n }\n };\n }\n\n if (builtIn.prepend !== undefined) {\n Utilities.setPropertyUnchecked(destination, 'prepend', appendPrependPatch(builtIn.prepend));\n }\n\n if (builtIn.append !== undefined) {\n Utilities.setPropertyUnchecked(destination, 'append', appendPrependPatch(builtIn.append));\n }\n};\n","import Native from './Native.js';\nimport CustomElementInternals from '../CustomElementInternals.js';\nimport * as Utilities from '../Utilities.js';\n\nimport PatchParentNode from './Interface/ParentNode.js';\n\n/**\n * @param {!CustomElementInternals} internals\n */\nexport default function(internals) {\n Utilities.setPropertyUnchecked(Document.prototype, 'createElement',\n /**\n * @this {Document}\n * @param {string} localName\n * @return {!Element}\n */\n function(localName) {\n // Only create custom elements if this document is associated with the registry.\n if (this.__CE_hasRegistry) {\n const definition = internals.localNameToDefinition(localName);\n if (definition) {\n return new (definition.constructor)();\n }\n }\n\n const result = /** @type {!Element} */\n (Native.Document_createElement.call(this, localName));\n internals.patch(result);\n return result;\n });\n\n Utilities.setPropertyUnchecked(Document.prototype, 'importNode',\n /**\n * @this {Document}\n * @param {!Node} node\n * @param {boolean=} deep\n * @return {!Node}\n */\n function(node, deep) {\n const clone = Native.Document_importNode.call(this, node, deep);\n // Only create custom elements if this document is associated with the registry.\n if (!this.__CE_hasRegistry) {\n internals.patchTree(clone);\n } else {\n internals.patchAndUpgradeTree(clone);\n }\n return clone;\n });\n\n const NS_HTML = \"http://www.w3.org/1999/xhtml\";\n\n Utilities.setPropertyUnchecked(Document.prototype, 'createElementNS',\n /**\n * @this {Document}\n * @param {?string} namespace\n * @param {string} localName\n * @return {!Element}\n */\n function(namespace, localName) {\n // Only create custom elements if this document is associated with the registry.\n if (this.__CE_hasRegistry && (namespace === null || namespace === NS_HTML)) {\n const definition = internals.localNameToDefinition(localName);\n if (definition) {\n return new (definition.constructor)();\n }\n }\n\n const result = /** @type {!Element} */\n (Native.Document_createElementNS.call(this, namespace, localName));\n internals.patch(result);\n return result;\n });\n\n PatchParentNode(internals, Document.prototype, {\n prepend: Native.Document_prepend,\n append: Native.Document_append,\n });\n};\n","import Native from './Native.js';\nimport CustomElementInternals from '../CustomElementInternals.js';\nimport * as Utilities from '../Utilities.js';\n\n/**\n * @param {!CustomElementInternals} internals\n */\nexport default function(internals) {\n // `Node#nodeValue` is implemented on `Attr`.\n // `Node#textContent` is implemented on `Attr`, `Element`.\n\n Utilities.setPropertyUnchecked(Node.prototype, 'insertBefore',\n /**\n * @this {Node}\n * @param {!Node} node\n * @param {?Node} refNode\n * @return {!Node}\n */\n function(node, refNode) {\n if (node instanceof DocumentFragment) {\n const insertedNodes = Array.prototype.slice.apply(node.childNodes);\n const nativeResult = Native.Node_insertBefore.call(this, node, refNode);\n\n // DocumentFragments can't be connected, so `disconnectTree` will never\n // need to be called on a DocumentFragment's children after inserting it.\n\n if (Utilities.isConnected(this)) {\n for (let i = 0; i < insertedNodes.length; i++) {\n internals.connectTree(insertedNodes[i]);\n }\n }\n\n return nativeResult;\n }\n\n const nodeWasConnected = Utilities.isConnected(node);\n const nativeResult = Native.Node_insertBefore.call(this, node, refNode);\n\n if (nodeWasConnected) {\n internals.disconnectTree(node);\n }\n\n if (Utilities.isConnected(this)) {\n internals.connectTree(node);\n }\n\n return nativeResult;\n });\n\n Utilities.setPropertyUnchecked(Node.prototype, 'appendChild',\n /**\n * @this {Node}\n * @param {!Node} node\n * @return {!Node}\n */\n function(node) {\n if (node instanceof DocumentFragment) {\n const insertedNodes = Array.prototype.slice.apply(node.childNodes);\n const nativeResult = Native.Node_appendChild.call(this, node);\n\n // DocumentFragments can't be connected, so `disconnectTree` will never\n // need to be called on a DocumentFragment's children after inserting it.\n\n if (Utilities.isConnected(this)) {\n for (let i = 0; i < insertedNodes.length; i++) {\n internals.connectTree(insertedNodes[i]);\n }\n }\n\n return nativeResult;\n }\n\n const nodeWasConnected = Utilities.isConnected(node);\n const nativeResult = Native.Node_appendChild.call(this, node);\n\n if (nodeWasConnected) {\n internals.disconnectTree(node);\n }\n\n if (Utilities.isConnected(this)) {\n internals.connectTree(node);\n }\n\n return nativeResult;\n });\n\n Utilities.setPropertyUnchecked(Node.prototype, 'cloneNode',\n /**\n * @this {Node}\n * @param {boolean=} deep\n * @return {!Node}\n */\n function(deep) {\n const clone = Native.Node_cloneNode.call(this, deep);\n // Only create custom elements if this element's owner document is\n // associated with the registry.\n if (!this.ownerDocument.__CE_hasRegistry) {\n internals.patchTree(clone);\n } else {\n internals.patchAndUpgradeTree(clone);\n }\n return clone;\n });\n\n Utilities.setPropertyUnchecked(Node.prototype, 'removeChild',\n /**\n * @this {Node}\n * @param {!Node} node\n * @return {!Node}\n */\n function(node) {\n const nodeWasConnected = Utilities.isConnected(node);\n const nativeResult = Native.Node_removeChild.call(this, node);\n\n if (nodeWasConnected) {\n internals.disconnectTree(node);\n }\n\n return nativeResult;\n });\n\n Utilities.setPropertyUnchecked(Node.prototype, 'replaceChild',\n /**\n * @this {Node}\n * @param {!Node} nodeToInsert\n * @param {!Node} nodeToRemove\n * @return {!Node}\n */\n function(nodeToInsert, nodeToRemove) {\n if (nodeToInsert instanceof DocumentFragment) {\n const insertedNodes = Array.prototype.slice.apply(nodeToInsert.childNodes);\n const nativeResult = Native.Node_replaceChild.call(this, nodeToInsert, nodeToRemove);\n\n // DocumentFragments can't be connected, so `disconnectTree` will never\n // need to be called on a DocumentFragment's children after inserting it.\n\n if (Utilities.isConnected(this)) {\n internals.disconnectTree(nodeToRemove);\n for (let i = 0; i < insertedNodes.length; i++) {\n internals.connectTree(insertedNodes[i]);\n }\n }\n\n return nativeResult;\n }\n\n const nodeToInsertWasConnected = Utilities.isConnected(nodeToInsert);\n const nativeResult = Native.Node_replaceChild.call(this, nodeToInsert, nodeToRemove);\n const thisIsConnected = Utilities.isConnected(this);\n\n if (thisIsConnected) {\n internals.disconnectTree(nodeToRemove);\n }\n\n if (nodeToInsertWasConnected) {\n internals.disconnectTree(nodeToInsert);\n }\n\n if (thisIsConnected) {\n internals.connectTree(nodeToInsert);\n }\n\n return nativeResult;\n });\n\n\n function patch_textContent(destination, baseDescriptor) {\n Object.defineProperty(destination, 'textContent', {\n enumerable: baseDescriptor.enumerable,\n configurable: true,\n get: baseDescriptor.get,\n set: /** @this {Node} */ function(assignedValue) {\n // If this is a text node then there are no nodes to disconnect.\n if (this.nodeType === Node.TEXT_NODE) {\n baseDescriptor.set.call(this, assignedValue);\n return;\n }\n\n let removedNodes = undefined;\n // Checking for `firstChild` is faster than reading `childNodes.length`\n // to compare with 0.\n if (this.firstChild) {\n // Using `childNodes` is faster than `children`, even though we only\n // care about elements.\n const childNodes = this.childNodes;\n const childNodesLength = childNodes.length;\n if (childNodesLength > 0 && Utilities.isConnected(this)) {\n // Copying an array by iterating is faster than using slice.\n removedNodes = new Array(childNodesLength);\n for (let i = 0; i < childNodesLength; i++) {\n removedNodes[i] = childNodes[i];\n }\n }\n }\n\n baseDescriptor.set.call(this, assignedValue);\n\n if (removedNodes) {\n for (let i = 0; i < removedNodes.length; i++) {\n internals.disconnectTree(removedNodes[i]);\n }\n }\n },\n });\n }\n\n if (Native.Node_textContent && Native.Node_textContent.get) {\n patch_textContent(Node.prototype, Native.Node_textContent);\n } else {\n internals.addPatch(function(element) {\n patch_textContent(element, {\n enumerable: true,\n configurable: true,\n // NOTE: This implementation of the `textContent` getter assumes that\n // text nodes' `textContent` getter will not be patched.\n get: /** @this {Node} */ function() {\n /** @type {!Array} */\n const parts = [];\n\n for (let i = 0; i < this.childNodes.length; i++) {\n parts.push(this.childNodes[i].textContent);\n }\n\n return parts.join('');\n },\n set: /** @this {Node} */ function(assignedValue) {\n while (this.firstChild) {\n Native.Node_removeChild.call(this, this.firstChild);\n }\n Native.Node_appendChild.call(this, document.createTextNode(assignedValue));\n },\n });\n });\n }\n};\n","import CustomElementInternals from '../../CustomElementInternals.js';\nimport * as Utilities from '../../Utilities.js';\n\n/**\n * @typedef {{\n * before: !function(...(!Node|string)),\n * after: !function(...(!Node|string)),\n * replaceWith: !function(...(!Node|string)),\n * remove: !function(),\n * }}\n */\nlet ChildNodeNativeMethods;\n\n/**\n * @param {!CustomElementInternals} internals\n * @param {!Object} destination\n * @param {!ChildNodeNativeMethods} builtIn\n */\nexport default function(internals, destination, builtIn) {\n /**\n * @param {!function(...(!Node|string))} builtInMethod\n * @return {!function(...(!Node|string))}\n */\n function beforeAfterPatch(builtInMethod) {\n return function(...nodes) {\n /**\n * A copy of `nodes`, with any DocumentFragment replaced by its children.\n * @type {!Array}\n */\n const flattenedNodes = [];\n\n /**\n * Elements in `nodes` that were connected before this call.\n * @type {!Array}\n */\n const connectedElements = [];\n\n for (var i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n\n if (node instanceof Element && Utilities.isConnected(node)) {\n connectedElements.push(node);\n }\n\n if (node instanceof DocumentFragment) {\n for (let child = node.firstChild; child; child = child.nextSibling) {\n flattenedNodes.push(child);\n }\n } else {\n flattenedNodes.push(node);\n }\n }\n\n builtInMethod.apply(this, nodes);\n\n for (let i = 0; i < connectedElements.length; i++) {\n internals.disconnectTree(connectedElements[i]);\n }\n\n if (Utilities.isConnected(this)) {\n for (let i = 0; i < flattenedNodes.length; i++) {\n const node = flattenedNodes[i];\n if (node instanceof Element) {\n internals.connectTree(node);\n }\n }\n }\n };\n }\n\n if (builtIn.before !== undefined) {\n Utilities.setPropertyUnchecked(destination, 'before', beforeAfterPatch(builtIn.before));\n }\n\n if (builtIn.before !== undefined) {\n Utilities.setPropertyUnchecked(destination, 'after', beforeAfterPatch(builtIn.after));\n }\n\n if (builtIn.replaceWith !== undefined) {\n Utilities.setPropertyUnchecked(destination, 'replaceWith',\n /**\n * @param {...(!Node|string)} nodes\n */\n function(...nodes) {\n /**\n * A copy of `nodes`, with any DocumentFragment replaced by its children.\n * @type {!Array}\n */\n const flattenedNodes = [];\n\n /**\n * Elements in `nodes` that were connected before this call.\n * @type {!Array}\n */\n const connectedElements = [];\n\n for (var i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n\n if (node instanceof Element && Utilities.isConnected(node)) {\n connectedElements.push(node);\n }\n\n if (node instanceof DocumentFragment) {\n for (let child = node.firstChild; child; child = child.nextSibling) {\n flattenedNodes.push(child);\n }\n } else {\n flattenedNodes.push(node);\n }\n }\n\n const wasConnected = Utilities.isConnected(this);\n\n builtIn.replaceWith.apply(this, nodes);\n\n for (let i = 0; i < connectedElements.length; i++) {\n internals.disconnectTree(connectedElements[i]);\n }\n\n if (wasConnected) {\n internals.disconnectTree(this);\n for (let i = 0; i < flattenedNodes.length; i++) {\n const node = flattenedNodes[i];\n if (node instanceof Element) {\n internals.connectTree(node);\n }\n }\n }\n });\n }\n\n if (builtIn.remove !== undefined) {\n Utilities.setPropertyUnchecked(destination, 'remove',\n function() {\n const wasConnected = Utilities.isConnected(this);\n\n builtIn.remove.call(this);\n\n if (wasConnected) {\n internals.disconnectTree(this);\n }\n });\n }\n};\n","import Native from './Native.js';\nimport CustomElementInternals from '../CustomElementInternals.js';\nimport CEState from '../CustomElementState.js';\nimport * as Utilities from '../Utilities.js';\n\nimport PatchParentNode from './Interface/ParentNode.js';\nimport PatchChildNode from './Interface/ChildNode.js';\n\n/**\n * @param {!CustomElementInternals} internals\n */\nexport default function(internals) {\n if (Native.Element_attachShadow) {\n Utilities.setPropertyUnchecked(Element.prototype, 'attachShadow',\n /**\n * @this {Element}\n * @param {!{mode: string}} init\n * @return {ShadowRoot}\n */\n function(init) {\n const shadowRoot = Native.Element_attachShadow.call(this, init);\n this.__CE_shadowRoot = shadowRoot;\n return shadowRoot;\n });\n }\n\n\n function patch_innerHTML(destination, baseDescriptor) {\n Object.defineProperty(destination, 'innerHTML', {\n enumerable: baseDescriptor.enumerable,\n configurable: true,\n get: baseDescriptor.get,\n set: /** @this {Element} */ function(htmlString) {\n const isConnected = Utilities.isConnected(this);\n\n // NOTE: In IE11, when using the native `innerHTML` setter, all nodes\n // that were previously descendants of the context element have all of\n // their children removed as part of the set - the entire subtree is\n // 'disassembled'. This work around walks the subtree *before* using the\n // native setter.\n /** @type {!Array|undefined} */\n let removedElements = undefined;\n if (isConnected) {\n removedElements = [];\n Utilities.walkDeepDescendantElements(this, element => {\n if (element !== this) {\n removedElements.push(element);\n }\n });\n }\n\n baseDescriptor.set.call(this, htmlString);\n\n if (removedElements) {\n for (let i = 0; i < removedElements.length; i++) {\n const element = removedElements[i];\n if (element.__CE_state === CEState.custom) {\n internals.disconnectedCallback(element);\n }\n }\n }\n\n // Only create custom elements if this element's owner document is\n // associated with the registry.\n if (!this.ownerDocument.__CE_hasRegistry) {\n internals.patchTree(this);\n } else {\n internals.patchAndUpgradeTree(this);\n }\n return htmlString;\n },\n });\n }\n\n if (Native.Element_innerHTML && Native.Element_innerHTML.get) {\n patch_innerHTML(Element.prototype, Native.Element_innerHTML);\n } else if (Native.HTMLElement_innerHTML && Native.HTMLElement_innerHTML.get) {\n patch_innerHTML(HTMLElement.prototype, Native.HTMLElement_innerHTML);\n } else {\n\n internals.addPatch(function(element) {\n patch_innerHTML(element, {\n enumerable: true,\n configurable: true,\n // Implements getting `innerHTML` by performing an unpatched `cloneNode`\n // of the element and returning the resulting element's `innerHTML`.\n // TODO: Is this too expensive?\n get: /** @this {Element} */ function() {\n return Native.Node_cloneNode.call(this, true).innerHTML;\n },\n // Implements setting `innerHTML` by creating an unpatched element,\n // setting `innerHTML` of that element and replacing the target\n // element's children with those of the unpatched element.\n set: /** @this {Element} */ function(assignedValue) {\n // NOTE: re-route to `content` for `template` elements.\n // We need to do this because `template.appendChild` does not\n // route into `template.content`.\n const isTemplate = (this.localName === 'template');\n /** @type {!Node} */\n const content = isTemplate ? (/** @type {!HTMLTemplateElement} */\n (this)).content : this;\n /** @type {!Node} */\n const rawElement = Native.Document_createElementNS.call(document,\n this.namespaceURI, this.localName);\n rawElement.innerHTML = assignedValue;\n\n while (content.childNodes.length > 0) {\n Native.Node_removeChild.call(content, content.childNodes[0]);\n }\n const container = isTemplate ? rawElement.content : rawElement;\n while (container.childNodes.length > 0) {\n Native.Node_appendChild.call(content, container.childNodes[0]);\n }\n },\n });\n });\n }\n\n\n Utilities.setPropertyUnchecked(Element.prototype, 'setAttribute',\n /**\n * @this {Element}\n * @param {string} name\n * @param {string} newValue\n */\n function(name, newValue) {\n // Fast path for non-custom elements.\n if (this.__CE_state !== CEState.custom) {\n return Native.Element_setAttribute.call(this, name, newValue);\n }\n\n const oldValue = Native.Element_getAttribute.call(this, name);\n Native.Element_setAttribute.call(this, name, newValue);\n newValue = Native.Element_getAttribute.call(this, name);\n internals.attributeChangedCallback(this, name, oldValue, newValue, null);\n });\n\n Utilities.setPropertyUnchecked(Element.prototype, 'setAttributeNS',\n /**\n * @this {Element}\n * @param {?string} namespace\n * @param {string} name\n * @param {string} newValue\n */\n function(namespace, name, newValue) {\n // Fast path for non-custom elements.\n if (this.__CE_state !== CEState.custom) {\n return Native.Element_setAttributeNS.call(this, namespace, name, newValue);\n }\n\n const oldValue = Native.Element_getAttributeNS.call(this, namespace, name);\n Native.Element_setAttributeNS.call(this, namespace, name, newValue);\n newValue = Native.Element_getAttributeNS.call(this, namespace, name);\n internals.attributeChangedCallback(this, name, oldValue, newValue, namespace);\n });\n\n Utilities.setPropertyUnchecked(Element.prototype, 'removeAttribute',\n /**\n * @this {Element}\n * @param {string} name\n */\n function(name) {\n // Fast path for non-custom elements.\n if (this.__CE_state !== CEState.custom) {\n return Native.Element_removeAttribute.call(this, name);\n }\n\n const oldValue = Native.Element_getAttribute.call(this, name);\n Native.Element_removeAttribute.call(this, name);\n if (oldValue !== null) {\n internals.attributeChangedCallback(this, name, oldValue, null, null);\n }\n });\n\n Utilities.setPropertyUnchecked(Element.prototype, 'removeAttributeNS',\n /**\n * @this {Element}\n * @param {?string} namespace\n * @param {string} name\n */\n function(namespace, name) {\n // Fast path for non-custom elements.\n if (this.__CE_state !== CEState.custom) {\n return Native.Element_removeAttributeNS.call(this, namespace, name);\n }\n\n const oldValue = Native.Element_getAttributeNS.call(this, namespace, name);\n Native.Element_removeAttributeNS.call(this, namespace, name);\n // In older browsers, `Element#getAttributeNS` may return the empty string\n // instead of null if the attribute does not exist. For details, see;\n // https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttributeNS#Notes\n const newValue = Native.Element_getAttributeNS.call(this, namespace, name);\n if (oldValue !== newValue) {\n internals.attributeChangedCallback(this, name, oldValue, newValue, namespace);\n }\n });\n\n\n function patch_insertAdjacentElement(destination, baseMethod) {\n Utilities.setPropertyUnchecked(destination, 'insertAdjacentElement',\n /**\n * @this {Element}\n * @param {string} position\n * @param {!Element} element\n * @return {?Element}\n */\n function(position, element) {\n const wasConnected = Utilities.isConnected(element);\n const insertedElement = /** @type {!Element} */\n (baseMethod.call(this, position, element));\n\n if (wasConnected) {\n internals.disconnectTree(element);\n }\n\n if (Utilities.isConnected(insertedElement)) {\n internals.connectTree(element);\n }\n return insertedElement;\n });\n }\n\n if (Native.HTMLElement_insertAdjacentElement) {\n patch_insertAdjacentElement(HTMLElement.prototype, Native.HTMLElement_insertAdjacentElement);\n } else if (Native.Element_insertAdjacentElement) {\n patch_insertAdjacentElement(Element.prototype, Native.Element_insertAdjacentElement);\n } else {\n console.warn('Custom Elements: `Element#insertAdjacentElement` was not patched.');\n }\n\n\n function patch_insertAdjacentHTML(destination, baseMethod) {\n /**\n * Patches and upgrades all nodes which are siblings between `start`\n * (inclusive) and `end` (exclusive). If `end` is `null`, then all siblings\n * following `start` will be patched and upgraded.\n * @param {!Node} start\n * @param {?Node} end\n */\n function upgradeNodesInRange(start, end) {\n const nodes = [];\n for (let node = start; node !== end; node = node.nextSibling) {\n nodes.push(node);\n }\n for (let i = 0; i < nodes.length; i++) {\n internals.patchAndUpgradeTree(nodes[i]);\n }\n }\n\n Utilities.setPropertyUnchecked(destination, 'insertAdjacentHTML',\n /**\n * @this {Element}\n * @param {string} position\n * @param {string} text\n */\n function(position, text) {\n position = position.toLowerCase();\n\n if (position === \"beforebegin\") {\n const marker = this.previousSibling;\n baseMethod.call(this, position, text);\n upgradeNodesInRange(\n /** @type {!Node} */ (marker || this.parentNode.firstChild), this);\n } else if (position === \"afterbegin\") {\n const marker = this.firstChild;\n baseMethod.call(this, position, text);\n upgradeNodesInRange(/** @type {!Node} */ (this.firstChild), marker);\n } else if (position === \"beforeend\") {\n const marker = this.lastChild;\n baseMethod.call(this, position, text);\n upgradeNodesInRange(marker || this.firstChild, null);\n } else if (position === \"afterend\") {\n const marker = this.nextSibling;\n baseMethod.call(this, position, text);\n upgradeNodesInRange(/** @type {!Node} */ (this.nextSibling), marker);\n } else {\n throw new SyntaxError(`The value provided (${String(position)}) is ` +\n \"not one of 'beforebegin', 'afterbegin', 'beforeend', or 'afterend'.\");\n }\n });\n }\n\n if (Native.HTMLElement_insertAdjacentHTML) {\n patch_insertAdjacentHTML(HTMLElement.prototype, Native.HTMLElement_insertAdjacentHTML);\n } else if (Native.Element_insertAdjacentHTML) {\n patch_insertAdjacentHTML(Element.prototype, Native.Element_insertAdjacentHTML);\n } else {\n console.warn('Custom Elements: `Element#insertAdjacentHTML` was not patched.');\n }\n\n\n PatchParentNode(internals, Element.prototype, {\n prepend: Native.Element_prepend,\n append: Native.Element_append,\n });\n\n PatchChildNode(internals, Element.prototype, {\n before: Native.Element_before,\n after: Native.Element_after,\n replaceWith: Native.Element_replaceWith,\n remove: Native.Element_remove,\n });\n};\n","import CustomElementInternals from '../CustomElementInternals.js';\nimport Native from './Native.js';\nimport PatchParentNode from './Interface/ParentNode.js';\n\n/**\n * @param {!CustomElementInternals} internals\n */\nexport default function(internals) {\n PatchParentNode(internals, DocumentFragment.prototype, {\n prepend: Native.DocumentFragment_prepend,\n append: Native.DocumentFragment_append,\n });\n};\n"]} \ No newline at end of file diff --git a/client/third_party/bundles/webcomponents-sd-ce-pf.js b/client/third_party/bundles/webcomponents-sd-ce-pf.js new file mode 100644 index 0000000..d36b1d2 --- /dev/null +++ b/client/third_party/bundles/webcomponents-sd-ce-pf.js @@ -0,0 +1,234 @@ +/** +@license @nocompile +Copyright (c) 2018 The Polymer Project Authors. All rights reserved. +This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt +The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt +Code distributed by Google as part of the polymer project is also +subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt +*/ +(function(){/* + + Copyright (c) 2016 The Polymer Project Authors. All rights reserved. + This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + Code distributed by Google as part of the polymer project is also + subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt +*/ +'use strict';var q,aa="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global&&null!=global?global:this,ba="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){a!=Array.prototype&&a!=Object.prototype&&(a[b]=c.value)};function ca(){ca=function(){};aa.Symbol||(aa.Symbol=da)}var da=function(){var a=0;return function(b){return"jscomp_symbol_"+(b||"")+a++}}(); +function ea(){ca();var a=aa.Symbol.iterator;a||(a=aa.Symbol.iterator=aa.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&ba(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return fa(this)}});ea=function(){}}function fa(a){var b=0;return ha(function(){return b"+this.innerHTML+""},set:function(a){if(this.parentNode){U.body.innerHTML=a;for(a=this.ownerDocument.createDocumentFragment();U.body.firstChild;)m.call(a,U.body.firstChild); +n.call(this.parentNode,a,this)}else throw Error("Failed to set the 'outerHTML' property on 'Element': This element has no parent node.");},configurable:!0})};p(a.prototype);Dc(a.prototype);a.b=function(c){c=b(c,"template");for(var d=0,e=c.length,f;d]/g, +l=function(a){switch(a){case "&":return"&";case "<":return"<";case ">":return">";case '"':return""";case "\u00a0":return" "}};xa=function(a){for(var b={},c=0;c";break a;case Node.TEXT_NODE:h=h.data;h=k&&kf[k.localName]?h:h.replace(jb,l);break a;case Node.COMMENT_NODE:h="\x3c!--"+h.data+"--\x3e";break a;default:throw window.console.error(h),Error("not implemented");}}c+=h}return c}}if(c||v){a.a=function(a,b){var c=f.call(a,!1); +this.R&&this.R(c);b&&(m.call(c.content,f.call(a.content,!0)),kb(c.content,a.content));return c};var kb=function(c,d){if(d.querySelectorAll&&(d=b(d,"template"),0!==d.length)){c=b(c,"template");for(var e=0,f=c.length,g,h;e]/g;function mc(a){switch(a){case "&":return"&";case "<":return"<";case ">":return">";case '"':return""";case "\u00a0":return" "}}function nc(a){for(var b={},c=0;c";break a;case Node.TEXT_NODE:h=h.data;h=k&&pc[k.localName]?h:h.replace(lc,mc);break a;case Node.COMMENT_NODE:h="\x3c!--"+h.data+"--\x3e";break a;default:throw window.console.error(h), +Error("not implemented");}}c+=h}return c};var E={},H=document.createTreeWalker(document,NodeFilter.SHOW_ALL,null,!1),I=document.createTreeWalker(document,NodeFilter.SHOW_ELEMENT,null,!1);function rc(a){var b=[];H.currentNode=a;for(a=H.firstChild();a;)b.push(a),a=H.nextSibling();return b}E.parentNode=function(a){H.currentNode=a;return H.parentNode()};E.firstChild=function(a){H.currentNode=a;return H.firstChild()};E.lastChild=function(a){H.currentNode=a;return H.lastChild()};E.previousSibling=function(a){H.currentNode=a;return H.previousSibling()}; +E.nextSibling=function(a){H.currentNode=a;return H.nextSibling()};E.childNodes=rc;E.parentElement=function(a){I.currentNode=a;return I.parentNode()};E.firstElementChild=function(a){I.currentNode=a;return I.firstChild()};E.lastElementChild=function(a){I.currentNode=a;return I.lastChild()};E.previousElementSibling=function(a){I.currentNode=a;return I.previousSibling()};E.nextElementSibling=function(a){I.currentNode=a;return I.nextSibling()}; +E.children=function(a){var b=[];I.currentNode=a;for(a=I.firstChild();a;)b.push(a),a=I.nextSibling();return b};E.innerHTML=function(a){return qc(a,function(a){return rc(a)})};E.textContent=function(a){switch(a.nodeType){case Node.ELEMENT_NODE:case Node.DOCUMENT_FRAGMENT_NODE:a=document.createTreeWalker(a,NodeFilter.SHOW_TEXT,null,!1);for(var b="",c;c=a.nextNode();)b+=c.nodeValue;return b;default:return a.nodeValue}};var J={},sc=B.I,tc=[Node.prototype,Element.prototype,HTMLElement.prototype];function K(a){var b;a:{for(b=0;bd.assignedNodes.length&&(d.da=!0)}d.da&&(d.da=!1,Nd(this,b))}a=this.m;b=[];for(d=0;db.indexOf(c))||b.push(c);for(a=0;a "+b}))}a=a.replace(og,function(a,b,c){return'[dir="'+c+'"] '+b+", "+b+'[dir="'+c+'"]'});return{value:a,Sa:b,stop:f}}function mg(a,b){a=a.split(pg);a[0]+=b;return a.join(pg)} +function lg(a,b){var c=a.match(qg);return(c=c&&c[2].trim()||"")?c[0].match(rg)?a.replace(qg,function(a,c,f){return b+f}):c.split(rg)[0]===b?c:sg:a.replace(hg,b)}function tg(a){a.selector===ug&&(a.selector="html")}Tf.prototype.c=function(a){return a.match(kg)?this.b(a,vg):mg(a.trim(),vg)};aa.Object.defineProperties(Tf.prototype,{a:{configurable:!0,enumerable:!0,get:function(){return"style-scope"}}}); +var fg=/:(nth[-\w]+)\(([^)]+)\)/,vg=":not(.style-scope)",dg=",",ig=/(^|[\s>+~]+)((?:\[.+?\]|[^\s>+~=[])+)/g,rg=/[[.:#*]/,hg=":host",ug=":root",kg="::slotted",gg=new RegExp("^("+kg+")"),qg=/(:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/,ng=/(?:::slotted)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/,og=/(.*):dir\((?:(ltr|rtl))\)/,bg=".",pg=":",Yf="class",sg="should_not_match",Vf=new Tf;function wg(a,b,c,d){this.K=a||null;this.b=b||null;this.sa=c||[];this.T=null;this.X=d||"";this.a=this.H=this.O=null}function xg(a){return a?a.__styleInfo:null}function yg(a,b){return a.__styleInfo=b}wg.prototype.c=function(){return this.K};wg.prototype._getStyleRules=wg.prototype.c;function zg(a){var b=this.matches||this.matchesSelector||this.mozMatchesSelector||this.msMatchesSelector||this.oMatchesSelector||this.webkitMatchesSelector;return b&&b.call(this,a)}var Ag=navigator.userAgent.match("Trident");function Bg(){}function Cg(a){var b={},c=[],d=0;Kf(a,function(a){Dg(a);a.index=d++;a=a.B.cssText;for(var c;c=Ef.exec(a);){var e=c[1];":"!==c[2]&&(b[e]=!0)}},function(a){c.push(a)});a.b=c;a=[];for(var e in b)a.push(e);return a} +function Dg(a){if(!a.B){var b={},c={};Eg(a,c)&&(b.J=c,a.rules=null);b.cssText=a.parsedCssText.replace(Hf,"").replace(Cf,"");a.B=b}}function Eg(a,b){var c=a.B;if(c){if(c.J)return Object.assign(b,c.J),!0}else{c=a.parsedCssText;for(var d;a=Cf.exec(c);){d=(a[2]||a[3]).trim();if("inherit"!==d||"unset"!==d)b[a[1].trim()]=d;d=!0}return d}} +function Fg(a,b,c){b&&(b=0<=b.indexOf(";")?Gg(a,b,c):Qf(b,function(b,e,f,g){if(!e)return b+g;(e=Fg(a,c[e],c))&&"initial"!==e?"apply-shim-inherit"===e&&(e="inherit"):e=Fg(a,c[f]||f,c)||f;return b+(e||"")+g}));return b&&b.trim()||""} +function Gg(a,b,c){b=b.split(";");for(var d=0,e,f;d *"===f||"html"===f,h=0===f.indexOf(":host")&&!g;"shady"===c&&(g=f===e+" > *."+e||-1!==f.indexOf("html"),h=!g&&0===f.indexOf(e));"shadow"===c&&(g=":host > *"===f||"html"===f,h=h&&!g);if(g||h)c=e,h&&(b.G||(b.G=cg(Vf,b,Vf.b,a?bg+a:"",e)),c=b.G||e),d({Za:c,Wa:h,wb:g})}} +function Jg(a,b){var c={},d={},e=b&&b.__cssBuild;Kf(b,function(b){Ig(a,b,e,function(e){zg.call(a.kb||a,e.Za)&&(e.Wa?Eg(b,c):Eg(b,d))})},null,!0);return{Ya:d,Va:c}} +function Kg(a,b,c,d){var e=Sf(b),f=ag(e.is,e.X),g=new RegExp("(?:^|[^.#[:])"+(b.extends?"\\"+f.slice(0,-1)+"\\]":f)+"($|[.:[\\s>+~])");e=xg(b).K;var h=Lg(e,d);return Zf(b,e,function(b){var e="";b.B||Dg(b);b.B.cssText&&(e=Gg(a,b.B.cssText,c));b.cssText=e;if(!T&&!Mf(b)&&b.cssText){var k=e=b.cssText;null==b.za&&(b.za=Ff.test(e));if(b.za)if(null==b.ea){b.ea=[];for(var r in h)k=h[r],k=k(e),e!==k&&(e=k,b.ea.push(r))}else{for(r=0;r=m._useCount&&m.parentNode&&m.parentNode.removeChild(m));T?f.a?(f.a.textContent=e,d=f.a):e&&(d=Nf(e,h,a.shadowRoot,f.b)):d?d.parentNode|| +(Ag&&-1b&&-1==[34,35,60,62,63,96].indexOf(b)?a:encodeURIComponent(a)}function d(a){var b=a.charCodeAt(0);return 32b&&-1==[34,35,60,62,96].indexOf(b)?a:encodeURIComponent(a)}function e(a,e,g){function h(a){jb.push(a)}var k=e||"scheme start",v=0,p="",x=!1,U=!1,jb=[];a:for(;(void 0!=a[v-1]||0==v)&&!this.h;){var l=a[v];switch(k){case "scheme start":if(l&&r.test(l))p+= +l.toLowerCase(),k="scheme";else if(e){h("Invalid scheme.");break a}else{p="";k="no scheme";continue}break;case "scheme":if(l&&G.test(l))p+=l.toLowerCase();else if(":"==l){this.g=p;p="";if(e)break a;void 0!==m[this.g]&&(this.D=!0);k="file"==this.g?"relative":this.D&&g&&g.g==this.g?"relative or authority":this.D?"authority first slash":"scheme data"}else if(e){void 0!=l&&h("Code point not allowed in scheme: "+l);break a}else{p="";v=0;k="no scheme";continue}break;case "scheme data":"?"==l?(this.u="?", +k="query"):"#"==l?(this.C="#",k="fragment"):void 0!=l&&"\t"!=l&&"\n"!=l&&"\r"!=l&&(this.qa+=c(l));break;case "no scheme":if(g&&void 0!==m[g.g]){k="relative";continue}else h("Missing scheme."),f.call(this),this.h=!0;break;case "relative or authority":if("/"==l&&"/"==a[v+1])k="authority ignore slashes";else{h("Expected /, got: "+l);k="relative";continue}break;case "relative":this.D=!0;"file"!=this.g&&(this.g=g.g);if(void 0==l){this.i=g.i;this.s=g.s;this.j=g.j.slice();this.u=g.u;this.v=g.v;this.f=g.f; +break a}else if("/"==l||"\\"==l)"\\"==l&&h("\\ is an invalid code point."),k="relative slash";else if("?"==l)this.i=g.i,this.s=g.s,this.j=g.j.slice(),this.u="?",this.v=g.v,this.f=g.f,k="query";else if("#"==l)this.i=g.i,this.s=g.s,this.j=g.j.slice(),this.u=g.u,this.C="#",this.v=g.v,this.f=g.f,k="fragment";else{k=a[v+1];var F=a[v+2];if("file"!=this.g||!r.test(l)||":"!=k&&"|"!=k||void 0!=F&&"/"!=F&&"\\"!=F&&"?"!=F&&"#"!=F)this.i=g.i,this.s=g.s,this.v=g.v,this.f=g.f,this.j=g.j.slice(),this.j.pop();k= +"relative path";continue}break;case "relative slash":if("/"==l||"\\"==l)"\\"==l&&h("\\ is an invalid code point."),k="file"==this.g?"file host":"authority ignore slashes";else{"file"!=this.g&&(this.i=g.i,this.s=g.s,this.v=g.v,this.f=g.f);k="relative path";continue}break;case "authority first slash":if("/"==l)k="authority second slash";else{h("Expected '/', got: "+l);k="authority ignore slashes";continue}break;case "authority second slash":k="authority ignore slashes";if("/"!=l){h("Expected '/', got: "+ +l);continue}break;case "authority ignore slashes":if("/"!=l&&"\\"!=l){k="authority";continue}else h("Expected authority, got: "+l);break;case "authority":if("@"==l){x&&(h("@ already seen."),p+="%40");x=!0;for(l=0;l.\n var capturedCloneNode = Node.prototype.cloneNode;\n var capturedCreateElement = Document.prototype.createElement;\n var capturedImportNode = Document.prototype.importNode;\n var capturedRemoveChild = Node.prototype.removeChild;\n var capturedAppendChild = Node.prototype.appendChild;\n var capturedReplaceChild = Node.prototype.replaceChild;\n\n var elementQuerySelectorAll = Element.prototype.querySelectorAll;\n var docQuerySelectorAll = Document.prototype.querySelectorAll;\n var fragQuerySelectorAll = DocumentFragment.prototype.querySelectorAll;\n\n var scriptSelector = 'script:not([type]),script[type=\"application/javascript\"],script[type=\"text/javascript\"]';\n\n function QSA(node, selector) {\n // IE 11 throws a SyntaxError with `scriptSelector` if the node has no children due to the `:not([type])` syntax\n if (!node.childNodes.length) {\n return [];\n }\n switch (node.nodeType) {\n case Node.DOCUMENT_NODE:\n return docQuerySelectorAll.call(node, selector);\n case Node.DOCUMENT_FRAGMENT_NODE:\n return fragQuerySelectorAll.call(node, selector);\n default:\n return elementQuerySelectorAll.call(node, selector);\n }\n }\n\n // returns true if nested templates cannot be cloned (they cannot be on\n // some impl's like Safari 8 and Edge)\n // OR if cloning a document fragment does not result in a document fragment\n var needsCloning = (function() {\n if (!needsTemplate) {\n var t = document.createElement('template');\n var t2 = document.createElement('template');\n t2.content.appendChild(document.createElement('div'));\n t.content.appendChild(t2);\n var clone = t.cloneNode(true);\n return (clone.content.childNodes.length === 0 || clone.content.firstChild.content.childNodes.length === 0\n || brokenDocFragment);\n }\n })();\n\n var TEMPLATE_TAG = 'template';\n var PolyfilledHTMLTemplateElement = function() {};\n\n if (needsTemplate) {\n\n var contentDoc = document.implementation.createHTMLDocument('template');\n var canDecorate = true;\n\n var templateStyle = document.createElement('style');\n templateStyle.textContent = TEMPLATE_TAG + '{display:none;}';\n\n var head = document.head;\n head.insertBefore(templateStyle, head.firstElementChild);\n\n /**\n Provides a minimal shim for the