diff --git a/.babelrc b/.babelrc index add9366043..238ad86869 100644 --- a/.babelrc +++ b/.babelrc @@ -2,5 +2,9 @@ "presets": [ "env", "react" - ] + ], + "plugins": [ + 'transform-class-properties', + 'transform-object-rest-spread' + ] } diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..58abd8a2e6 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,92 @@ +module.exports = { + "env": { + "browser": true, + "commonjs": true, + "es6": true + }, + "extends": "eslint:recommended", + "parser": "babel-eslint", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": [ + "react" + ], + "settings": { + "react": { + "version": "16.4.1" + } + }, + "rules": { + "indent": [ + "error", + 2 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ], + + // Best practice + "eqeqeq": 2, + "curly": 2, + "dot-location": [2, "property"], + "dot-notation": 2, + "no-alert": 2, + + // Style + "array-bracket-spacing": 2, + "brace-style": 2, + "comma-dangle": 2, + "comma-spacing": 2, + "comma-style": 2, + "eol-last": 2, + "key-spacing": 2, + "keyword-spacing": 2, + "no-lonely-if": 2, + "no-multiple-empty-lines": 2, + "no-tabs": 2, + "semi-style": [2, "last"], + "spaced-comment": [2, "always"], + + // ES 6 + "arrow-spacing": 2, + "no-var": 2, + + // React + "jsx-quotes": [2, "prefer-single"], + "react/forbid-prop-types": [1, { "forbid": ["any"] }], + "react/jsx-equals-spacing": [2, "never"], + "react/jsx-indent": [2, 2], + "react/jsx-indent-props": [2, 2], + "react/jsx-key": 1, + "react/jsx-no-duplicate-props": 2, + "react/jsx-no-undef": 2, + "react/jsx-pascal-case": 2, + "react/jsx-uses-react": 1, + "react/jsx-uses-vars": 1, + "react/jsx-wrap-multilines": 0, + "react/no-children-prop": 2, + "react/no-direct-mutation-state": 1, + "react/no-typos": 2, + "react/no-unknown-property": 2, + "react/no-unused-prop-types": 2, + "react/prefer-es6-class": 1, + "react/prefer-stateless-function": 1, + "react/react-in-jsx-scope": 2, + "react/self-closing-comp": 2 + + } +}; diff --git a/.gitignore b/.gitignore index e724beeff2..79bd6ed445 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,8 @@ typings/ release dist -nuclear.json \ No newline at end of file +nuclear.json +bundle.electron.js + +# Prettier configuration file +.prettierrc \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 04539757e1..93c893449d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,34 @@ -language: node_js -node_js: - - 6 +matrix: + include: + - os: osx + osx_image: xcode9.2 + language: node_js + node_js: "8" + env: + - ELECTRON_CACHE=$HOME/.cache/electron + - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder + - ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=true + + - os: linux + language: node_js + node_js: "8" + +before_install: + - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install libdbus-1-dev -y; fi script: + - npm test - npm run build:dist + - | + if [ "$TRAVIS_OS_NAME" == "linux" ]; then + npm run build:electron:linux && npm run build:linux + else + npm run build:electron && npm run build:macos + fi + cache: directories: - - node_modules \ No newline at end of file + - node_modules + - $HOME/.cache/electron + - $HOME/.cache/electron-builder diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..df16452a6d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# Contributing to Nuclear + +Welcome, and thank you for your interest in contributing to Nuclear! + +There are many ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved. + +## Reporting Issues + +### Before reporting a new issue or feature request + +Have you identified a reproducible problem in nuclear? Have a feature request? We want to hear about it! Here's how you can make reporting your issue as effective as possible. + +Before you create a new issue, please do a search in [open issues](https://github.com/nukeop/nuclear/issues) to see if the issue or feature request has already been filed. + +If you find your issue already exists, make relevant comments and add your reaction. Use a reaction in place of a "+1" comment: + +* 👍 - upvote +* 👎 - downvote + +If you cannot find an existing issue that describes your bug or feature, create a new issue using the guidelines below. + +### Writing Good Bug Reports and Feature Requests + +File a single issue per problem and feature request. Do not enumerate multiple bugs or feature requests in the same issue. + +Do not add your issue as a comment to an existing issue unless it's for the identical input. Many issues look similar, but have different causes. + +The more information you can provide, the more likely someone will be successful at reproducing the issue and finding a fix. + +Please include the following with each issue: + +* Version of nuclear (written under the main logo) + +* Your operating system + +* Reproducible steps (1... 2... 3...) that cause the issue + +* What you expected to see, versus what you actually saw + +* Images, animations, or a link to a video showing the issue occurring + +* A code snippet that demonstrates the issue or a link to a code repository the developers can easily pull down to recreate the issue locally + + * **Note:** Because the developers need to copy and paste the code snippet, including a code snippet as a media file (i.e. .gif) is not sufficient. + + +## Contributing Fixes + +If you are interested in writing code to fix issues or add features, +please see How to [Contribute](https://github.com/nukeop/nuclear/wiki/Contributing) in the wiki. + +# Thank You! + +Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..8a23b18226 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node + +WORKDIR /usr/src/ +COPY . nuclear + +RUN apt-get update && apt-get install -y libnss3 libgtk-3-0 libx11-xcb1 libxss1 libasound2 + +WORKDIR nuclear +RUN npm install && npm run build:dist && npm run build:electron && npm run pack +RUN ls -a | grep -v release | xargs rm -rf || true + +CMD ["./release/linux-unpacked/nuclear"] diff --git a/LEGAL.md b/LEGAL.md new file mode 100644 index 0000000000..906d99d34a --- /dev/null +++ b/LEGAL.md @@ -0,0 +1,2 @@ +# Legal information +My lawyer tells me I am allowed to smack anyone saying this program is illegal with a flyswatter. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..dbbe355815 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index 8b857582da..6498151b0c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ -# nuclear [![Maintainability](https://api.codeclimate.com/v1/badges/a15c4888a63c900f6cc1/maintainability)](https://codeclimate.com/github/nukeop/nuclear/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/a15c4888a63c900f6cc1/test_coverage)](https://codeclimate.com/github/nukeop/nuclear/test_coverage) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/30750586202742279fa8958a12e519ed)](https://www.codacy.com/app/nukeop/nuclear?utm_source=github.com&utm_medium=referral&utm_content=nukeop/nuclear&utm_campaign=Badge_Grade) ![Travis](https://api.travis-ci.org/nukeop/nuclear.svg?branch=master) +# ![nuclear](https://i.imgur.com/oT1006i.png) -An Electron-based, multiplatform music player app that streams from multiple sources +[![Maintainability](https://api.codeclimate.com/v1/badges/a15c4888a63c900f6cc1/maintainability)](https://codeclimate.com/github/nukeop/nuclear/maintainability) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/30750586202742279fa8958a12e519ed)](https://www.codacy.com/app/nukeop/nuclear?utm_source=github.com&utm_medium=referral&utm_content=nukeop/nuclear&utm_campaign=Badge_Grade) ![Travis](https://api.travis-ci.org/nukeop/nuclear.svg?branch=master) + +Desktop music player focused on streaming from free sources + +[Official website](https://nuclear.gumblert.tech) + +[Mastodon](https://mstdn.io/@nuclear) + +[Twitter](https://twitter.com/nuclear_player) + +Support channel (Matrix): `#nuclear:matrix.org` ## What is this? nuclear is a free music streaming program that pulls content from free sources all over the internet. @@ -8,9 +18,13 @@ nuclear is a free music streaming program that pulls content from free sources a If you know [mps-youtube](https://github.com/mps-youtube/mps-youtube), this is a similar music player but with a GUI. It's also focusing more on audio. Imagine Spotify which you don't have to pay for and with a bigger library. -## Rewritten from scratch +## Pre-alpha release +The current version is a pre-alpha early access. Some of it is usable, some of it isn't. If there are things that don't work as expected or are counterintuitive, please open an issue so I can prioritize working on them. -This version of Nuclear has been rewritten from scratch and is being currently prepared for the 0.4.0 release. The code is completely new, much more maintainable and extensible. +## What if I am religiously opposed to using Electron for any and all purposes? +Then you are not the target audience of this program. See mps-youtube (link above) for a similar program that will not taint your machine with a library you happen to dislike. + +On an unrelated note, highly polarized opinions about languages and frameworks are characteristic of people who lack real-world programming experience and are more interested in building an identity than creating computer programs. ## Features @@ -21,62 +35,86 @@ This version of Nuclear has been rewritten from scratch and is being currently p - Song queue, which can be exported as a playlist - Loading saved playlists (stored in json files) - Scrobbling to last.fm (along with updating the 'now playing' status) +- Newest releases with reviews - tracks and albums +- Browsing by genre ## Planned features - Support for local files -- Browsing by genre - Browsing by popularity - Country-specific top lists -- Newest releases - Listening suggestions (similar artists, albums, tracks) - Unlimited downloads - Realtime lyrics - Locally stored library/favourites +## Manual and docs +https://nuclearmusic.rtfd.io/ + ## Community-maintained packages Here's a list of packages maintained by third parties. We would like to thank the maintainers for their work. We do not control these and cannot be held responsible for their contents, but if any of these appear suspicious to you, feel free to open an issue so we can reach out to the maintainers. -| Package type | Link | Maintainer | -|:--------------:|:--------------------------------------------------:|:-----------------------------:| -| AUR (Arch) | https://aur.archlinux.org/packages/nuclear-player/ | [mikelpint](https://github.com/mikelpint) | +| Package type | Link | Maintainer | +|:--------------:|:------------------------------------------------------:|:-----------------------------:| +| AUR (Arch) | https://aur.archlinux.org/packages/nuclear-player-bin/ | [mikelpint](https://github.com/mikelpint) | +| Choco (Win) | https://chocolatey.org/packages/nuclear/ | [JourneyOver](https://github.com/JourneyOver) | ## Screenshots This will be updated as the program evolves. -![album search](http://i.imgur.com/tLSv6pw.png) +![laptop mockup 1](https://i.imgur.com/31Tc5qf.jpg) + +![laptop mockup 2](https://i.imgur.com/HqMP5HF.jpg) -![album display](http://i.imgur.com/hAEXUaQ.png) +![album search](https://i.imgur.com/tLSv6pw.png) -![artist view](http://i.imgur.com/DCrlVqt.png) +![album display](https://i.imgur.com/hAEXUaQ.png) -![playlist view](http://i.imgur.com/YM3eP3i.png) +![artist view](https://i.imgur.com/DCrlVqt.png) + +![dashboard](https://i.imgur.com/tewcTEu.png) + +![playlist view](https://i.imgur.com/YM3eP3i.png) + +![genre view](https://i.imgur.com/KrzUvwp.png) ## Dev build process -To develop the project locally, you should only do: -``` -npm install -npm run watch -``` -This launches webpack. It watches local files for changes and rebuilds the project as needed. The project also has hot reload built in. -And in another terminal window: +Make sure you're using the latest version of Node and NPM. To develop the project locally, you should only do: +```bash +$ npm install +$ npm run watch ``` -npm run electron +This launches webpack. It watches local files for changes and rebuilds the project as needed. The project also has hot reload built in. And in another terminal window: +```bash +$ npm run electron:dev ``` - This launches a development version of the program. Tmux is very useful here so you can keep an eye on all running processes. -If you're getting a message about dbus being compiled with a different version of node when running the electron script, try the following: +--- +To run production version: + +```bash +$ npm run build:dist +$ npm run electron:prod ``` -npm install electron-rebuild -.node_modules/.bin/electron-rebuild +--- +To build for current operating system: +```bash +$ npm run build:dist +$ npm run build:electron +$ npm run pack ``` -And run the script again. +Instead of `pack` you can use `build:all` to build for all operating systems or `build:[system]` to build for a particular system (see package.json). + +In case of errors with dbus/mpris, try removing optional dependencies from package.json and node_modules. + +## License +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. #### Support on Beerpay diff --git a/app/App.js b/app/App.js index c162e3e0f8..92764bb59e 100644 --- a/app/App.js +++ b/app/App.js @@ -3,20 +3,26 @@ import FontAwesome from 'react-fontawesome'; import Sound from 'react-sound'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { Link, withRouter } from 'react-router-dom'; +import { NavLink, withRouter } from 'react-router-dom'; +import classnames from 'classnames'; +import _ from 'lodash'; import * as Actions from './actions'; import * as PlayerActions from './actions/player'; import * as PluginsActions from './actions/plugins'; import * as QueueActions from './actions/queue'; +import * as SettingsActions from './actions/settings'; import * as ScrobblingActions from './actions/scrobbling'; import './app.global.scss'; import styles from './styles.scss'; +import compact from './compact.scss'; import logoImg from '../resources/media/logo_full_light.png'; +import logoIcon from '../resources/media/512x512.png'; import artPlaceholder from '../resources/media/art_placeholder.png'; import { config as PluginConfig } from './plugins/config'; +import settingsConst from './constants/settings'; import Footer from './components/Footer'; import Navbar from './components/Navbar'; @@ -24,132 +30,302 @@ import VerticalPanel from './components/VerticalPanel'; import Spacer from './components/Spacer'; import MainContentContainer from './containers/MainContentContainer'; - import PlayQueueContainer from './containers/PlayQueueContainer'; - -import SearchBox from './components/SearchBox'; import SearchBoxContainer from './containers/SearchBoxContainer'; import IpcContainer from './containers/IpcContainer'; import SoundContainer from './containers/SoundContainer'; +import ToastContainer from './containers/ToastContainer'; +import ShortcutsContainer from './containers/ShortcutsContainer'; +import ErrorBoundary from './containers/ErrorBoundary'; -import Cover from './components/Cover'; +import ui from 'nuclear-ui'; +import NavButtons from './components/NavButtons'; import PlayerControls from './components/PlayerControls'; -import PlayQueue from './components/PlayQueue'; import Seekbar from './components/Seekbar'; import SidebarMenu from './components/SidebarMenu'; +import SidebarMenuItem from './components/SidebarMenu/SidebarMenuItem'; import TrackInfo from './components/TrackInfo'; import WindowControls from './components/WindowControls'; import VolumeControls from './components/VolumeControls'; class App extends React.Component { - togglePlayback() { - if(this.props.player.playbackStatus==Sound.status.PAUSED && - this.props.scrobbling.lastFmScrobblingEnabled && - this.props.scrobbling.lastFmSessionKey) { - let currentSong = this.props.queue.queueItems[this.props.queue.currentSong]; - this.props.actions.updateNowPlayingAction(currentSong.artist, currentSong.name, this.props.scrobbling.lastFmSessionKey); + togglePlayback () { + if ( + this.props.player.playbackStatus === Sound.status.PAUSED && + this.props.scrobbling.lastFmScrobblingEnabled && + this.props.scrobbling.lastFmSessionKey + ) { + let currentSong = this.props.queue.queueItems[ + this.props.queue.currentSong + ]; + this.props.actions.updateNowPlayingAction( + currentSong.artist, + currentSong.name, + this.props.scrobbling.lastFmSessionKey + ); } this.props.actions.togglePlayback(this.props.player.playbackStatus); } - nextSong() { + nextSong () { this.props.actions.nextSong(); - if( this.props.scrobbling.lastFmScrobblingEnabled && - this.props.scrobbling.lastFmSessionKey) { - let currentSong = this.props.queue.queueItems[this.props.queue.currentSong]; - this.props.actions.updateNowPlayingAction(currentSong.artist, currentSong.name, this.props.scrobbling.lastFmSessionKey); + if ( + this.props.scrobbling.lastFmScrobblingEnabled && + this.props.scrobbling.lastFmSessionKey + ) { + let currentSong = this.props.queue.queueItems[ + this.props.queue.currentSong + ]; + this.props.actions.updateNowPlayingAction( + currentSong.artist, + currentSong.name, + this.props.scrobbling.lastFmSessionKey + ); } } - componentWillMount() { - this.props.actions.lastFmReadSettings(); - this.props.actions.createSearchPlugins(PluginConfig.plugins); + renderNavBar () { + return ( + + + + + + {this.props.settings.framelessWindow && } + + ); } - render() { + renderRightPanel (settings) { return ( -
- - - - - - -
- - -
- -
Version 0.4.0
-
- - Dashboard - Downloads - Playlists - Settings - Search results -
-
- - - - - - -
-
- -
-
- - -
- + + + ); + } + renderSidebarMenu (settings, toggleOption) { + return ( + + +
+ - +
+ {settings.compactMenuBar ? '0.4.5' : 'Version 0.4.5'} +
-
- - + {this.renderNavLink('dashboard', 'dashboard', 'Dashboard', settings)} + {this.renderNavLink('downloads', 'download', 'Downloads', settings)} + {this.renderNavLink('playlists', 'music', 'Playlists', settings)} + {this.renderNavLink('lyrics', 'microphone', 'Lyrics', settings)} + {this.renderNavLink('plugins', 'flask', 'Plugins', settings)} + {this.renderNavLink('search', 'search', 'Search Results', settings)} + {this.renderNavLink('settings', 'cogs', 'Settings', settings)} + + {this.renderSidebarFooter(settings, toggleOption)} + + + ); + } + + renderNavLink (name, icon, prettyName, settings) { + return ( + + + {!settings.compactMenuBar && prettyName} + + + ); + } + renderSidebarFooter (settings, toggleOption) { + return ( + ); } + + renderFooter (settings) { + return ( +
+ +
+
+ {this.renderCover()} + {this.renderTrackInfo()} +
+ {this.renderPlayerControls()} + {this.renderVolumeControl(settings)} +
+
+ ); + } + + renderCover () { + return ( + + ); + } + + getCurrentSongParameter (parameter) { + return this.props.queue.queueItems[this.props.queue.currentSong] + ? this.props.queue.queueItems[this.props.queue.currentSong][parameter] + : null; + } + + renderTrackInfo () { + return ( + + ); + } + renderPlayerControls () { + const { player, queue } = this.props; + const couldPlay = queue.queueItems.length > 0; + const couldForward = queue.currentSong + 1 < queue.queueItems.length; + const couldBack = queue.currentSong > 0; + + return ( + + ); + } + + renderVolumeControl (settings) { + return ( + + ); + } + + renderNavbar () { + return ( + + + + + + + ); + } + + componentWillMount () { + this.props.actions.readSettings(); + this.props.actions.lastFmReadSettings(); + this.props.actions.createSearchPlugins(PluginConfig.plugins); + } + + render () { + let { settings } = this.props; + let { toggleOption } = this.props.actions; + return ( + + +
+ {this.renderNavBar()} +
+ {this.renderSidebarMenu(settings, toggleOption)} + + + + {this.renderRightPanel(settings)} +
+ {this.renderFooter(settings)} + + +
+
+ + +
+ ); + } } -function mapStateToProps(state) { +function mapStateToProps (state) { return { queue: state.queue, player: state.player, - scrobbling: state.scrobbling - } + scrobbling: state.scrobbling, + settings: state.settings + }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps (dispatch) { return { - actions: bindActionCreators(Object.assign({}, - ScrobblingActions, - QueueActions, - PlayerActions, - PluginsActions, - Actions - ), dispatch) + actions: bindActionCreators( + Object.assign( + {}, + ScrobblingActions, + SettingsActions, + QueueActions, + PlayerActions, + PluginsActions, + Actions + ), + dispatch + ) }; } -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App)); +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(App) +); diff --git a/app/actions/dashboard.js b/app/actions/dashboard.js index 6eacc41d38..c0ef51b013 100644 --- a/app/actions/dashboard.js +++ b/app/actions/dashboard.js @@ -1,7 +1,14 @@ -import { - getBestNewAlbums, - getBestNewTracks -} from 'pitchfork-bnm'; +import logger from 'electron-timber'; +import core from 'nuclear-core'; +import { getBestNewAlbums, getBestNewTracks } from 'pitchfork-bnm'; + +import globals from '../globals'; +import { getNewsIndex, getNewsItem } from '../rest/Nuclear'; + +const lastfm = new core.LastFmApi( + globals.lastfmApiKey, + globals.lastfmApiSecret +); export const LOAD_BEST_NEW_ALBUMS_START = 'LOAD_BEST_NEW_ALBUMS_START'; export const LOAD_BEST_NEW_ALBUMS_SUCCESS = 'LOAD_BEST_NEW_ALBUMS_SUCCESS'; @@ -11,6 +18,53 @@ export const LOAD_BEST_NEW_TRACKS_START = 'LOAD_BEST_NEW_TRACKS_START'; export const LOAD_BEST_NEW_TRACKS_SUCCESS = 'LOAD_BEST_NEW_TRACKS_SUCCESS'; export const LOAD_BEST_NEW_TRACKS_ERROR = 'LOAD_BEST_NEW_TRACKS_ERROR'; +export const LOAD_NUCLEAR_NEWS_START = 'LOAD_NUCLEAR_NEWS_START'; +export const LOAD_NUCLEAR_NEWS_SUCCESS = 'LOAD_NUCLEAR_NEWS_SUCCESS'; +export const LOAD_NUCLEAR_NEWS_ERROR = 'LOAD_NUCLEAR_NEWS_ERROR'; + +export const LOAD_TOP_TAGS_START = 'LOAD_TOP_TAGS_START'; +export const LOAD_TOP_TAGS_SUCCESS = 'LOAD_TOP_TAGS_SUCCESS'; +export const LOAD_TOP_TAGS_ERROR = 'LOAD_TOP_TAGS_ERROR'; + +export const LOAD_TOP_TRACKS_START = 'LOAD_TOP_TRACKS_START'; +export const LOAD_TOP_TRACKS_SUCCESS = 'LOAD_TOP_TRACKS_SUCCESS'; +export const LOAD_TOP_TRACKS_ERROR = 'LOAD_TOP_TRACKS_ERROR'; + +export function loadTopTagsStart() { + return { + type: LOAD_TOP_TAGS_START + }; +} + +export function loadTopTagsSuccess(tags) { + return { + type: LOAD_TOP_TAGS_SUCCESS, + payload: tags + }; +} + +export function loadTopTagsError() { + return { + type: LOAD_TOP_TAGS_ERROR + }; +} + +export function loadTopTags() { + return dispatch => { + dispatch(loadTopTagsStart()); + lastfm + .getTopTags() + .then(response => response.json()) + .then(results => { + dispatch(loadTopTagsSuccess(results.toptags.tag)); + }) + .catch(error => { + dispatch(loadTopTagsError(error)); + logger.error(error); + }); + }; +} + export function loadBestNewAlbumsStart() { return { type: LOAD_BEST_NEW_ALBUMS_START @@ -33,15 +87,15 @@ export function loadBestNewAlbumsError() { export function loadBestNewAlbums() { return dispatch => { dispatch(loadBestNewAlbumsStart()); - getBestNewAlbums(). - then(albums => { - dispatch(loadBestNewAlbumsSuccess(albums)); + getBestNewAlbums() + .then(albums => { + dispatch(loadBestNewAlbumsSuccess(albums)); }) .catch(error => { - dispatch(loadBestNewAlbumsError()); - console.error(error); + dispatch(loadBestNewAlbumsError()); + logger.error(error); }); - }; + }; } export function loadBestNewTracksStart() { @@ -66,13 +120,87 @@ export function loadBestNewTracksError() { export function loadBestNewTracks() { return dispatch => { dispatch(loadBestNewAlbumsStart()); - getBestNewTracks(). - then(tracks => { - dispatch(loadBestNewTracksSuccess(tracks)); + getBestNewTracks() + .then(tracks => { + dispatch(loadBestNewTracksSuccess(tracks)); + }) + .catch(error => { + dispatch(loadBestNewTracksError()); + logger.error(error); + }); + }; +} + +export function loadNuclearNewsStart() { + return { + type: LOAD_NUCLEAR_NEWS_START + }; +} + +export function loadNuclearNewsSuccess(news) { + return { + type: LOAD_NUCLEAR_NEWS_SUCCESS, + payload: news + }; +} + +export function loadNuclearNewsError() { + return { + type: LOAD_NUCLEAR_NEWS_ERROR + }; +} + +export function loadNuclearNews() { + return dispatch => { + dispatch(loadNuclearNewsStart()); + getNewsIndex() + .then(index => { + return Promise.all( + index.articles.map((item, i) => { + return getNewsItem(item); + }) + ); + }) + .then(articles => { + dispatch(loadNuclearNewsSuccess(articles)); + }) + .catch(err => { + dispatch(loadNuclearNewsError(err)); + }); + }; +} + +export function loadTopTracksStart() { + return { + type: LOAD_TOP_TRACKS_START + }; +} + +export function loadTopTracksSuccess(tracks) { + return { + type: LOAD_TOP_TRACKS_SUCCESS, + payload: tracks + }; +} + +export function loadTopTracksError() { + return { + type: LOAD_TOP_TRACKS_ERROR + }; +} + +export function loadTopTracks() { + return dispatch => { + dispatch(loadTopTracksStart()); + lastfm + .getTopTracks() + .then(tracks => tracks.json()) + .then(tracksJson => { + dispatch(loadTopTracksSuccess(tracksJson.tracks.track)); }) .catch(error => { - dispatch(loadBestNewTracksError()); - console.error(error); + dispatch(loadTopTracksError()); + logger.error(error); }); }; } diff --git a/app/actions/index.js b/app/actions/index.js index bb728c8a1a..e70a91d1f7 100644 --- a/app/actions/index.js +++ b/app/actions/index.js @@ -1,8 +1,12 @@ -const mb = require('../rest/Musicbrainz'); +import logger from 'electron-timber'; +import core from 'nuclear-core'; +import _ from 'lodash'; +import globals from '../globals'; + const discogs = require('../rest/Discogs'); -const lastfm = require('../rest/Lastfm'); +const youtube = require('../rest/Youtube'); -const _ = require('lodash'); +let lastfm = new core.LastFmApi(globals.lastfmApiKey, globals.lastfmApiSecret); export const UNIFIED_SEARCH_START = 'UNIFIED_SEARCH_START'; export const UNIFIED_SEARCH_SUCCESS = 'UNIFIED_SEARCH_SUCCESS'; @@ -20,205 +24,344 @@ export const ARTIST_INFO_SEARCH_SUCCESS = 'ARTIST_INFO_SEARCH_SUCCESS'; export const ARTIST_RELEASES_SEARCH_START = 'ARTIST_RELEASES_SEARCH_START'; export const ARTIST_RELEASES_SEARCH_SUCCESS = 'ARTIST_RELEASES_SEARCH_SUCCESS'; -export const LASTFM_ARTIST_INFO_SEARCH_START = 'LASTFM_ARTIST_INFO_SEARCH_START'; -export const LASTFM_ARTIST_INFO_SEARCH_SUCCESS = 'LASTFM_ARTIST_INFO_SEARCH_SUCCESS'; +export const LASTFM_ARTIST_INFO_SEARCH_START = + 'LASTFM_ARTIST_INFO_SEARCH_START'; +export const LASTFM_ARTIST_INFO_SEARCH_SUCCESS = + 'LASTFM_ARTIST_INFO_SEARCH_SUCCESS'; + +export const LASTFM_TRACK_SEARCH_START = 'LASTFM_TRACK_SEARCH_START'; +export const LASTFM_TRACK_SEARCH_SUCCESS = 'LASTFM_TRACK_SEARCH_SUCCESS'; + +export const YOUTUBE_PLAYLIST_SEARCH_START = 'YOUTUBE_PLAYLIST_SEARCH_START'; +export const YOUTUBE_PLAYLIST_SEARCH_SUCCESS = 'YOUTUBE_PLAYLIST_SEARCH_SUCCESS'; -export function sourcesSearch(terms, plugins) { - var searchResults = {}; - for(var i=0; i { - return discogs.searchReleases(terms) - .then(searchResults => searchResults.json()) - .then(searchResultsJson => { - dispatch({ - type: ALBUM_SEARCH_SUCCESS, - payload: searchResultsJson.results +export function unifiedSearchError () { + return { + type: UNIFIED_SEARCH_ERROR + }; +} + +function discogsSearch (terms, searchType, dispatchType) { + return dispatch => { + return discogs.search(terms, searchType) + .then(searchResults => searchResults.json()) + .then(searchResultsJson => { + dispatch({ + type: dispatchType, + + payload: searchResultsJson.results + }); + }) + .catch(error => { + logger.error(error); }); - }); - } + }; +} + +export function albumSearch (terms) { + return discogsSearch(terms, 'master', 'ALBUM_SEARCH_SUCCESS'); +} + +export function artistSearch (terms) { + return discogsSearch(terms, 'artist', 'ARTIST_SEARCH_SUCCESS'); +} + +export function lastFmTrackSearchStart (terms) { + return { + type: LASTFM_TRACK_SEARCH_START, + payload: terms + }; } -export function artistSearch(terms) { - return (dispatch) => { - return discogs.searchArtists(terms) - .then(searchResults => searchResults.json()) - .then(searchResultsJson => { - dispatch({ - type: ARTIST_SEARCH_SUCCESS, - payload: searchResultsJson.results +export function lastFmTrackSearchSuccess (terms, searchResults) { + return { + type: LASTFM_TRACK_SEARCH_SUCCESS, + payload: { + id: terms, + info: searchResults + } + }; +} + +export function lastFmTrackSearch (terms) { + return dispatch => { + dispatch(lastFmTrackSearchStart(terms)); + Promise.all([lastfm.searchTracks(terms)]) + .then(results => Promise.all(results.map(info => info.json()))) + .then(results => { + dispatch( + lastFmTrackSearchSuccess(terms, _.get(results[0], 'results.trackmatches.track', [])) + ); + }) + .catch(error => { + logger.error(error); }); - }); - } + }; +} + +export function youtubePlaylistSearchStart (terms) { + return { + type: YOUTUBE_PLAYLIST_SEARCH_START, + payload: terms + }; } -export function unifiedSearch(terms) { - return (dispatch) => { +export function youtubePlaylistSearchSuccess (terms, results) { + return { + type: YOUTUBE_PLAYLIST_SEARCH_SUCCESS, + payload: { + id: terms, + info: results + } + }; +} + +export function youtubePlaylistSearch (terms) { + return dispatch => { + dispatch(youtubePlaylistSearchStart(terms)); + youtube.urlSearch(terms) + .then(results => { + dispatch( + youtubePlaylistSearchSuccess(terms, results) + ); + }) + .catch(error => { + logger.error(error); + }); + }; +} + +export function unifiedSearch (terms, history) { + return dispatch => { dispatch(unifiedSearchStart()); + Promise.all([ dispatch(albumSearch(terms)), - dispatch(artistSearch(terms)) - ]).then(() => { - dispatch(unifiedSearchSuccess()); - }); + dispatch(artistSearch(terms)), + dispatch(lastFmTrackSearch(terms)), + dispatch(youtubePlaylistSearch(terms)) + ]) + .then(() => { + dispatch(unifiedSearchSuccess()); + if (history.location.pathname !== '/search') { + history.push('/search'); + } + }) + .catch(error => { + logger.error(error); + dispatch(unifiedSearchError()); + }); }; } -export function albumInfoStart(albumId) { +export function albumInfoStart (albumId) { return { type: ALBUM_INFO_SEARCH_START, payload: albumId - } + }; } -export function albumInfoSuccess(albumId, info) { +export function albumInfoSuccess (albumId, info) { return { type: ALBUM_INFO_SEARCH_SUCCESS, payload: { id: albumId, info: info } - } + }; } -export function albumInfoSearch(albumId) { - return (dispatch) => { +export function albumInfoSearch (albumId, releaseType) { + return dispatch => { dispatch(albumInfoStart(albumId)); - discogs.releaseInfo(albumId) - .then (info => info.json()) - .then (albumInfo => { - dispatch(albumInfoSuccess(albumId, albumInfo)); - }); + discogs + .releaseInfo(albumId, releaseType) + .then(info => { + if (info.ok) { + return info.json(); + } else { + throw `Error fetching album data from Discogs for id ${albumId}`; + } + }) + .then(albumInfo => { + dispatch(albumInfoSuccess(albumId, albumInfo)); + }) + .catch(error => { + logger.error(error); + }); }; } -export function artistInfoStart(artistId) { +export function artistInfoStart (artistId) { return { type: ARTIST_INFO_SEARCH_START, payload: artistId - } + }; } -export function artistInfoSuccess(artistId, info) { +export function artistInfoSuccess (artistId, info) { return { type: ARTIST_INFO_SEARCH_SUCCESS, payload: { id: artistId, info: info } - } + }; } -export function artistInfoSearch(artistId) { - return (dispatch) => { +export function artistInfoSearch (artistId) { + return dispatch => { dispatch(artistInfoStart(artistId)); - discogs.artistInfo(artistId) - .then (info => info.json()) - .then (artistInfo => { - dispatch(artistInfoSuccess(artistId, artistInfo)); - dispatch(lastFmArtistInfoSearch(artistInfo.name, artistId)); - }); + discogs + .artistInfo(artistId) + .then(info => info.json()) + .then(artistInfo => { + dispatch(artistInfoSuccess(artistId, artistInfo)); + dispatch(lastFmArtistInfoSearch(artistInfo.name, artistId)); + }) + .catch(error => { + logger.error(error); + }); }; } -export function artistReleasesStart(artistId) { +export function artistReleasesStart (artistId) { return { type: ARTIST_RELEASES_SEARCH_START, payload: artistId - } + }; } -export function artistReleasesSuccess(artistId, releases) { +export function artistReleasesSuccess (artistId, releases) { return { type: ARTIST_RELEASES_SEARCH_SUCCESS, payload: { id: artistId, releases: releases } - } + }; } -export function artistReleasesSearch(artistId) { - return (dispatch) => { +export function artistReleasesSearch (artistId) { + return dispatch => { dispatch(artistReleasesStart(artistId)); - discogs.artistReleases(artistId) - .then (releases => releases.json()) - .then (releases => { - dispatch(artistReleasesSuccess(artistId, releases)); - }); + discogs + .artistReleases(artistId) + .then(releases => releases.json()) + .then(releases => { + dispatch(artistReleasesSuccess(artistId, releases)); + }) + .catch(error => { + logger.error(error); + }); }; } -export function artistInfoSearchByName(artistName, history) { - return (dispatch) => { - - discogs.searchArtists(artistName) - .then(searchResults => searchResults.json()) - .then(searchResultsJson => { +export function artistInfoSearchByName (artistName, history) { + return dispatch => { + discogs + .search(artistName, 'artists') + .then(searchResults => searchResults.json()) + .then(searchResultsJson => { let artist = searchResultsJson.results[0]; if (history) { history.push('/artist/' + artist.id); } dispatch(artistInfoSearch(artist.id)); - }); - } + }) + .catch(error => { + logger.error(error); + }); + }; +} + +export function albumInfoSearchByName (albumName, history) { + return dispatch => { + discogs + .search(albumName, 'albums') + .then(searchResults => searchResults.json()) + .then(searchResultsJson => { + let album = searchResultsJson.results[0]; + if (album.type == 'artist') { + dispatch(lastFmArtistInfoSearch(album.title, album.id)); + if (history) { + history.push('/artist/' + album.id); + } + } else { + dispatch(albumInfoSearch(album.id, album.type)); + if (history) { + history.push('/album/' + album.id); + } + } + + }) + .catch(error => { + logger.error(error); + }); + }; } -export function lastFmArtistInfoStart(artistId) { +export function lastFmArtistInfoStart (artistId) { return { type: LASTFM_ARTIST_INFO_SEARCH_START, payload: artistId - } + }; } -export function lastFmArtistInfoSuccess(artistId, info) { +export function lastFmArtistInfoSuccess (artistId, info) { return { type: LASTFM_ARTIST_INFO_SEARCH_SUCCESS, payload: { id: artistId, info: info } - } + }; } -export function lastFmArtistInfoSearch(artist, artistId) { +export function lastFmArtistInfoSearch (artist, artistId) { return dispatch => { dispatch(lastFmArtistInfoStart(artistId)); Promise.all([ lastfm.getArtistInfo(artist), lastfm.getArtistTopTracks(artist) ]) - .then (results => Promise.all(results.map(info => info.json()))) - .then (results => { - let info = {}; - results.forEach(result => { - info = Object.assign(info, result); - }); + .then(results => Promise.all(results.map(info => info.json()))) + .then(results => { + let info = {}; + results.forEach(result => { + info = Object.assign(info, result); + }); - dispatch(lastFmArtistInfoSuccess(artistId, info)); - }); + dispatch(lastFmArtistInfoSuccess(artistId, info)); + }) + .catch(error => { + logger.error(error); + }); }; } diff --git a/app/actions/lyrics.js b/app/actions/lyrics.js new file mode 100644 index 0000000000..a9d911bcca --- /dev/null +++ b/app/actions/lyrics.js @@ -0,0 +1,34 @@ +import * as LyricsSearch from '../plugins/Lyrics'; +import logger from 'electron-timber'; +export const LYRIC_SEARCH_START = 'LYRIC_SEARCH_START'; +export const LYRIC_SEARCH_SUCCESS = 'LYRIC_SEARCH_SUCCESS'; + +export function lyricSearchStart (query) { + return { + type: LYRIC_SEARCH_START, + payload: query + }; +} + +export function lyricSearchSuccess (query, result) { + return { + type: LYRIC_SEARCH_SUCCESS, + payload: { + type: query, + info: result + } + }; +} + +export function lyricsSearch (track) { + return dispatch => { + dispatch(lyricSearchStart(track)); + LyricsSearch.search(track.artist, track.name) + .then(results => { + dispatch(lyricSearchSuccess(results)); + }) + .catch(error => { + logger.error(error); + }); + }; +} diff --git a/app/actions/player.js b/app/actions/player.js index 436d40b5e1..30daa78b21 100644 --- a/app/actions/player.js +++ b/app/actions/player.js @@ -1,47 +1,95 @@ import Sound from 'react-sound'; +import { sendPaused, sendPlay, sendVolume, sendPlaybackProgress, sendSeek } from '../mpris'; export const START_PLAYBACK = 'START_PLAYBACK'; export const PAUSE_PLAYBACK = 'PAUSE_PLAYBACK'; export const UPDATE_PLAYBACK_PROGRESS = 'UPDATE_PLAYBACK_PROGRESS'; export const UPDATE_SEEK = 'UPDATE_SEEK'; - -export function togglePlayback(currentState) { - return dispatch => { - if (currentState == Sound.status.PLAYING) { - dispatch(pausePlayback()); - } else { - dispatch(startPlayback()); - } - }; -} +export const UPDATE_VOLUME = 'UPDATE_VOLUME'; +export const MUTE = 'MUTE'; +export const UNMUTE = 'UNMUTE'; +export const UPDATE_PLAYBACK_STREAM_LOADING = 'UPDATE_PLAYBACK_STREAM_LOADING'; export function startPlayback() { + sendPlay(); return { type: START_PLAYBACK, payload: null - } + }; } export function pausePlayback() { + sendPaused(); return { type: PAUSE_PLAYBACK, payload: null - } + }; +} + +export function togglePlayback(currentState) { + return dispatch => { + if (currentState === Sound.status.PLAYING) { + dispatch(pausePlayback()); + } else { + dispatch(startPlayback()); + } + }; } export function updatePlaybackProgress(progress, seek) { + sendPlaybackProgress(progress, seek); return { type: UPDATE_PLAYBACK_PROGRESS, payload: { - progress, + progress, seek } - } + }; } export function updateSeek(seek) { + sendSeek(seek); return { type: UPDATE_SEEK, payload: seek - } -} \ No newline at end of file + }; +} + +export function updateVolume(volume) { + sendVolume(volume); + return { + type: UPDATE_VOLUME, + payload: volume + }; +} + +export function mute(){ + return { + type: MUTE, + payload: null + }; +} + +export function unMute(){ + return { + type: UNMUTE, + payload: null + }; +} + +export function toggleMute(muted) { + return dispatch => { + if (muted){ + dispatch(mute()); + } else { + dispatch(unMute()); + } + }; +} + +export function updateStreamLoading(state) { + return { + type: UPDATE_PLAYBACK_STREAM_LOADING, + payload: state + }; +} diff --git a/app/actions/playlists.js b/app/actions/playlists.js index d7c2d0a7e7..021d3916d4 100644 --- a/app/actions/playlists.js +++ b/app/actions/playlists.js @@ -5,12 +5,12 @@ export const ADD_PLAYLIST = 'ADD_PLAYLIST'; export function addPlaylist(tracks, name) { return dispatch => { - let playlists = store.get('playlists').value(); + let playlists = store.get('playlists') || {}; let playlist = {name, tracks}; if (tracks.length === 0) { dispatch({ - type: null + type: null }); return; } @@ -21,7 +21,7 @@ export function addPlaylist(tracks, name) { playlists = [playlist]; } - store.set('playlists', playlists).write(); + store.set('playlists', playlists); dispatch({ type: ADD_PLAYLIST, payload: playlists @@ -31,17 +31,17 @@ export function addPlaylist(tracks, name) { export function loadPlaylists() { return dispatch => { - let playlists = store.get('playlists').value(); + let playlists = store.get('playlists') || {}; if (playlists) { dispatch({ - type: LOAD_PLAYLISTS, - payload: playlists + type: LOAD_PLAYLISTS, + payload: playlists }); } else { dispatch({ - type: LOAD_PLAYLISTS, - payload: [] + type: LOAD_PLAYLISTS, + payload: [] }); } }; diff --git a/app/actions/plugins.js b/app/actions/plugins.js index 6136970742..074fbdf9cb 100644 --- a/app/actions/plugins.js +++ b/app/actions/plugins.js @@ -1,20 +1,21 @@ export const CREATE_PLUGINS = 'CREATE_PLUGINS'; +export const SELECT_DEFAULT_MUSIC_SOURCE = 'SELECT_DEFAULT_MUSIC_SOURCE'; import config from '../plugins/config'; -export function createSearchPlugins(pluginClasses) { - var plugins = {}; +export function createSearchPlugins (pluginClasses) { + let plugins = {}; - for ( var i=0; i { +function addTrackToQueue (musicSources, item) { + return dispatch => { item.loading = true; + item.uuid = uuidv4(); dispatch({ type: ADD_TO_QUEUE, payload: item }); - - Promise.all(_.map(musicSources, m => m.search(item.artist + ' ' + item.name))) + Promise.all(_.map(musicSources, m => m.search({ artist: item.artist, track: item.name }))) .then(results => Promise.all(results)) .then(results => { - dispatch({ + _.pull(results, null); + dispatch({ type: ADD_STREAMS_TO_QUEUE_ITEM, - payload: Object.assign({}, item, {loading: false, streams: results}) - }); + payload: Object.assign({}, item, { streams: results, loading: false }) + }); }); }; } -export function addPlaylistTracksToQueue(musicSources, tracks) { - return (dispatch) => { - tracks.map((track, i) => { - dispatch({ - type: ADD_TO_QUEUE, - payload: track - }); +export function addToQueue (musicSources, item) { + return addTrackToQueue(musicSources, item); +} - - Promise.all(_.map(musicSources, m => m.search(track.artist + ' ' + track.name))) - .then(results => Promise.all(results)) - .then(results => { - let item = track; - dispatch({ - type: ADD_STREAMS_TO_QUEUE_ITEM, - payload: Object.assign({}, item, {streams: results}) - }); - }); +export function removeFromQueue (item) { + return { + type: REMOVE_FROM_QUEUE, + payload: item + }; +} + +export function addPlaylistTracksToQueue (musicSources, tracks) { + return dispatch => { + tracks.map((item, i) => { + dispatch(addTrackToQueue(musicSources, item)); }); + }; +} +export function rerollTrack (musicSource, selectedStream, track) { + return dispatch => { + musicSource.getAlternateStream({ artist: track.artist, track: track.name }, selectedStream).then(newStream => { + let streams = _.map(track.streams, stream => { + return stream.source === newStream.source ? newStream : stream; + }); + dispatch({ + type: REPLACE_STREAMS_IN_QUEUE_ITEM, + payload: Object.assign({}, track, { streams }) + }); + }); }; } -export function clearQueue() { +export function clearQueue () { return { type: CLEAR_QUEUE, payload: null }; } -export function nextSong() { +export function nextSong () { return { type: NEXT_SONG, payload: null }; } -export function previousSong() { +export function previousSong () { return { type: PREVIOUS_SONG, payload: null }; } -export function selectSong(index) { +export function selectSong (index) { return { type: SELECT_SONG, payload: index - } + }; +} + +export function swapSongs (itemFrom, itemTo) { + return { + type: SWAP_SONGS, + payload: { + itemFrom, + itemTo + } + }; } diff --git a/app/actions/scrobbling.js b/app/actions/scrobbling.js index f5a490d3b2..3d6b01f064 100644 --- a/app/actions/scrobbling.js +++ b/app/actions/scrobbling.js @@ -1,12 +1,8 @@ import { store } from '../persistence/store'; -import { - lastFmLoginConnect, - lastFmLogin, - scrobble, - updateNowPlaying -} from '../rest/Lastfm'; +import core from 'nuclear-core'; import globals from '../globals'; const electron = window.require('electron'); +const lastfm = new core.LastFmApi(globals.lastfmApiKey, globals.lastfmApiSecret); export const LASTFM_CONNECT = 'LASTFM_CONNECT'; export const LASTFM_LOGIN = 'LASTFM_LOGIN'; @@ -19,107 +15,107 @@ export const LASTFM_UPDATE_NOW_PLAYING = 'LASTFM_UPDATE_NOW_PLAYING'; export function lastFmReadSettings() { return dispatch => { - let settings = store.get('lastFm').value(); + let settings = store.get('lastFm') || {}; if (settings) { - dispatch({ - type: LASTFM_READ_SETTINGS, - payload: { - lastFmName: settings.lastFmName, - lastFmAuthToken: settings.lastFmAuthToken, - lastFmSessionKey: settings.lastFmSessionKey, - lastFmScrobblingEnabled: settings.lastFmScrobblingEnabled - } - }); + dispatch({ + type: LASTFM_READ_SETTINGS, + payload: { + lastFmName: settings.lastFmName, + lastFmAuthToken: settings.lastFmAuthToken, + lastFmSessionKey: settings.lastFmSessionKey, + lastFmScrobblingEnabled: settings.lastFmScrobblingEnabled + } + }); } else { dispatch({ type: LASTFM_READ_SETTINGS, payload: null }); } - } + }; } export function lastFmConnectAction() { return dispatch => { - lastFmLoginConnect() - .then(response => response.json()) - .then(response => { - let authToken = response.token; - electron.shell.openExternal( - 'http://www.last.fm/api/auth/?api_key=' + globals.lastfmApiKey + '&token=' + authToken - ); + lastfm.lastFmLoginConnect() + .then(response => response.json()) + .then(response => { + let authToken = response.token; + electron.shell.openExternal( + 'http://www.last.fm/api/auth/?api_key=' + globals.lastfmApiKey + '&token=' + authToken + ); - store.set('lastFm.lastFmAuthToken', authToken).write(); + store.set('lastFm.lastFmAuthToken', authToken); - dispatch({ - type: LASTFM_CONNECT, - payload: authToken + dispatch({ + type: LASTFM_CONNECT, + payload: authToken + }); }); - }); }; } export function lastFmLoginAction(authToken) { return dispatch => { - lastFmLogin(authToken) - .then(response => response.json()) - .then(response => { + lastfm.lastFmLogin(authToken) + .then(response => response.json()) + .then(response => { - let sessionKey = response.session.key; - let sessionName = response.session.name; + let sessionKey = response.session.key; + let sessionName = response.session.name; - store.set('lastFm.lastFmName', sessionName).write(); - store.set('lastFm.lastFmSessionKey', sessionKey).write(); + store.set('lastFm.lastFmName', sessionName); + store.set('lastFm.lastFmSessionKey', sessionKey); - dispatch({ - type: LASTFM_LOGIN, - payload: { - sessionKey: sessionKey, - name: sessionName - } + dispatch({ + type: LASTFM_LOGIN, + payload: { + sessionKey: sessionKey, + name: sessionName + } + }); }); - }); }; } export function enableScrobbling() { - store.set('lastFm.lastFmScrobblingEnabled', true).write(); + store.set('lastFm.lastFmScrobblingEnabled', true); return { type: LASTFM_ENABLE_SCROBBLING, payload: null - } + }; } export function disableScrobbling() { - store.set('lastFm.lastFmScrobblingEnabled', false).write(); - + store.set('lastFm.lastFmScrobblingEnabled', false); + return { type: LASTFM_DISABLE_SCROBBLING, payload: null - } + }; } export function scrobbleAction(artist, track, session) { return dispatch => { - scrobble(artist, track, session) - .then(response => { - dispatch({ - type: LASTFM_SCROBBLE, - payload: null + lastfm.scrobble(artist, track, session) + .then(response => { + dispatch({ + type: LASTFM_SCROBBLE, + payload: null + }); }); - }); - } + }; } export function updateNowPlayingAction(artist, track, session) { return dispatch => { - updateNowPlaying(artist, track, session) - .then(response => { - dispatch({ - type: LASTFM_UPDATE_NOW_PLAYING, - payload: null + lastfm.updateNowPlaying(artist, track, session) + .then(response => { + dispatch({ + type: LASTFM_UPDATE_NOW_PLAYING, + payload: null + }); }); - }); - } + }; } diff --git a/app/actions/settings.js b/app/actions/settings.js new file mode 100644 index 0000000000..a81068c0b2 --- /dev/null +++ b/app/actions/settings.js @@ -0,0 +1,48 @@ +import { store, getOption, setOption } from '../persistence/store'; + +export const READ_SETTINGS = 'READ_SETTINGS'; +export const SET_BOOLEAN_OPTION = 'SET_BOOLEAN_OPTION'; +export const SET_STRING_OPTION = 'SET_STRING_OPTION'; +export const SET_NUMBER_OPTION = 'SET_NUMBER_OPTION'; + +export function readSettings() { + let settings = store.get('settings'); + return { + type: READ_SETTINGS, + payload: settings + }; +} + +export function setBooleanOption(option, state) { + setOption(option, state); + + return { + type: SET_BOOLEAN_OPTION, + payload: {option, state} + }; +} + +export function setStringOption(option, state) { + setOption(option, state); + + return { + type: SET_STRING_OPTION, + payload: {option, state} + }; +} + +export function setNumberOption(option, state) { + setOption(option, state); + + return { + type: SET_NUMBER_OPTION, + payload: {option, state} + }; +} + +export function toggleOption(option, state) { + let optionState = state[option.name]; + return optionState !==undefined + ? setBooleanOption(option.name, !optionState) + : setBooleanOption(option.name, !option.default); +} diff --git a/app/actions/tag.js b/app/actions/tag.js new file mode 100644 index 0000000000..1f8416f5dd --- /dev/null +++ b/app/actions/tag.js @@ -0,0 +1,52 @@ +import core from 'nuclear-core'; +import globals from '../globals'; + +export const LOAD_TAG_INFO_START = 'LOAD_TAG_INFO_START'; +export const LOAD_TAG_INFO_SUCCESS = 'LOAD_TAG_INFO_SUCCESS'; +export const LOAD_TAG_INFO_ERROR = 'LOAD_TAG_INFO_ERROR'; +const lastfm = new core.LastFmApi(globals.lastfmApiKey, globals.lastfmApiSecret); + +export function loadTagInfoStart(tag) { + return { + type: LOAD_TAG_INFO_START, + payload: tag + }; +} + +export function loadTagInfoSuccess(tag, data) { + return { + type: LOAD_TAG_INFO_SUCCESS, + payload: { + tag, + data + } + }; +} + +export function loadTagInfoError(tag) { + return { + type: LOAD_TAG_INFO_ERROR, + payload: tag + }; +} + +export function loadTagInfo(tag) { + return dispatch => { + dispatch(loadTagInfoStart(tag)); + + Promise.all([ + lastfm.getTagInfo(tag), + lastfm.getTagTracks(tag), + lastfm.getTagAlbums(tag), + lastfm.getTagArtists(tag) + ]) + .then(results => Promise.all(results.map(r => r.json()))) + .then(results => { + dispatch(loadTagInfoSuccess(tag, results)); + }) + .catch(error => { + console.error(error); + dispatch(loadTagInfoError(error)); + }); + }; +} diff --git a/app/actions/toasts.js b/app/actions/toasts.js new file mode 100644 index 0000000000..848c60cee5 --- /dev/null +++ b/app/actions/toasts.js @@ -0,0 +1,53 @@ +import uuidv4 from 'uuid/v4'; + +export const ADD_NOTIFICATION = 'ADD_NOTIFICATION'; +export const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION'; + +export function addNotification(notification) { + return { + type: ADD_NOTIFICATION, + payload: notification + }; +} + +export function removeNotification(id) { + return { + type: REMOVE_NOTIFICATION, + payload: id + }; +} + +export function notify(title, details, icon, timeout=3) { + return generateNotification(title, details, icon, {}, timeout); +} + +export function error(title, details, icon, timeout=3) { + return generateNotification(title, details, icon, {error: true}, timeout); +} + +export function warning(title, details, icon, timeout=3) { + return generateNotification(title, details, icon, {warning: true}, timeout); +} + +export function success(title, details, icon, timeout=3) { + return generateNotification(title, details, icon, {success: true}, timeout); +} + +export function info(title, details, icon, timeout=3) { + return generateNotification(title, details, icon, {info: true}, timeout); +} + +function generateNotification(title, details, icon, type, timeout=3) { + return dispatch => { + let id = uuidv4(); + dispatch(addNotification(Object.assign({}, { + onClick: () => { + dispatch(removeNotification(id)); + }, + id, title, details, icon + }, + type))); + + setTimeout(() => dispatch(removeNotification(id)), timeout * 1000); + }; +} diff --git a/app/app.global.scss b/app/app.global.scss index a43c6376f0..827276e4b7 100644 --- a/app/app.global.scss +++ b/app/app.global.scss @@ -1,4 +1,4 @@ -@import "vars"; +@import 'vars'; html { position: absolute; @@ -23,22 +23,34 @@ a { &:hover { text-decoration: none; color: $white; - transition: 0.25s; + transition: $short-duration; + } +} + +a.disabled { + color: rgba($grey, 0.25); + cursor: default; + + &:hover, + &:focus, + &:active { + background-color: $background2 !important; + color: rgba($grey, 0.25); } } ::-webkit-scrollbar { - width: 0.5rem; + width: 0.5rem; } ::-webkit-scrollbar-track { - background-color: $background; - border-radius: 0; + background-color: $background; + border-radius: 0; } ::-webkit-scrollbar-thumb { - border-radius: 0.5rem; - background-color: $background2; + border-radius: 0.5rem; + background-color: $background2; } //Semantic UI section @@ -48,6 +60,10 @@ a { position: relative; } +.ui.inverted.segment { + background: $background3; +} + .ui.menu { .item { color: $white; @@ -76,10 +92,13 @@ a { } } -.ui.tab.loading.segment, .ui.tab.loading, .ui.loader, .ui.dimmer .ui.loader { +.ui.tab.loading.segment, +.ui.tab.loading, +.ui.loader, +.ui.dimmer .ui.loader { &:before { background: rgba($background2, 0.2); - border: .2em solid rgba($background2, .6); + border: 0.2em solid rgba($background2, 0.6); } &:after { @@ -87,25 +106,62 @@ a { } } +.ui.modals.dimmer { + background: rgba($background3, 0.6); + + .content { + height: auto; + } +} + +.ui.modal.transition { + height: auto; +} + +.ui.button { + transition: $short-duration; +} + +.ui.inverted.green.button { + border-color: $green; + color: $green; + + &:hover, + &:focus { + background-color: $green; + } +} + +.ui.inverted.red.basic.button:focus { + color: $red !important; + + &:hover, + &:focus { + background-color: $red; + } +} + .ui.checkbox { - .box::before, label::before { + .box::before, + label::before { background-color: rgba($background3, 0.35) !important; - transition: 0.5s; + transition: $medium-duration; } - .box:hover::before, label:hover::before { + .box:hover::before, + label:hover::before { background-color: rgba($background2, 0.25) !important; - transition: 0.5s; + transition: $medium-duration; } } .ui.toggle.checkbox { - input:focus:checked~.box:before, - input:focus:checked~label:before, - input:checked~.box:before, - input:checked~label:before { + input:focus:checked ~ .box:before, + input:focus:checked ~ label:before, + input:checked ~ .box:before, + input:checked ~ label:before { background-color: $blue !important; - transition: 0.5s; + transition: $medium-duration; } } @@ -127,3 +183,33 @@ hr { flex: 0 0 auto; margin: 0.5rem; } + +.ui.inverted.list .item a:not(.ui) { + color: $white !important; + + &:hover, + &:focus, + &:active { + color: $white !important; + } +} + +.link_button { + display: flex; + justify-content: center; + align-items: center; + background: $background2; + color: $white !important; + width: 2rem; + height: 2rem; + + &:hover { + background: lighten($background2, 10%); + color: $white !important; + } + + &:active { + background: lighten($background2, 20%); + color: $white !important; + } +} diff --git a/app/compact.scss b/app/compact.scss new file mode 100644 index 0000000000..b24d1b0838 --- /dev/null +++ b/app/compact.scss @@ -0,0 +1,25 @@ +@import './vars'; + +.compact_panel { + width: 3rem; + + .sidebar_brand { + padding: 1rem 0; + } + + .sidebar_menu_item_container { + display: flex; + justify-content: center; + padding: 0.5rem 0.25rem; + } + + .active_nav_link { + border: none; + background: rgba($background3, 0.6); + } + + span { + margin: 0 !important; + } + +} diff --git a/app/components/AlbumCover/AlbumInfo/index.js b/app/components/AlbumCover/AlbumInfo/index.js index fab3f6123b..9e48cfad62 100644 --- a/app/components/AlbumCover/AlbumInfo/index.js +++ b/app/components/AlbumCover/AlbumInfo/index.js @@ -9,16 +9,16 @@ class AlbumInfo extends React.Component {
{ this.props.nameOnly - ?( -
{this.props.artist}
- ) - :
{this.props.title}
+ ?( +
{this.props.artist}
+ ) + :
{this.props.title}
} { !this.props.nameOnly - ?
{this.props.artist}
- : null + ?
{this.props.artist}
+ : null }
); diff --git a/app/components/AlbumCover/index.js b/app/components/AlbumCover/index.js index fd2187c9fc..d74b578d26 100644 --- a/app/components/AlbumCover/index.js +++ b/app/components/AlbumCover/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import styles from './styles.css'; @@ -8,7 +9,7 @@ import AlbumOverlay from './AlbumOverlay'; class AlbumCover extends React.Component { render() { - var style = {}; + let style = {}; if (this.props.nameOnly) { style = { @@ -25,8 +26,8 @@ class AlbumCover extends React.Component { { this.props.nameOnly - ? null - : + ? null + : } - { - this.props.albums && this.props.albums.length > 0 + return ( +
+ { + this.props.albums && this.props.albums.length > 0 ?
- { - this.props.albums.map((el, i) => { - return this.albumInfoSearch(el.id)} - /> - }) - } -
+ { + this.props.albums.map((el, i) => { + return ( + this.albumInfoSearch(el.id, el.type)} + />); + }) + } +
: - - - - - } -
- ); + + + + + } + + ); } } diff --git a/app/components/AlbumList/styles.scss b/app/components/AlbumList/styles.scss index 7e2b961294..6bd10cb0f0 100644 --- a/app/components/AlbumList/styles.scss +++ b/app/components/AlbumList/styles.scss @@ -9,5 +9,5 @@ width: 100%; height: 100%; flex-flow: row wrap; - justify-content: center; + justify-content: flex-start; } diff --git a/app/components/AlbumView/index.js b/app/components/AlbumView/index.js index 93411000b5..71169b6745 100644 --- a/app/components/AlbumView/index.js +++ b/app/components/AlbumView/index.js @@ -1,8 +1,11 @@ import React from 'react'; import FontAwesome from 'react-fontawesome'; -import {Dimmer, Loader, Image, Segment} from 'semantic-ui-react'; +import { Dimmer, Loader } from 'semantic-ui-react'; +import _ from 'lodash'; import ContextPopup from '../ContextPopup'; +import TrackRow from '../TrackRow'; +import * as Utils from '../../utils'; import styles from './styles.scss'; import artPlaceholder from '../../../resources/media/art_placeholder.png'; @@ -12,91 +15,239 @@ class AlbumView extends React.Component { super(props); } - artistInfoSearch(artistId) { + getArtistName (track, album) { + if (!track.artists) { + return album.artists[0].name; + } else { + let firstArtist = _.find(track.artists, artist => artist.join === '') + .name; + let artistName = firstArtist; + _(track.artists) + .filter(artist => artist.name !== firstArtist) + .forEach(artist => { + artistName += ' ' + artist.join + ' ' + artist.name; + }); + + return artistName; + } + } + + addAlbumToQueue (album) { + album.tracklist.map(track => { + this.props.addToQueue(this.props.musicSources, { + artist: album.artists[0].name, + name: track.title, + thumbnail: album.images[0].uri + }); + }); + } + + artistInfoSearch (artistId) { this.props.artistInfoSearch(artistId); this.props.history.push('/artist/' + artistId); } - render() { + playAll (album) { + this.props.clearQueue(); + this.addAlbumToQueue(album); + this.props.selectSong(0); + this.props.startPlayback(); + } + + render () { + let { album } = this.props; + if ( + _.some(_.map([album.images, album.artists, album.genres], _.isEmpty)) && + album.loading !== true + ) { + return this.renderInvalidData(); + } + + let albumImage = this.getAlbumImage(album); + return this.renderAlbumLoading(album, albumImage); + } + + renderInvalidData () { + return ( +
+

Discogs returned invalid data.

+

Try going back to search.

+
+ ); + } + + getAlbumImage (album) { + let albumImage = _.find(album.images, { type: 'primary' }); + if (!albumImage) { + albumImage = album.images ? album.images[0].uri : artPlaceholder; + } else { + albumImage = albumImage.uri; + } + return albumImage; + } + + renderAlbumArtistName (album) { + return ( + + ); + } + + renderAlbumGenre (album) { + return ( +
+ + {album.genres[0]} +
+ ); + } + + renderPlayAllButton (album) { + return ( + this.playAll(album)} + href='#' + className={styles.play_button} + > + Play + + ); + } + + renderAlbumYear (album) { + return ( +
+ + {album.year} +
+ ); + } + + renderAlbumTracksCount (album) { + return ( +
+ + {album.tracklist.length} +
+ ); + } + + renderAlbumInfoBox (album, albumImage) { + return ( +
+ +
+
{album.title}
+ {this.renderAlbumArtistName(album)} + {this.renderAlbumGenre(album)} + {this.renderAlbumYear(album)} + {this.renderAlbumTracksCount(album)} +
+ {this.renderPlayAllButton(album)} + {this.renderOptionsButtons(album)} +
+
+
+ ); + } + + renderAlbumLoading (album, albumImage) { return (
- - + + - { - this.props.album.loading - ? null - :
-
- -
-
{this.props.album.title}
- -
- - {this.props.album.styles[0]}
- -
- - {this.props.album.year}
-
- - {this.props.album.tracklist.length}
-
- -
- - - - - - - - - - - { - this.props.album.tracklist.map((el, i) => { - return ( - - - - - - } - artist={this.props.album.artists[0].name} - title={el.title} - thumb={this.props.album.images[0].uri} - > - this.props.addToQueue(this.props.musicSources, { - artist: this.props.album.artists[0].name, - name: el.title, - thumbnail: this.props.album.images[0].uri - })} - className={styles.add_button} - > - Add to queue - - Play now - - ) - }) - } - -
Song
{i + 1}{el.title}{el.duration}
- -
+ album.loading !== true && ( +
+ {this.renderAlbumInfoBox(album, albumImage)} + {this.renderAlbumTracksList(album)} +
+ ) } +
+
+ ); + } + + renderTrack (track, album, index) { + if (parseInt(track.duration) !== track.duration) { + track.duration = Utils.stringDurationToSeconds(track.duration); + } + _.set(track, 'name', track.title); + _.set(track, 'image[0][#text]', _.get(album, 'images[0].uri')); + _.set(track, 'artist.name', this.getArtistName(track, album)); + return (); + } + renderTrackTableHeader () { + return ( + + + + + Song + + + + + ); + } + + renderAlbumTracksList (album) { + return ( + + {this.renderTrackTableHeader()} + + {album.tracklist.map((track, index) => this.renderTrack(track, album, index) + )} + +
+ ); + } - - + renderOptionsButtons (album) { + return ( + + + + } + artist={album.artists[0].name} + title={album.title} + thumb={album.images ? album.images[0].uri : artPlaceholder} + > + this.addAlbumToQueue(album)} + className={styles.add_button} + aria-label='Add album to queue' + > + Add to queue + + ); } } diff --git a/app/components/AlbumView/styles.scss b/app/components/AlbumView/styles.scss index e0da9801b2..c01157bd0c 100644 --- a/app/components/AlbumView/styles.scss +++ b/app/components/AlbumView/styles.scss @@ -5,6 +5,29 @@ height: 100%; margin: 1rem 0rem; + .album_buttons { + display: flex; + flex-flow: row; + padding-top: 1rem; + + a { + padding: 0.375rem 2.5rem; + } + } + + .play_button { + background: $pink; + color: $white; + border-radius: 1.5rem; + text-transform: uppercase; + margin-right: 2rem; + } + + .more_button { + border: 1px solid white; + border-radius: 1.5rem; + } + .dimmable { width: 100%; height: 100%; @@ -59,7 +82,7 @@ font-size: 20px; a { color: $lightbg; - transition: 0.25s; + transition: $short-duration; &:hover { color: $white; @@ -97,11 +120,11 @@ tbody { tr { - transition: 0.25s; + transition: $short-duration; } tr:hover { - background: rgba($background2, 0.25); + background: rgba($background2, 0.25); } td { @@ -121,4 +144,6 @@ } } -.add_button {text-align: left;} +.add_button { + text-align: left; +} diff --git a/app/components/ArtistView/ArtistTags/index.js b/app/components/ArtistView/ArtistTags/index.js index 56691daff3..ab07b046b5 100644 --- a/app/components/ArtistView/ArtistTags/index.js +++ b/app/components/ArtistView/ArtistTags/index.js @@ -7,17 +7,25 @@ class ArtistTags extends React.Component { super(props); } + onTagClick(tag) { + this.props.history.push('/tag/' + tag); + } + render() { return (
{ - this.props.tags && this.props.tags.length > 0 - ? this.props.tags.map((el, i) => { - return ( - #{el.name} - ); - }) - : null + this.props.tags && this.props.tags.length > 0 && + this.props.tags.map((el, i) => { + return ( + this.onTagClick.bind(this)(el.name)} + key={i} + className={styles.tag} + >#{el.name} + ); + }) }
); diff --git a/app/components/ArtistView/ArtistTags/styles.scss b/app/components/ArtistView/ArtistTags/styles.scss index 951537ff20..8bdfac4981 100644 --- a/app/components/ArtistView/ArtistTags/styles.scss +++ b/app/components/ArtistView/ArtistTags/styles.scss @@ -2,18 +2,19 @@ .tags_container { display: flex; - flex-flow: row; - flex: 1 1 auto; - align-items: center; - + flex: 1 1 auto; + flex-flow: row wrap; } .tag { display: flex; - margin: 0 0.5rem; + + margin: 0 0.5rem 0.5rem 0.5rem; padding: 0.25rem 0.5rem; + + color: $white; border-radius: 0.75rem; background-color: rgba($green, 0.8); - color: $white; + white-space: nowrap; } diff --git a/app/components/ArtistView/PopularTracks/index.js b/app/components/ArtistView/PopularTracks/index.js index 52e522ed1b..856c1dfc2e 100644 --- a/app/components/ArtistView/PopularTracks/index.js +++ b/app/components/ArtistView/PopularTracks/index.js @@ -1,61 +1,95 @@ import React from 'react'; import FontAwesome from 'react-fontawesome'; -import numeral from 'numeral'; +import TrackRow from '../../TrackRow'; import artPlaceholder from '../../../../resources/media/art_placeholder.png'; -import ContextPopup from '../../ContextPopup'; - import styles from './styles.scss'; class PopularTracks extends React.Component { constructor(props) { super(props); + + this.state = { + expanded: false + }; + } + + toggleExpand () { + this.setState(prevState => { + return { expanded: !prevState.expanded }; + }); + } + + renderAddAllButton (artist, tracks) { + return ( + { + tracks.track + .slice(0, this.state.expanded ? 15 : 5) + .map(track => { + this.props.addToQueue(this.props.musicSources, { + artist: artist.name, + name: track.name, + thumbnail: track.image[0]['#text'] || artPlaceholder + }); + }); + }} + className={styles.add_button} + aria-label='Add all tracks to queue' + > + Add all + + ); } - render() { + render () { + let { artist, tracks } = this.props; + return (
-
- Popular tracks: +
Popular tracks:
+ {this.renderAddAllButton(artist, tracks)} + + + + + + + + + + {tracks.track + .slice(0, this.state.expanded ? 15 : 5) + .map((track, index) => { + return ( + + ); + })} + +
+ + TitlePlay Counts
+
+
- { - this.props.tracks.track.slice(0, 5).map((track, index)=> { - return ( - - -
- {track.name} -
-
- {numeral(track.playcount).format('0,0')} -
-
- } - artist={this.props.artist.name} - title={track.name} - thumb={track.image[0]['#text'] || artPlaceholder} - > - this.props.addToQueue(this.props.musicSources, { - artist: this.props.artist.name, - name: track.name, - thumbnail: track.image[0]['#text'] || artPlaceholder - })} - className={styles.add_button} - > - Add to queue - - Play now - - ) - }) - }
); - } } diff --git a/app/components/ArtistView/PopularTracks/styles.scss b/app/components/ArtistView/PopularTracks/styles.scss index 0c6c40e6d3..5b788502d9 100644 --- a/app/components/ArtistView/PopularTracks/styles.scss +++ b/app/components/ArtistView/PopularTracks/styles.scss @@ -19,7 +19,7 @@ align-items: center; flex-flow: row; - transition: 0.25s ease-in-out; + transition: $short-duration ease-in-out; border-bottom: 1px solid rgba($background2, 0.2); @@ -44,8 +44,24 @@ flex: 1 1 auto; margin: 0.25rem 0.5rem; text-align: right; + text-transform: uppercase; + } + } + + .expand_button { + display: flex; + justify-content: center; + padding: 0.5rem; + margin-top: 0.5rem; + transition: $short-duration; + cursor: pointer; + + &:hover { + background: lighten($background, 5%); } } } -.add_button {text-align: left;} \ No newline at end of file +.add_button { + text-align: left; +} diff --git a/app/components/ArtistView/SimilarArtists/index.js b/app/components/ArtistView/SimilarArtists/index.js index 59b596e029..f6ca84e0c7 100644 --- a/app/components/ArtistView/SimilarArtists/index.js +++ b/app/components/ArtistView/SimilarArtists/index.js @@ -22,11 +22,13 @@ class SimilarArtists extends React.Component { { this.props.artists.map((artist, index) => { return ( -
{this.artistInfoSearchByName(artist.name)}} className={styles.artist_row}> - -
{artist.name}
-
- ) +
{ + this.artistInfoSearchByName(artist.name); + }} className={styles.artist_row}> + +
{artist.name}
+
+ ); }) } diff --git a/app/components/ArtistView/SimilarArtists/styles.scss b/app/components/ArtistView/SimilarArtists/styles.scss index 67cf6e3886..1dd4ad0e06 100644 --- a/app/components/ArtistView/SimilarArtists/styles.scss +++ b/app/components/ArtistView/SimilarArtists/styles.scss @@ -23,7 +23,7 @@ margin: 0.25rem 0; padding: 0.25rem; - transition: 0.25s ease-in-out; + transition: $short-duration ease-in-out; border-radius: 2px; background: $background2; diff --git a/app/components/ArtistView/index.js b/app/components/ArtistView/index.js index 9863a4beb8..042114754b 100644 --- a/app/components/ArtistView/index.js +++ b/app/components/ArtistView/index.js @@ -1,6 +1,6 @@ import React from 'react'; -import {Dimmer, Loader} from 'semantic-ui-react'; -import Spacer from '../Spacer'; +import _ from 'lodash'; +import { Dimmer, Loader } from 'semantic-ui-react'; import AlbumList from '../AlbumList'; import ArtistTags from './ArtistTags'; import SimilarArtists from './SimilarArtists'; @@ -15,82 +15,135 @@ class ArtistView extends React.Component { this.isLoading = this.isLoading.bind(this); } - isLoading() { - return this.props.artist.loading || - !this.props.artist.lastfm || - this.props.artist.lastfm.loading; + isLoading () { + return ( + _.get(this.props, 'artist.loading') || + _.isEmpty(_.get(this.props, 'artist.lastfm')) || + _.get(this.props, 'artist.lastfm.loading') + ); + } + + renderArtistHeader (artist, history) { + return ( +
+
+
+ +
+

{artist.name}

+ + {typeof artist.lastfm.artist !== 'undefined' && ( + + )} +
+
+
+ ); + } + + renderPopularTrack () { + let { + artist, + addToQueue, + selectSong, + startPlayback, + clearQueue, + musicSources + } = this.props; + return ( + !this.isLoading() && + artist.lastfm.toptracks && ( + + ) + ); + } + + renderSimilarArtists () { + let { artist, history, artistInfoSearchByName } = this.props; + + return ( + !this.isLoading() && + typeof artist.lastfm.artist !== 'undefined' && ( + + ) + ); + } + + renderHeaderBanner () { + let { artist, history } = this.props; + + return ( +
+ {this.renderArtistHeader(artist, history)} +
+ ); } - render() { + render () { + let { artist, history, albumInfoSearch } = this.props; return (
- + - {this.isLoading() - ? null - :
-
- -
-
-
-
-

{this.props.artist.name}

- -
-
-
- -
-
- } + {!this.isLoading() && ( +
{this.renderHeaderBanner()}
+ )}
-
- { - this.isLoading() - ? null - : - } + {this.renderPopularTrack()} - { - this.isLoading() - ? null - : - } + {this.renderSimilarArtists()}
- +
{ + return b.year - a.year; + })} + albumInfoSearch={albumInfoSearch} + history={history} />
- ) + ); } } export default ArtistView; diff --git a/app/components/Card/index.js b/app/components/Card/index.js index 3a518caeaf..0977bd2bc3 100644 --- a/app/components/Card/index.js +++ b/app/components/Card/index.js @@ -1,10 +1,12 @@ import React from 'react'; +import classnames from 'classnames'; import { Image } from 'semantic-ui-react'; +import Img from 'react-image-smooth-loading'; import artPlaceholder from '../../../resources/media/art_placeholder.png'; import styles from './styles.scss'; -var classNames = require('classnames'); +Img.globalPlaceholder = artPlaceholder; class Card extends React.Component { constructor(props) { @@ -16,7 +18,7 @@ class Card extends React.Component {
-
+

{this.props.header}

{ this.props.content - ?

{this.props.content}

- : null + ?

{this.props.content}

+ : null }
diff --git a/app/components/Card/styles.scss b/app/components/Card/styles.scss index 6783808399..b456cdd6c1 100644 --- a/app/components/Card/styles.scss +++ b/app/components/Card/styles.scss @@ -1,23 +1,35 @@ -@import "../../vars"; +@import '../../vars'; .card_container { width: 20%; padding: 1rem; + + @media screen and (max-width: 1600px) { + width: 25%; + } + + @media screen and (max-width: 1280px) { + width: 33.33%; + } + + @media screen and (max-width: 1100px) { + width: 50%; + } } .card { display: flex; flex-flow: column; - box-shadow: 0 0.25rem 0.5rem 0 rgba(0,0,0,0.2); + box-shadow: 0 0.25rem 0.5rem 0 rgba(0, 0, 0, 0.2); background-color: $background2; border-radius: 0.125rem; width: auto; height: 100%; - transition: 0.25s ease-in-out; + transition: $short-duration ease-in-out; cursor: pointer; &:hover { - box-shadow: 0 0.5rem 1rem 0 rgba(0,0,0,0.2); + box-shadow: 0 0.5rem 1rem 0 rgba(0, 0, 0, 0.2); transform: scale(1.1); } @@ -25,12 +37,14 @@ flex: 1 1 auto; width: 100%; border-radius: 0.125rem 0.125rem 0 0; + overflow: hidden; div { width: 100%; height: 0; - padding-top: 100%; border-radius: 0.125rem 0.125rem 0 0; + background-size: cover !important; + background-position: center !important; } } diff --git a/app/components/ContextPopup/index.js b/app/components/ContextPopup/index.js index 10bb3f20da..4a8ee7cdfb 100644 --- a/app/components/ContextPopup/index.js +++ b/app/components/ContextPopup/index.js @@ -6,26 +6,46 @@ import styles from './styles.scss'; class ContextPopup extends React.Component { constructor(props) { super(props); + + this.state = { + isOpen: false + }; + } + + handleOpen() { + this.setState({ isOpen: true }); } + handleClose() { + this.setState({ isOpen: false }); + } + render() { return ( + open={this.state.isOpen} + onClose={this.handleClose.bind(this)} + onOpen={this.handleOpen.bind(this)} + hideOnScroll + >
{this.props.title}
-
by {this.props.artist}
+ { + this.props.artist + ?
by {this.props.artist}
+ : null + }

-
+
{this.props.children}
diff --git a/app/components/ContextPopup/styles.scss b/app/components/ContextPopup/styles.scss index dddd5fa863..e293195c30 100644 --- a/app/components/ContextPopup/styles.scss +++ b/app/components/ContextPopup/styles.scss @@ -6,7 +6,8 @@ background-color: $background !important; color: $white; border-color: $background2 !important; - box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.18), 0px 0px 20px 0px rgba(0, 0, 0, 0.09) !important; + box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.18), + 0px 0px 20px 0px rgba(0, 0, 0, 0.09) !important; height: auto !important; &:before { @@ -14,10 +15,10 @@ box-shadow: 1px 1px 0 0 $background2 !important; } - transition: 0.2s; + transition: $very-short-duration; } -.popup_content>.content { +.popup_content > .content { position: absolute; display: flex; width: 100%; diff --git a/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListActiveItem/index.js b/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListActiveItem/index.js index 954863750f..38022e0170 100644 --- a/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListActiveItem/index.js +++ b/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListActiveItem/index.js @@ -1,16 +1,73 @@ import React from 'react'; +import FontAwesome from 'react-fontawesome'; import styles from './styles.scss'; class BestNewListActiveItem extends React.Component { constructor(props) { - super(props); + super(props); } - - render() { - let { - item - } = this.props; + + renderThumbnail (item) { + return (
+
+
); + } + + renderArtistTitleBox (item) { + return (); + } + + renderReview (item) { + return (
+ {item.abstract ? ( +
{item.abstract}
+ ) : null} +
+ {item.review.split('\n').map(i => { + return ( + +
+ {i} +
+ ); + })} +
+
); + } + + addToQueue (item) { + return this.props.addToQueue(this.props.musicSources, { + artist: item.artist, + name: item.title, + thumbnail: item.thumbnail + }); + } + render () { + let { item } = this.props; if (!item) { return null; @@ -18,47 +75,34 @@ class BestNewListActiveItem extends React.Component { return (
-
-
-
+ {this.renderThumbnail(item)}
- { - item.score - ?
- {item.score} -
- : null - } - -
-
- {item.title} -
-
- by {item.artist} -
-
- - -
-
- { - item.abstract - ?
{item.abstract}
- : null - } -
- {item.review} -
- -
-
- + {item.score ? ( +
{item.score}
+ ) : null} + {this.renderArtistTitleBox(item)} +
+
); } diff --git a/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListActiveItem/styles.scss b/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListActiveItem/styles.scss index 081271be99..af005eda43 100644 --- a/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListActiveItem/styles.scss +++ b/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListActiveItem/styles.scss @@ -10,6 +10,7 @@ width: 25%; height: 0; padding-bottom: 25%; + margin-bottom: 25%; } .item_thumbnail { @@ -20,7 +21,7 @@ left: 1rem; right: 1rem; border-radius: 0.25rem; - transition: 0.25s; + transition: $short-duration; background-size: cover; } @@ -84,14 +85,37 @@ padding-left: 0; } } - + .artist { font-size: 1.25rem; color: $lightbg; + + a { + color: $lightbg; + + &:hover { + color: $white; + } + } } .title { font-size: 1.5rem; padding-bottom: 0.5rem; } + + .paragraph { + margin-bottom: 1rem; + + &:last-child { + margin: 0; + } + } +} + +.add_button { + padding: 0.5rem; + &:hover { + background: $pink !important; + } } diff --git a/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListItem/index.js b/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListItem/index.js index a12395a77e..fe38bdf959 100644 --- a/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListItem/index.js +++ b/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListItem/index.js @@ -12,11 +12,11 @@ class BestNewListItem extends React.Component { item, onMouseEnter } = this.props; - return( + return (
-
diff --git a/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListItem/styles.scss b/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListItem/styles.scss index 22b214cf30..9391554609 100644 --- a/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListItem/styles.scss +++ b/app/components/Dashboard/BestNewMusicTab/BestNewList/BestNewListItem/styles.scss @@ -7,7 +7,7 @@ padding-bottom: 16.666%; background-color: $background; border-radius: 0.25rem; - transition: 0.25s; + transition: $short-duration; &:hover { background-color: $background2; diff --git a/app/components/Dashboard/BestNewMusicTab/BestNewList/index.js b/app/components/Dashboard/BestNewMusicTab/BestNewList/index.js index 97986db84f..46ca1d2344 100644 --- a/app/components/Dashboard/BestNewMusicTab/BestNewList/index.js +++ b/app/components/Dashboard/BestNewMusicTab/BestNewList/index.js @@ -12,35 +12,46 @@ class BestNewList extends React.Component { }; } - render() { + render () { let { - data + data, + artistInfoSearchByName, + albumInfoSearchByName, + history } = this.props; return (
- -
+ +
- { - data.map((el, i) => { - return this.setState({ - activeItem: i - }) - } - item={el} - key={i} - />; - }) - } -
- + { + data.map((el, i) => { + return this.setState({ + activeItem: i + }) + } + item={el} + key={i} + />; + }) + } +
+
); } diff --git a/app/components/Dashboard/BestNewMusicTab/index.js b/app/components/Dashboard/BestNewMusicTab/index.js index 868aa10ea6..c5dfe153b3 100644 --- a/app/components/Dashboard/BestNewMusicTab/index.js +++ b/app/components/Dashboard/BestNewMusicTab/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Dimmer, Loader, Tab} from 'semantic-ui-react'; +import { Tab } from 'semantic-ui-react'; import BestNewList from './BestNewList'; import styles from './styles.scss'; @@ -9,34 +9,52 @@ class BestNewMusicTab extends React.Component { super(props); } - isLoading() { + isLoading () { return this.props.dashboardData.bestNewAlbums.length < 1 || this.props.dashboardData.bestNewTracks.length < 1; } - render() { + render () { + let { + dashboardData, + artistInfoSearchByName, + history, + albumInfoSearchByName + } = this.props; return (
-

- Best new albums -

-
- - - -

- Best new tracks -

+ Best new albums + +
+ +
+

+ Best new tracks +

- -
-
- + +
+
); } diff --git a/app/components/Dashboard/ChartsTab/index.js b/app/components/Dashboard/ChartsTab/index.js new file mode 100644 index 0000000000..25c15bc0c7 --- /dev/null +++ b/app/components/Dashboard/ChartsTab/index.js @@ -0,0 +1,55 @@ +import React from 'react'; +import { Tab } from 'semantic-ui-react'; +import TrackRow from '../../TrackRow'; +import FontAwesome from 'react-fontawesome'; + + +import styles from './styles.scss'; + +class ChartsTab extends React.Component { + constructor(props) { + super(props); + } + + render () { + return ( + +
+

Top Tracks from LastFm.

+ + + + + + + + + + + + {this.props.topTracks.map((track, index) => { + return ; + })} + +
+ + ArtistTitlePlaycounts
+
+
+ ); + } +} + +export default ChartsTab; diff --git a/app/components/Dashboard/ChartsTab/styles.scss b/app/components/Dashboard/ChartsTab/styles.scss new file mode 100644 index 0000000000..5416c603ac --- /dev/null +++ b/app/components/Dashboard/ChartsTab/styles.scss @@ -0,0 +1,74 @@ +@import '../../../vars'; +.charts_container { +} + +.popular_tracks_container { + /*display: flex; + flex: 1 1 auto; + flex-flow: column; + + margin: 0 0.5rem; + + .header { + margin-bottom: 1rem; + + font-size: 16px; + font-variant: small-caps; + }*/ + .popular_tracks_header { + tr { + height: 10px + } + th { + height: 30px; + text-align: inherit !important; + border-bottom: 2px solid #44475a; + } + } + .track_row { + /*display: flex; + align-items: center; + flex-flow: row;*/ + + transition: $short-duration ease-in-out; + + border-bottom: 1px solid rgba($background2, 0.2); + + &:hover { + background: lighten($background, 10%); + } + + img { + flex: 0 0 auto; + + width: 3rem; + height: 3rem; + } + + .popular_track_name { + flex: 1 1 auto; + margin: 0.25rem 0.5rem; + text-align: left; + } + + .playcount { + flex: 1 1 auto; + margin: 0.25rem 0.5rem; + text-align: right; + text-transform: uppercase; + } + } + + .expand_button { + display: flex; + justify-content: center; + padding: 0.5rem; + margin-top: 0.5rem; + transition: $short-duration; + cursor: pointer; + + &:hover { + background: lighten($background, 5%); + } + } +} diff --git a/app/components/Dashboard/GenresTab/index.js b/app/components/Dashboard/GenresTab/index.js new file mode 100644 index 0000000000..d43ea82380 --- /dev/null +++ b/app/components/Dashboard/GenresTab/index.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { Tab } from 'semantic-ui-react'; +import Img from 'react-image-smooth-loading'; + +import styles from './styles.scss'; + +class GenresTab extends React.Component { + constructor(props) { + super(props); + } + + onGenreClick (genreName) { + this.props.history.push('/tag/' + genreName); + } + + render () { + let { + genres + } = this.props; + + return ( + +
+ { + typeof genres !== 'undefined' + ? genres.map((tag, i) => { + return ( +
this.onGenreClick(tag.name)} + > + +
+ +
+
+ {tag.name} +
+
+ ); + }) + : null + } +
+
+ ); + } +} + +export default GenresTab; diff --git a/app/components/Dashboard/GenresTab/styles.scss b/app/components/Dashboard/GenresTab/styles.scss new file mode 100644 index 0000000000..a2f9c1b280 --- /dev/null +++ b/app/components/Dashboard/GenresTab/styles.scss @@ -0,0 +1,75 @@ +@import '../../../vars'; + +.genre_tab_container { + display: flex; + flex-flow: row wrap; + + .genre_container { + position: relative; + display: flex; + flex-flow: row; + justify-content: center; + align-items: center; + width: 25%; + height: 0; + padding-bottom: 25%; + border-radius: 0.25rem; + transition: $short-duration; + overflow: hidden; + cursor: pointer; + + &:hover { + transform: scale(1.1); + } + } + + .genre_name, + .genre_overlay { + position: absolute; + top: 1rem; + bottom: 1rem; + left: 1rem; + right: 1rem; + border-radius: 0.25rem; + } + + .genre_overlay { + overflow: hidden; + background-color: $black; + } + + .genre_overlay img:first-child { + opacity: 0.6; + } + + .genre_name { + display: flex; + justify-content: center; + align-items: center; + font-size: 1rem; + letter-spacing: 4px; + text-transform: uppercase; + text-align: center; + } + + @media screen and (max-width: 1366px) { + .genre_container { + width: 33.33%; + padding-bottom: 33.33%; + } + } + + @media screen and (max-width: 1180px) { + .genre_container { + width: 50%; + padding-bottom: 50%; + } + } + + @media screen and (max-width: 1050px) { + .genre_container { + width: 100%; + padding-bottom: 100%; + } + } +} diff --git a/app/components/Dashboard/NewsTab/NewsItem/index.js b/app/components/Dashboard/NewsTab/NewsItem/index.js new file mode 100644 index 0000000000..f3351da810 --- /dev/null +++ b/app/components/Dashboard/NewsTab/NewsItem/index.js @@ -0,0 +1,36 @@ +import React from 'react'; +import moment from 'moment'; + +import styles from './styles.scss'; + +class NewsItem extends React.Component { + constructor(props) { + super(props); + } + + render() { + let { item } = this.props; + return ( +
+

{item.title}

+

+ {moment.unix(item.timestamp).format('dddd, MMMM, Do YYYY, h:mm:ss A')} +

+ +

+ +

+ {item.tags.map((tag, i) => { + return ( + + {tag} + + ); + })} +
+
+ ); + } +} + +export default NewsItem; diff --git a/app/components/Dashboard/NewsTab/NewsItem/styles.scss b/app/components/Dashboard/NewsTab/NewsItem/styles.scss new file mode 100644 index 0000000000..a4bad66fb5 --- /dev/null +++ b/app/components/Dashboard/NewsTab/NewsItem/styles.scss @@ -0,0 +1,33 @@ +@import '../../../../vars'; + +.news_item { + border: 1px solid rgba($lightbg, 0.3); + padding: 1rem; + border-radius: 0.25rem; + margin-bottom: 1rem; + + &:last-child { + margin-bottom: 0; + } + + h1 { + width: 100%; + padding: 0.5rem 1rem 0.5rem 0; + } + + h4 { + margin: 1rem 0; + } + + .tags { + display: flex; + flex-flow: row; + justify-content: flex-end; + } + + .tag { + padding: 0.25rem 0.5rem; + border-radius: 1rem; + background-color: rgba(white, 0.5); + } +} diff --git a/app/components/Dashboard/NewsTab/index.js b/app/components/Dashboard/NewsTab/index.js new file mode 100644 index 0000000000..dbed6608e1 --- /dev/null +++ b/app/components/Dashboard/NewsTab/index.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { Dimmer, Loader, Tab } from 'semantic-ui-react'; +import moment from 'moment'; +import _ from 'lodash'; + +import NewsItem from './NewsItem'; +import styles from './styles.scss'; + +class NewsTab extends React.Component { + constructor(props) { + super(props); + } + + render() { + let { news } = this.props; + + return ( + +
+ {_(news) + .sortBy('timestamp') + .reverse() + .value() + .map((item, i) => { + + return ; + })} +
+
+ ); + } +} + +export default NewsTab; diff --git a/app/components/Dashboard/NewsTab/styles.scss b/app/components/Dashboard/NewsTab/styles.scss new file mode 100644 index 0000000000..2e062a33ab --- /dev/null +++ b/app/components/Dashboard/NewsTab/styles.scss @@ -0,0 +1,4 @@ +.news_container { + display: flex; + flex-flow: column; +} diff --git a/app/components/Dashboard/index.js b/app/components/Dashboard/index.js index 182852ab54..955ae10da0 100644 --- a/app/components/Dashboard/index.js +++ b/app/components/Dashboard/index.js @@ -2,34 +2,76 @@ import React from 'react'; import { Tab } from 'semantic-ui-react'; import BestNewMusicTab from './BestNewMusicTab'; +import ChartsTab from './ChartsTab'; +import GenresTab from './GenresTab'; +import NewsTab from './NewsTab'; class Dashboard extends React.Component { - panes() { + panes () { return [ { - menuItem: 'Best new music', - render: () => + menuItem: 'Best new music', + render: () => ( + + ) }, { - menuItem: 'Genres', - render: () => { return null; } + menuItem: 'Top Tracks', + render: () => ( + + ) }, { - menuItem: 'News', - render: () => { return null; } + menuItem: 'Genres', + render: () => ( + + ) }, + /* { + menuItem: 'Events', + render: () => { + return null; + }, + },*/ + { + menuItem: 'News', + render: () => + } ]; } - componentDidMount() { + componentDidMount () { this.props.loadBestNewTracks(); this.props.loadBestNewAlbums(); + this.props.loadNuclearNews(); + this.props.loadTopTags(); + this.props.loadTopTracks(); } - - render() { + + render () { return (
- +
); } diff --git a/app/components/Downloads/index.js b/app/components/Downloads/index.js new file mode 100644 index 0000000000..13f5b17337 --- /dev/null +++ b/app/components/Downloads/index.js @@ -0,0 +1,20 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import styles from './styles.scss'; + +const Downloads = props => { + return ( +
+

+ Downloads in Nuclear are coming soon. +

+
+ ); +}; + +Downloads.propTypes = { + +}; + +export default Downloads; diff --git a/app/components/Downloads/styles.scss b/app/components/Downloads/styles.scss new file mode 100644 index 0000000000..687f4af237 --- /dev/null +++ b/app/components/Downloads/styles.scss @@ -0,0 +1,3 @@ +.downloads_container { + +} diff --git a/app/components/Header/styles.scss b/app/components/Header/styles.scss index 1d5ce8568f..f580ee5f34 100644 --- a/app/components/Header/styles.scss +++ b/app/components/Header/styles.scss @@ -1,4 +1,5 @@ .header_container{ font-size: 30px; + line-height: 30px; margin: 12px; } diff --git a/app/components/InputDialog/index.js b/app/components/InputDialog/index.js new file mode 100644 index 0000000000..605275cafa --- /dev/null +++ b/app/components/InputDialog/index.js @@ -0,0 +1,86 @@ +import React from 'react'; +import { Button, Input, Modal } from 'semantic-ui-react'; + +import styles from './styles.scss'; + +class InputDialog extends React.Component { + constructor(props) { + super(props); + + this.state = { + isOpen: false, + inputString: '' + }; + + this.handleClose = this.handleClose.bind(this); + this.handleChange = this.handleChange.bind(this); + } + + handleClose() { + this.setState({ + isOpen: false + }); + } + + handleOpen() { + this.setState({ + isOpen: true + }); + } + + handleChange(e) { + this.setState({ + inputString: e.target.value + }); + } + + render() { + let { + trigger, + header, + placeholder, + accept, + onAccept + } = this.props; + + let onClick = () => { + onAccept(this.state.inputString); + this.handleClose(); + }; + + return ( + + + {header} + { + input && input.focus(); + }} + placeholder={placeholder} + onChange={this.handleChange} + /> + + + + + + + ); + } +} + +export default InputDialog; diff --git a/app/components/InputDialog/styles.scss b/app/components/InputDialog/styles.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/components/LyricsView/index.js b/app/components/LyricsView/index.js new file mode 100644 index 0000000000..2c4947addf --- /dev/null +++ b/app/components/LyricsView/index.js @@ -0,0 +1,50 @@ +import React from 'react'; +import _ from 'lodash'; +import Header from '../Header'; + +import styles from './styles.scss'; + +class LyricsView extends React.Component { + constructor(props) { + super(props); + } + + renderLyrics () { + let lyrics = this.props.lyrics; + let lyricsStr = _.get(lyrics, 'lyricsSearchResults', ''); + lyricsStr = _.get(lyricsStr, 'type', ''); + if (lyricsStr === '') { + lyricsStr = 'No lyrics were found for this song.'; + } + return (
{lyricsStr}
); + } + + renderLyricsHeader () { + let track = this.props.track; + return ( +
+ {track.name} by {track.artist} +
+ + ); + } + + renderNoSelectedTrack () { + return (

Play a track from the queue to get the lyrics here

); + } + + render () { + let track = this.props.track; + if (track === null) { + { this.renderNoSelectedTrack(); } + } + return ( +
+ {this.renderLyricsHeader()} + {this.renderLyrics()} +
+ ); + } +} + +export default LyricsView; diff --git a/app/components/LyricsView/styles.scss b/app/components/LyricsView/styles.scss new file mode 100644 index 0000000000..9a2ab9b281 --- /dev/null +++ b/app/components/LyricsView/styles.scss @@ -0,0 +1,5 @@ +.lyrics_text{ + white-space: pre-line; + font-size: 1.3em; + padding: 1em; +} diff --git a/app/components/MainLayout/styles.scss b/app/components/MainLayout/styles.scss index 5fceaaa20a..424cf24112 100644 --- a/app/components/MainLayout/styles.scss +++ b/app/components/MainLayout/styles.scss @@ -29,7 +29,7 @@ height: 100%; margin: 0; border-radius: 0.5rem; - transition: 1s; + transition: $long-duration; } .ui.dimmer { diff --git a/app/components/NavButtons/index.js b/app/components/NavButtons/index.js new file mode 100644 index 0000000000..200eeadf0a --- /dev/null +++ b/app/components/NavButtons/index.js @@ -0,0 +1,51 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import FontAwesome from 'react-fontawesome'; +import cx from 'classnames'; +import styles from './styles.scss'; + +class NavButtons extends React.Component { + + constructor(props) { + super(props); + this.enableBackButton = this.enableBackButton.bind(this); + this.enableForwardButton = this.enableForwardButton.bind(this); + } + + enableBackButton(currentHistoryIndex) { + return currentHistoryIndex > 1; + } + + enableForwardButton(currentHistoryIndex, historyLength) { + return currentHistoryIndex < (historyLength - 1); + } + + render() { + let { back, forward, historyLength, historyCurrentIndex} = this.props; + + return ( + + ); + }; +} + +NavButtons.propTypes = { + back: PropTypes.func, + forward: PropTypes.func +}; + +NavButtons.defaultProps = { + back: () => {}, + forward: () => {} +}; + +export default NavButtons; diff --git a/app/components/NavButtons/styles.scss b/app/components/NavButtons/styles.scss new file mode 100644 index 0000000000..e0f6746a0b --- /dev/null +++ b/app/components/NavButtons/styles.scss @@ -0,0 +1,38 @@ +@import '../../vars.scss'; + +.nav_buttons { + display: flex; + flex-flow: row; + justify-content: center; + align-items: center; + margin-left: 12px; + z-index: 40; + -webkit-app-region: no-drag; + + a { + display: flex; + justify-content: center; + align-items: center; + background: $blue; + height: 30px; + width: 30px; + cursor: pointer; + + &.disable { + opacity: 0.4; + cursor: not-allowed; + } + + &:not(.disable):hover { + background: darken($blue, 10%); + } + + &:not(.disable):active { + background: darken($blue, 15%); + } + + &:first-child { + margin-right: 12px; + } + } +} diff --git a/app/components/PlayOptionsControls/index.js b/app/components/PlayOptionsControls/index.js new file mode 100644 index 0000000000..7c1bbe1079 --- /dev/null +++ b/app/components/PlayOptionsControls/index.js @@ -0,0 +1,41 @@ +import React from 'react'; +import classnames from 'classnames'; +import FontAwesome from 'react-fontawesome'; +import _ from 'lodash'; + +import styles from './styles.scss'; +import settingsConst from '../../constants/settings'; + +function renderOptionControl(props, settingName, fontAwesomeName, title) { + return ( +
+ props.toggleOption( + _.find(settingsConst, { name: settingName }), + props.settings + ) + } + > + +
+ ); +} + +const PlayOptionsControls = props => { + return ( +
+ {renderOptionControl(props, 'loopAfterQueueEnd', 'repeat', 'Loop')} + {renderOptionControl(props, 'shuffleQueue', 'random', 'Shuffle')} + {renderOptionControl(props, 'autoradio', 'magic', 'Autoradio')} +
+ ); +}; + +PlayOptionsControls.propTypes = {}; +PlayOptionsControls.defaultProps = {}; + +export default PlayOptionsControls; diff --git a/app/components/PlayOptionsControls/styles.scss b/app/components/PlayOptionsControls/styles.scss new file mode 100644 index 0000000000..16d0cf0e91 --- /dev/null +++ b/app/components/PlayOptionsControls/styles.scss @@ -0,0 +1,29 @@ +@import '../../vars.scss'; +@import '../../mixin.scss'; + +.play_options_controls { + display: flex; + flex-flow: row; + justify-content: space-between; + align-items: center; + height: 100%; + + .icon { + @include transition; + color: rgba($white, 0.25); + cursor: pointer; + + span { + font-size: 18px; + margin: 0.2em; + } + + &.active { + color: $white; + } + + &:hover { + color: $white; + } + } +} diff --git a/app/components/PlayQueue/QueueItem/index.js b/app/components/PlayQueue/QueueItem/index.js index 0008632bc9..9a7aa6cd8b 100644 --- a/app/components/PlayQueue/QueueItem/index.js +++ b/app/components/PlayQueue/QueueItem/index.js @@ -1,70 +1,99 @@ import React from 'react'; import classNames from 'classnames'; import FontAwesome from 'react-fontawesome'; -import {formatDuration} from '../../../utils'; +import _ from 'lodash'; +import { formatDuration, getSelectedStream } from '../../../utils'; import styles from './styles.scss'; class QueueItem extends React.Component { - constructor(props){ + constructor(props) { super(props); this.state = { - style: {} + style: {} }; } componentDidMount() { setTimeout(() => { - this.setState( - { - style: {'opacity': 1} - })}, 1); + this.setState({ + style: { opacity: 1 } + }); + }, 1); } - render() { + renderTrackDuration(selectedStream) { return ( -
this.props.selectSong(this.props.index)} - > -
- { - this.props.loading - ? - : - } +
+
+ {selectedStream ? formatDuration(selectedStream.duration) : null} +
+
+ ); + } -
-
+ renderRemoveFromQueueButton(track, removeFromQueue) { + return ( +
removeFromQueue(track)} + > + +
+ ); + } -
- {this.props.track.name} -
-
- {this.props.track.artist} -
+ renderThumbnailContainer(track, removeFromQueue, loading) { + return ( +
+ {loading ? ( + + ) : ( + + )} + {this.renderRemoveFromQueueButton(track, removeFromQueue)} +
+ ); + } -
+ renderClassName(current) { + return classNames(styles.queue_item_container, { + [`${styles.current_song}`]: current + }); + } + + renderItemInfoContainer(track) { + return ( +
+
{track.name}
+
{track.artist}
+
+ ); + } + render() { + let { + current, + loading, + track, + index, + defaultMusicSource, + selectSong, + removeFromQueue + } = this.props; -
-
- { - this.props.track.streams - ? formatDuration(this.props.track.streams[0].duration) - : null - } -
-
-
+ let selectedStream = getSelectedStream(track.streams, defaultMusicSource); + + return ( +
selectSong(index)} + > + {this.renderThumbnailContainer(track, removeFromQueue, loading)} + {this.renderItemInfoContainer(track)} + {this.renderTrackDuration(selectedStream)} +
); } } diff --git a/app/components/PlayQueue/QueueItem/styles.scss b/app/components/PlayQueue/QueueItem/styles.scss index 5628736bae..719f123ed2 100644 --- a/app/components/PlayQueue/QueueItem/styles.scss +++ b/app/components/PlayQueue/QueueItem/styles.scss @@ -2,7 +2,7 @@ .queue_item_container { display: flex; - transition: all 0.4s ease-out; + transition: all $medium-duration ease-out; margin: 0.5rem; @@ -13,20 +13,27 @@ flex-flow: row; height: 60px; -} -.queue_item_container>span { - display: flex; - align-items: center; - width: 100%; -} + span { + display: flex; + align-items: center; + width: 100%; -.queue_item_container>span:before { - text-align: center; - flex: 1 1 auto; + &:before { + text-align: center; + flex: 1 1 auto; + } + } + + &:hover { + .thumbnail_overlay { + opacity: 1; + } + } } .thumbnail_container { + position: relative; display: flex; width: 48px; height: 48px; @@ -44,6 +51,23 @@ border-radius: 48px; } +.thumbnail_overlay { + position: absolute; + display: flex; + flex-flow: row; + justify-content: center; + align-items: center; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: rgba($black, 0.85); + opacity: 0; + border-radius: 50%; + cursor: pointer; + transition: $medium-duration; +} + .item_info_container { display: flex; overflow: hidden; diff --git a/app/components/PlayQueue/QueueMenu/index.js b/app/components/PlayQueue/QueueMenu/index.js index b295f0af10..9f73f68d12 100644 --- a/app/components/PlayQueue/QueueMenu/index.js +++ b/app/components/PlayQueue/QueueMenu/index.js @@ -1,34 +1,57 @@ import React from 'react'; import FontAwesome from 'react-fontawesome'; +import _ from 'lodash'; +import InputDialog from '../../InputDialog'; import Spacer from '../../Spacer'; import styles from './styles.scss'; +import globalStyles from '../../../app.global.scss'; +import settingsConst from '../../../constants/settings'; class QueueMenu extends React.Component { constructor(props){ super(props); } - handleAddPlaylist(fun, items) { - return () => { - fun(items, Date.now()); - } + handleAddPlaylist(addPlaylist, notify, items) { + return name => { + addPlaylist(items, name); + notify( + 'Playlist created', + `Playlist ${name} has been created.` + ); + }; } - + render() { let { addPlaylist, clearQueue, - items + notify, + items, + toggleOption, + settings } = this.props; return (
- - - + toggleOption(_.find(settingsConst, ['name', 'compactQueueBar']), settings)}> + + + + + Input playlist name:} + placeholder='Playlist name...' + accept='Save' + onAccept={this.handleAddPlaylist(addPlaylist, notify, items)} + trigger={ + + } + /> +

diff --git a/app/components/PlayQueue/QueueMenu/styles.scss b/app/components/PlayQueue/QueueMenu/styles.scss index 598d31021b..9ee83aa05b 100644 --- a/app/components/PlayQueue/QueueMenu/styles.scss +++ b/app/components/PlayQueue/QueueMenu/styles.scss @@ -9,12 +9,16 @@ .queue_menu_buttons { display: flex; flex-flow: row; - margin: 0.5rem 0 0.5rem 0; + margin-top: 0.5rem; a { + display: flex; + justify-content: center; + align-items: center; background: $background2; - padding: 0.5rem 0.75rem; - margin: 0 0.5rem; + width: 2rem; + height: 2rem; + margin: 0; &:first-child { margin-left: 0; diff --git a/app/components/PlayQueue/index.js b/app/components/PlayQueue/index.js index 065237d65c..6b03d6ac35 100644 --- a/app/components/PlayQueue/index.js +++ b/app/components/PlayQueue/index.js @@ -1,8 +1,10 @@ import React from 'react'; import classnames from 'classnames'; +import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; import styles from './styles.scss'; +import QueuePopup from '../QueuePopup'; import QueueItem from './QueueItem'; import QueueMenu from './QueueMenu'; @@ -11,6 +13,10 @@ class PlayQueue extends React.Component { super(props); } + onDragEnd(result) { + this.props.actions.swapSongs(result.source.index, result.destination.index); + } + renderQueueItems() { if (!this.props.items) { return null; @@ -18,32 +24,84 @@ class PlayQueue extends React.Component { return this.props.items.map((el, i) => { return ( - + + {(provided, snapshot) => ( +
+ + } + track={el} + musicSources={this.props.plugins.plugins.musicSources} + defaultMusicSource={this.props.plugins.defaultMusicSource} + rerollTrack={this.props.actions.rerollTrack} + /> +
+ )} +
); }); } render() { + let { + compact, + items, + settings + } = this.props; + + let { + clearQueue, + addPlaylist, + toggleOption, + success + } = this.props.actions; + return ( -
- - -
- {this.renderQueueItems()} -
- -
+ +
+ + + + {(provided, snapshot) => ( +
+ {this.renderQueueItems()} + {provided.placeholder} +
+ )} +
+ +
+
); } } diff --git a/app/components/PlayQueue/styles.scss b/app/components/PlayQueue/styles.scss index 2ae316fbb5..f6a2c26b9c 100644 --- a/app/components/PlayQueue/styles.scss +++ b/app/components/PlayQueue/styles.scss @@ -1,3 +1,5 @@ +@import '../../vars.scss'; + .play_queue_container { display: flex; overflow-y: auto; @@ -10,10 +12,47 @@ flex-flow: column; flex: 1 1 auto; overflow-y: auto; - + transition: $short-duration; .queue_item_container { - transition: all 0.4s ease-out; + transition: all $medium-duration ease-out; opacity: 0; } } + + .dragged_over { + background: $background3; + } + + &.compact { + .queue_menu_buttons { + a:not(.compactButton) { + display: none; + } + } + + .queue_item_container { + margin: 0.25rem; + height: 2.5rem; + } + + .thumbnail_container { + width: 100%; + height: auto; + margin: 0; + padding: 0.25rem; + } + + .item_info_container { + display: none; + } + + .item_duration_container { + display: none; + } + + img { + width: 100%; + height: auto; + } + } } diff --git a/app/components/PlayerControls/PlayPauseButton/index.js b/app/components/PlayerControls/PlayPauseButton/index.js index 051fde9388..968650279f 100644 --- a/app/components/PlayerControls/PlayPauseButton/index.js +++ b/app/components/PlayerControls/PlayPauseButton/index.js @@ -1,22 +1,32 @@ import React from 'react'; +import classnames from 'classnames'; import FontAwesome from 'react-fontawesome'; import styles from './styles.scss'; class PlayPauseButton extends React.Component { getIcon() { - if (this.props.playing) { - return ; - } else if (this.props.loading) { - return ; + if (this.props.loading) { + return ; + } else if (this.props.playing) { + return ; } else { - return ; + return ; } } render() { return ( -
+
{ this.getIcon() } diff --git a/app/components/PlayerControls/PlayPauseButton/styles.scss b/app/components/PlayerControls/PlayPauseButton/styles.scss index a4e36a1517..bbcf003fb5 100644 --- a/app/components/PlayerControls/PlayPauseButton/styles.scss +++ b/app/components/PlayerControls/PlayPauseButton/styles.scss @@ -16,6 +16,12 @@ flex-flow: row; align-items: center; + &.loading { + a { + padding: 0; + } + } + a { flex: 1 1 auto; padding-left: 0.3rem; @@ -24,4 +30,10 @@ margin-left: -0.3rem; } } + + } + +.player_button_disabled a { + cursor: not-allowed; +} \ No newline at end of file diff --git a/app/components/PlayerControls/PlayerButton/index.js b/app/components/PlayerControls/PlayerButton/index.js index f32d0a3175..2f913d7577 100644 --- a/app/components/PlayerControls/PlayerButton/index.js +++ b/app/components/PlayerControls/PlayerButton/index.js @@ -1,5 +1,6 @@ import React from 'react'; import FontAwesome from 'react-fontawesome'; +import classNames from 'classnames'; import styles from './styles.scss'; @@ -7,7 +8,13 @@ class PlayerButton extends React.Component { render() { return ( -
+
); diff --git a/app/components/PlayerControls/PlayerButton/styles.scss b/app/components/PlayerControls/PlayerButton/styles.scss index 367a931a1b..803523f4b1 100644 --- a/app/components/PlayerControls/PlayerButton/styles.scss +++ b/app/components/PlayerControls/PlayerButton/styles.scss @@ -18,3 +18,7 @@ .player_button_container a { flex: 1 1 auto; } + +.player_button_disabled a { + cursor: not-allowed; +} diff --git a/app/components/PlayerControls/index.js b/app/components/PlayerControls/index.js index 400c9a5974..bdb63b59ba 100644 --- a/app/components/PlayerControls/index.js +++ b/app/components/PlayerControls/index.js @@ -4,17 +4,26 @@ import styles from './styles.scss'; import PlayerButton from './PlayerButton'; import PlayPauseButton from './PlayPauseButton'; -import Spacer from '../Spacer'; class PlayerControls extends React.Component { render() { return (
- - - - - + + +
); } diff --git a/app/components/PlayerControls/styles.scss b/app/components/PlayerControls/styles.scss index 8a4f69de30..7e7da05016 100644 --- a/app/components/PlayerControls/styles.scss +++ b/app/components/PlayerControls/styles.scss @@ -1,8 +1,6 @@ .player_controls_container { display: flex; - - width: 200px; - - flex: 0 0 33%; + flex: 0 0 20%; align-items: center; + justify-content: center; } diff --git a/app/components/PlaylistView/index.js b/app/components/PlaylistView/index.js index 0f9ebebfc4..f4d31570e8 100644 --- a/app/components/PlaylistView/index.js +++ b/app/components/PlaylistView/index.js @@ -1,8 +1,12 @@ import React from 'react'; import FontAwesome from 'react-fontawesome'; +import _ from 'lodash'; import ContextPopup from '../ContextPopup'; +import TrackRow from '../TrackRow'; + import Spacer from '../Spacer'; +import artPlaceholder from '../../../resources/media/art_placeholder.png'; import styles from './styles.scss'; @@ -11,26 +15,32 @@ class PlaylistView extends React.Component { super(props); } - addPlaylistToQueue(musicSources, playlist, addTracks, selectSong, startPlayback) { + addPlaylistToQueue ( + musicSources, + playlist, + addTracks, + selectSong, + startPlayback + ) { addTracks(musicSources, playlist.tracks); selectSong(0); startPlayback(); } - renderOptions(trigger, playlist) { + renderOptions (trigger, playlist) { return ( -
+ trigger={trigger} + artist={null} + title={playlist.name} + thumb={_.get(playlist, 'tracks[0].thumbnail', artPlaceholder)} + > +
); } - - render() { + + renderPlayButton () { let { playlist, addTracks, @@ -38,52 +48,100 @@ class PlaylistView extends React.Component { selectSong, startPlayback } = this.props; + return ( + + this.addPlaylistToQueue( + musicSources, + playlist, + addTracks, + selectSong, + startPlayback + ) + } + > + Play + + ); + } - let popupTrigger = (); - + renderPlaylistInfo () { + let { playlist } = this.props; + let popupTrigger = ( + + + + ); return ( -
-
-
-
- -
-
-
- {playlist.name} -
- - -
- +
+
+ +
+
+
{playlist.name}
+ +
+ {this.renderPlayButton()} + {this.renderOptions(popupTrigger, playlist)} +
+
+
+ ); + } -
+ renderPlaylistTracksHeader () { + return ( + + + + + + Artist + Title + + ); + } -
- - { - playlist.tracks.map(track => { - return ( -
- -
-
{track.artist}
-
{track.name}
-
-
- ); - }) - } - + renderTrack (track, index) { + const newTrack = _.cloneDeep(track); + _.set(newTrack, 'artist.name', newTrack.artist); + _.set(newTrack, 'image[0][\'#text\']', newTrack.thumbnail); + return (< TrackRow + key={'playlist-track-row-' + index} + track={newTrack} + index={'playlist-track-' + index} + clearQueue={this.props.clearQueue} + addToQueue={this.props.addToQueue} + startPlayback={this.props.startPlayback} + selectSong={this.props.selectSong} + musicSources={this.props.musicSources} + displayCover + displayArtist + /> + ); + } + + render () { + let { playlist } = this.props; + return ( +
+
+ {this.renderPlaylistInfo()} +
+ + {this.renderPlaylistTracksHeader()} + + {playlist.tracks.map((track, index) => this.renderTrack(track, index))} + +
+
+
-
-
); } } diff --git a/app/components/Playlists/Playlist/index.js b/app/components/Playlists/Playlist/index.js index 01f899dc5d..7c0adb04ea 100644 --- a/app/components/Playlists/Playlist/index.js +++ b/app/components/Playlists/Playlist/index.js @@ -9,7 +9,7 @@ class Playlist extends React.Component { } goToPlaylist(history, playlist) { - history.push('/playlist/' + playlist); + history.push('/playlist/' + playlist); } render() { @@ -21,7 +21,9 @@ class Playlist extends React.Component { return (
-
{this.goToPlaylist(history, index)}} className={styles.playlist_container}> +
{ + this.goToPlaylist(history, index); + }} className={styles.playlist_container}>
{playlist.tracks.length} songs
- +
diff --git a/app/components/Playlists/index.js b/app/components/Playlists/index.js index 2475b3875a..ab3aa5934c 100644 --- a/app/components/Playlists/index.js +++ b/app/components/Playlists/index.js @@ -6,24 +6,25 @@ import styles from './styles.scss'; class Playlists extends React.Component { render() { - let { - history, - playlists - } = this.props; + let { history, playlists } = this.props; return (
- { - playlists.map((playlist, i) => { - return ( - No playlists.} + {playlists && + playlists.length > 0 && + playlists.map((playlist, i) => { + return ( + - ); - }) - } + key={i} + /> + ); + })}
); } diff --git a/app/components/PluginsView/index.js b/app/components/PluginsView/index.js new file mode 100644 index 0000000000..f33628d112 --- /dev/null +++ b/app/components/PluginsView/index.js @@ -0,0 +1,61 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; +import { Dropdown, List, Segment } from 'semantic-ui-react'; +import _ from 'lodash'; + +import Header from '../Header'; +import styles from './styles.scss'; + +class PluginsView extends React.Component { + constructor(props) { + super(props); + } + + selectDefaultMusicSource(e, data) { + this.props.actions.selectDefaultMusicSource(data.value); + } + + render() { + let { + actions, + plugins, + defaultMusicSource + } = this.props; + + let dropdownOptions = plugins.musicSources.map(s => { + return { + text: s.name, + value: s.sourceName + }; + }); + + let defaultOption = _.find(dropdownOptions, {value: defaultMusicSource}); + defaultOption = defaultOption || dropdownOptions[0]; + + return ( +
+
+ Plugins +
+
+
+ Music sources +
+ + + Select the default music source: + {' '} + + +
+
+ ); + } +} + +export default PluginsView; diff --git a/app/components/PluginsView/styles.scss b/app/components/PluginsView/styles.scss new file mode 100644 index 0000000000..c0793433f6 --- /dev/null +++ b/app/components/PluginsView/styles.scss @@ -0,0 +1,55 @@ +@import '../../vars'; + +.plugins_view_container { + display: flex; + flex-flow: column; + + width: 100%; + height: 100%; + + .menu.transition.visible { + height: max-content; + background-color: $background2; + padding: 0; + } + + .text { + color: $white; + } + + .plugin_settings { + display: flex; + flex-flow: column; + + margin-top: 1rem; + + .header_container { + font-size: 24px; + } + } + + .ui.list .item { + display: flex; + flex-flow: row; + padding-bottom: 0.5rem; + + &:first-child { + padding-bottom: 0.5rem; + } + } + + .plugin_index { + display: flex; + justify-content: center; + align-items: center; + padding-right: 1rem; + } + + .plugin_buttons { + display: flex; + flex-flow: row; + justify-content: flex-end; + align-items: center; + flex: 1 1 auto; + } +} diff --git a/app/components/QueuePopup/index.js b/app/components/QueuePopup/index.js new file mode 100644 index 0000000000..c630ca453e --- /dev/null +++ b/app/components/QueuePopup/index.js @@ -0,0 +1,145 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; +import _ from 'lodash'; +import { Dropdown, Popup } from 'semantic-ui-react'; +import { getSelectedStream } from '../../utils'; + +import styles from './styles.scss'; + +class QueuePopup extends React.Component { + constructor(props) { + super(props); + + this.state = { + isOpen: false + }; + } + + toggleOpen() { + this.setState({ + isOpen: !this.state.isOpen + }); + this.container.click(); + } + + handleClose() { + this.setState({ isOpen: false }); + } + + rerollTrack(track) { + let selectedStream = getSelectedStream( + track.streams, + this.props.defaultMusicSource + ); + let musicSource = _.find( + this.props.musicSources, + s => s.sourceName === selectedStream.source + ); + this.props.rerollTrack(musicSource, selectedStream, track); + } + + renderStreamSourceDropdown() { + let { track, musicSources, defaultMusicSource } = this.props; + + let dropdownOptions = _.map(musicSources, s => { + return { + key: s.sourceName, + text: s.sourceName, + value: s.sourceName, + content: s.sourceName + }; + }); + + let selectedStream = getSelectedStream(track.streams, defaultMusicSource); + return ( +
+ {' '} + o.value === selectedStream.source) + .value + } + /> +
+ ); + } + + renderStreamRefreshButton() { + let { track } = this.props; + return ( + + ); + } + + renderStreamInfo() { + let { trigger, track, musicSources, defaultMusicSource } = this.props; + let selectedStream = getSelectedStream(track.streams, defaultMusicSource); + return ( +
+
+ +
+
+ {this.renderStreamSourceDropdown()} +
+ + {selectedStream.title} +
+
+ + {selectedStream.id} +
+
+ {this.renderStreamRefreshButton()} +
+ ); + } + + renderPopupTrigger() { + let { trigger } = this.props; + return ( +
{ + this.container = element; + }} + > + {trigger} +
+ ); + } + + render() { + let { trigger, track, defaultMusicSource } = this.props; + let selectedStream = getSelectedStream(track.streams, defaultMusicSource); + + return ( +
+ + {track.streams && selectedStream ? ( + this.renderStreamInfo() + ) : ( +
Stream still loading.
+ )} +
+
+ ); + } +} + +export default QueuePopup; diff --git a/app/components/QueuePopup/styles.scss b/app/components/QueuePopup/styles.scss new file mode 100644 index 0000000000..eda2fe609c --- /dev/null +++ b/app/components/QueuePopup/styles.scss @@ -0,0 +1,97 @@ +@import '../../vars'; + +.queue_popup { + background-color: $background !important; + color: $white !important; + border-color: $background2 !important; + box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.18), + 0px 0px 20px 0px rgba(0, 0, 0, 0.09) !important; + height: auto !important; + transition: $very-short-duration; + + &:before { + background-color: $background !important; + box-shadow: 1px -1px 0 0 $background2 !important; + } + + .stream_info { + position: relative; + display: block; + } + + .stream_text_info { + position: absolute; + top: 0; + bottom: 0; + width: 100%; + display: flex; + flex-flow: column; + padding: 0.5rem; + background: rgba($black, 0.85); + border-radius: 0.25rem; + } + + .menu.transition.visible { + height: max-content; + background-color: $background2; + padding: 0; + } + + .text { + color: $white; + } + + label { + color: rgba($white, 0.25); + font-size: 12px; + } + + .stream_source { + display: flex; + flex-flow: column; + } + + .stream_title { + display: flex; + flex-flow: column; + } + + .stream_id { + display: flex; + flex-flow: column; + } + + .stream_thumbnail { + display: flex; + flex-flow: row; + justify-content: center; + align-items: center; + width: 100%; + border-radius: 0.25rem; + overflow: hidden; + } + + img { + width: 100%; + height: auto; + } + + .stream_buttons { + position: absolute; + top: 0; + bottom: 0; + width: 100%; + display: flex; + flex-flow: column; + justify-content: flex-end; + align-items: flex-end; + padding: 0.5rem; + pointer-events: none; + + a { + background: rgba($black, 0.5); + padding: 0.5rem 0.75rem; + pointer-events: all; + } + } +} diff --git a/app/components/SearchBox/index.js b/app/components/SearchBox/index.js index cde1029308..7e546142a7 100644 --- a/app/components/SearchBox/index.js +++ b/app/components/SearchBox/index.js @@ -5,25 +5,24 @@ import DebounceInput from 'react-debounce-input'; import styles from './styles.scss'; class SearchBox extends React.Component { - render() { return (
- -
+ +
{e.preventDefault(); this.props.handleSearch(e);}} + onChange={this.props.handleSearch} autoFocus /> { this.props.loading - ? - : null + ? + : null } - +
); } diff --git a/app/components/SearchBox/styles.scss b/app/components/SearchBox/styles.scss index 00f1029ffc..e850765926 100644 --- a/app/components/SearchBox/styles.scss +++ b/app/components/SearchBox/styles.scss @@ -16,7 +16,6 @@ margin: 12px 0px 12px 12px; color: $grey; - border-radius: 2px 0px 0px 2px; background-color: $blue; align-items: center; @@ -29,7 +28,7 @@ flex: 1 1 auto; } -.search_box_container form { +.search_box_container .form { display: flex; flex: 1 1 auto; @@ -43,7 +42,6 @@ color: $grey; border: none; - border-radius: 0px 2px 2px 0px; outline: none; background-color: $background3; @@ -53,16 +51,19 @@ flex: 1 1 auto; } -form>span { - position: relative; - right: 30px; +form, +.form { + > span { + position: relative; + right: 30px; - display: flex !important; + display: flex !important; - width: 30px; - height: 30px; + width: 30px; + height: 30px; - align-items: center; + align-items: center; + } } ::-webkit-input-placeholder { diff --git a/app/components/SearchResults/AllResults/index.js b/app/components/SearchResults/AllResults/index.js index 5d91825932..9349cd99a1 100644 --- a/app/components/SearchResults/AllResults/index.js +++ b/app/components/SearchResults/AllResults/index.js @@ -1,6 +1,9 @@ import React from 'react'; +import artPlaceholder from '../../../../resources/media/art_placeholder.png'; import Card from '../../Card'; +import PlaylistResults from '../PlaylistResults'; +import TracksResults from '../TracksResults'; import styles from './styles.scss'; @@ -8,46 +11,95 @@ class AllResults extends React.Component { constructor(props) { super(props); } + renderResults (collection, onClick) { + return collection.slice(0, 5).map((el, i) => { + return ( + onClick(el.id, el.type)} + key={'item-' + i} + /> + ); + }); + } + + renderTracks (arr = [], limit = 5) { + return (); - renderResults(collection, onClick) { + } + + renderPlaylistSection () { return ( - collection.slice(0, 5).map((el, i) => { - return ( - onClick(el.id)} - key={i} - /> - ) - }) - ); +
+

Playlist

+
+ +
+
); } - render() { - if (this.props.artistSearchResults.length <= 0 && - this.props.albumSearchResults.length <=0 ) { - return (
Nothing found.
); - } + renderSection (title, collection, onClick) { + return (
+

{title}

+
+ {this.renderResults( + collection, + onClick + )} +
+
); + } - return ( -
+ renderArtistsSection () { + return this.renderSection('Artists', this.props.artistSearchResults, this.props.artistInfoSearch); + } -
-

Artists

-
- { this.renderResults(this.props.artistSearchResults, this.props.artistInfoSearch) } -
-
+ renderAlbumsSection () { + return this.renderSection('Albums', this.props.albumSearchResults, this.props.albumInfoSearch); + } -
-

Albums

-
- { this.renderResults(this.props.albumSearchResults, this.props.albumInfoSearch) } -
-
+ renderTracksSection () { + return (
+

Tracks

+
+ {this.renderTracks(this.props.trackSearchResults.info)} +
+
); + } + + render () { + if ( + this.props.artistSearchResults.length <= 0 && + this.props.albumSearchResults.length <= 0 && + this.props.trackSearchResults.length <= 0 + ) { + return
Nothing found.
; + } + return ( +
+ {this.renderArtistsSection()} + {this.renderAlbumsSection()} + {this.renderTracksSection()} + {this.renderPlaylistSection()}
); } diff --git a/app/components/SearchResults/PlaylistResults/index.js b/app/components/SearchResults/PlaylistResults/index.js new file mode 100644 index 0000000000..00d4a28157 --- /dev/null +++ b/app/components/SearchResults/PlaylistResults/index.js @@ -0,0 +1,70 @@ +import React from 'react'; + +import TracksResults from '../TracksResults'; +import FontAwesome from 'react-fontawesome'; +import artPlaceholder from '../../../../resources/media/art_placeholder.png'; +import _ from 'lodash'; + +import styles from './styles.scss'; + +class PlaylistResults extends React.Component { + constructor(props) { + super(props); + } + + addTrack (track) { + if (typeof track !== 'undefined') { + this.props.addToQueue(this.props.musicSources, { + artist: track.artist, + name: track.name, + thumbnail: _.get(track, 'image[1][\'#text\']', artPlaceholder) + }); + } + } + renderAddAllButton (tracks) { + return (tracks.length > 0 ? { + tracks + .map(track => { + this.addTrack(track); + }); + }} + className={styles.add_button} + aria-label='Add all tracks to queue' + > + Add all + : null + ); + } + + renderLoading () { + return (
Loading...
); + } + + renderResults () { + return (
+ {this.renderAddAllButton(this.props.playlistSearchResults.info)} +
); + } + + renderNoResult () { + return (
No result
); + } + render () { + return ( + this.props.playlistSearchStarted ? ((this.props.playlistSearchStarted.length > 0 && typeof this.props.playlistSearchResults.info === 'undefined') ? this.renderLoading() : this.renderResults()) : this.renderNoResult() + ); + } +} + +export default PlaylistResults; diff --git a/app/components/SearchResults/PlaylistResults/styles.scss b/app/components/SearchResults/PlaylistResults/styles.scss new file mode 100644 index 0000000000..40f092eaf1 --- /dev/null +++ b/app/components/SearchResults/PlaylistResults/styles.scss @@ -0,0 +1 @@ +.add_button{} diff --git a/app/components/SearchResults/TracksResults/index.js b/app/components/SearchResults/TracksResults/index.js new file mode 100644 index 0000000000..d46a321f22 --- /dev/null +++ b/app/components/SearchResults/TracksResults/index.js @@ -0,0 +1,47 @@ +import React from 'react'; +import _ from 'lodash'; + +import TrackRow from '../../TrackRow'; + +class TracksResults extends React.Component { + constructor(props) { + super(props); + } + + render () { + let collection = this.props.tracks || []; + let limit = this.props.limit; + if (collection.length === 0) { + return 'No result'; + } else { + return ( + + + {(collection || []).slice(0, limit).map((track, index) => { + if (track && _.hasIn(track, 'name') & _.hasIn(track, 'image') && _.hasIn(track, 'artist')) { + const newTrack = _.cloneDeep(track); + if (!newTrack.artist.name) { + _.set(newTrack, 'artist.name', newTrack.artist); + } + return < TrackRow + key={'search-result-track-row-' + index} + track={newTrack} + index={'popular-track-' + index} + clearQueue={this.props.clearQueue} + addToQueue={this.props.addToQueue} + startPlayback={this.props.startPlayback} + selectSong={this.props.selectSong} + musicSources={this.props.musicSources} + displayArtist + displayCover + />; + } + })} + +
+ ); + } + } +} + +export default TracksResults; diff --git a/app/components/SearchResults/TracksResults/styles.scss b/app/components/SearchResults/TracksResults/styles.scss new file mode 100644 index 0000000000..361a56854a --- /dev/null +++ b/app/components/SearchResults/TracksResults/styles.scss @@ -0,0 +1,92 @@ + +@import '../../../vars'; + +.all_results_container { + display: flex; + flex-flow: row wrap; + justify-content: space-around; + width: 100%; + + .column { + display: flex; + flex-flow: column; + width: 100%; + + h3 { + font-variant: small-caps; + margin: 1rem; + } + + .row { + display: flex; + flex-flow: row wrap; + } + } +} + + +.popular_tracks_container { + display: flex; + flex: 1 1 auto; + flex-flow: column; + + margin: 0 0.5rem; + + .header { + margin-bottom: 1rem; + + font-size: 16px; + font-variant: small-caps; + } + + .track_row { + display: flex; + align-items: center; + flex-flow: row; + + transition: $short-duration ease-in-out; + + border-bottom: 1px solid rgba($background2, 0.2); + + &:hover { + background: lighten($background, 10%); + } + + img { + flex: 0 0 auto; + + width: 3rem; + height: 3rem; + } + + .popular_track_name { + flex: 1 1 auto; + margin: 0.25rem 0.5rem; + text-align: left; + } + + .playcount { + flex: 1 1 auto; + margin: 0.25rem 0.5rem; + text-align: right; + text-transform: uppercase; + } + } + + .expand_button { + display: flex; + justify-content: center; + padding: 0.5rem; + margin-top: 0.5rem; + transition: $short-duration; + cursor: pointer; + + &:hover { + background: lighten($background, 5%); + } + } +} + +.add_button { + text-align: left; +} diff --git a/app/components/SearchResults/index.js b/app/components/SearchResults/index.js index ed378852c7..47dab5ee6e 100644 --- a/app/components/SearchResults/index.js +++ b/app/components/SearchResults/index.js @@ -2,33 +2,43 @@ import React from 'react'; import { Tab } from 'semantic-ui-react'; import AllResults from './AllResults'; +import TracksResults from './TracksResults'; +import PlaylistResults from './PlaylistResults'; import Card from '../Card'; import styles from './styles.scss'; class SearchResults extends React.Component { - - renderAllResultsPane() { + renderAllResultsPane () { return (
- +
+ +
-
+ ); } - renderPane(collection, onClick) { + renderPane (collection, onClick) { return (
- { - collection.length > 0 + {collection.length > 0 ? this.props.unifiedSearchStarted ? null : collection.map((el, i) => { @@ -40,53 +50,112 @@ class SearchResults extends React.Component { } return ( onClick(el.id)} + onClick={() => onClick(el.id, el.type)} /> - ) + ); }) - : 'Nothing found.' - } + : 'Nothing found.'}
); } - panes() { - var panes = [ + renderLastFmPane (collection) { + if (typeof collection !== 'undefined') { + + return ( + +
+ {collection.length > 0 + ? this.props.unifiedSearchStarted + ? null + : + : 'Nothing found.'} +
+
+ ); + } else { + return ( + +
No result
+
+ ); + } + } + + + renderPlaylistPane () { + return (); + } + + panes () { + let panes = [ { menuItem: 'All', render: () => this.renderAllResultsPane() }, { menuItem: 'Artists', - render: () => this.renderPane(this.props.artistSearchResults, this.artistInfoSearch.bind(this)) + render: () => + this.renderPane( + this.props.artistSearchResults, + this.artistInfoSearch.bind(this) + ) }, { menuItem: 'Albums', - render: () => this.renderPane(this.props.albumSearchResults, this.albumInfoSearch.bind(this)) + render: () => + this.renderPane( + this.props.albumSearchResults, + this.albumInfoSearch.bind(this) + ) + }, + { + menuItem: 'Tracks', + render: () => this.renderLastFmPane(this.props.trackSearchResults.info) + }, + { + menuItem: 'Playlist', + render: () => this.renderPlaylistPane(this.props.playlistSearchResults) } ]; return panes; } - albumInfoSearch(albumId) { - this.props.albumInfoSearch(albumId); + albumInfoSearch (albumId, releaseType) { + this.props.albumInfoSearch(albumId, releaseType); this.props.history.push('/album/' + albumId); } - artistInfoSearch(artistId) { + artistInfoSearch (artistId) { this.props.artistInfoSearch(artistId); this.props.history.push('/artist/' + artistId); } - render() { + render () { return (
- +
); } diff --git a/app/components/SearchResults/styles.scss b/app/components/SearchResults/styles.scss index 7dbaf17ea3..545cc61219 100644 --- a/app/components/SearchResults/styles.scss +++ b/app/components/SearchResults/styles.scss @@ -3,5 +3,5 @@ flex-flow: row wrap; position: relative; height: 100%; - justify-content: space-around; + justify-content: flex-start; } diff --git a/app/components/Seekbar/index.js b/app/components/Seekbar/index.js index 871cba6f13..9513713d53 100644 --- a/app/components/Seekbar/index.js +++ b/app/components/Seekbar/index.js @@ -4,14 +4,13 @@ import styles from './styles.scss'; class Seekbar extends React.Component { - handleClick(seek, queue) { - return event => { - let percent = (event.pageX - event.target.offsetLeft)/document.body.clientWidth; - let duration = queue.queueItems[queue.currentSong].streams[0].duration; - seek(percent * duration * 1000); - } - - } + handleClick(seek, queue) { + return event => { + let percent = (event.pageX - event.target.offsetLeft)/document.body.clientWidth; + let duration = queue.queueItems[queue.currentSong].streams[0].duration; + seek(percent * duration * 1000); + }; + } render() { return ( diff --git a/app/components/Seekbar/styles.scss b/app/components/Seekbar/styles.scss index 993d72edfa..32da6beaf4 100644 --- a/app/components/Seekbar/styles.scss +++ b/app/components/Seekbar/styles.scss @@ -1,17 +1,15 @@ @import "../../vars"; .seekbar_container { - height: 4px; + height: 1em; cursor: pointer; - background-color: transparent; + background-color: $background2; flex: 0 0 auto; } .seekbar_fill { - height: 4px; - background-color: $pink; } diff --git a/app/components/Settings/index.js b/app/components/Settings/index.js index b548b33381..54a21e626f 100644 --- a/app/components/Settings/index.js +++ b/app/components/Settings/index.js @@ -1,102 +1,235 @@ import React from 'react'; import FontAwesome from 'react-fontawesome'; -import { Button, Radio } from 'semantic-ui-react'; +import { Button, Input, Radio } from 'semantic-ui-react'; +import Range from 'react-range-progress'; +import cx from 'classnames'; +import _ from 'lodash'; import Header from '../Header'; import Spacer from '../Spacer'; +import settingsEnum from '../../constants/settingsEnum'; import styles from './styles.scss'; +const volumeSliderColors = { + fillColor: { r: 248, g: 248, b: 242, a: 1 }, + trackColor: { r: 68, g: 71, b: 90, a: 1 }, + thumbColor: { r: 248, g: 248, b: 242, a: 1 } +}; + class Settings extends React.Component { - toggleScrobbling(lastFmScrobblingEnabled, enableScrobbling, disableScrobbling) { - lastFmScrobblingEnabled ? disableScrobbling(): enableScrobbling(); + toggleScrobbling ( + lastFmScrobblingEnabled, + enableScrobbling, + disableScrobbling + ) { + lastFmScrobblingEnabled ? disableScrobbling() : enableScrobbling(); + } + + isChecked (option) { + return typeof this.props.settings[option.name] !== 'undefined' + ? this.props.settings[option.name] + : option.default; + } + + getOptionValue (option) { + return this.props.settings[option.name]; + } + + validateNumberInput (value) { + const intValue = _.parseInt(value); + return _.isNull(value) || !_.isNaN(intValue); + } + + renderLastFmTitle () { + return ( +
+ +
+ ); } - render() { - const { + renderLastFmLoginButtons () { + let { lastFmAuthToken, lastFmName, - lastFmSessionKey, - lastFmScrobblingEnabled + lastFmSessionKey } = this.props.scrobbling; + const { lastFmConnectAction, lastFmLoginAction } = this.props.actions; + return ( +
+ + User: {lastFmName ? lastFmName : 'Not logged in'} + + + {!lastFmSessionKey && ( + + )} + {!lastFmSessionKey && ( + + )} +
+ ); + } - const { - lastFmConnectAction, - lastFmLoginAction, - enableScrobbling, - disableScrobbling - } = this.props.actions; + renderLastFmOptionRadio () { + let { lastFmScrobblingEnabled } = this.props.scrobbling; + const { enableScrobbling, disableScrobbling } = this.props.actions; + return ( +
+ + + + this.toggleScrobbling( + lastFmScrobblingEnabled, + enableScrobbling, + disableScrobbling + ) + } + /> +
+ ); + } + renderSocialSettings () { return ( -
-
-
- Social -
-
-
- -
- -
-

- In order to enable scrobbling, you first have to connect and authorize nuclear on Last.fm, then click log in. -

-
- -
- User: {lastFmName ? lastFmName : 'Not logged in'} - - { - lastFmSessionKey - ? null - : - } - { - lastFmSessionKey - ? null - : - } -
- -
- - - this.toggleScrobbling(lastFmScrobblingEnabled, enableScrobbling, disableScrobbling)} - /> -
-
+
+
Social
+
+ {this.renderLastFmTitle()} -
-
- Playback -
-
-
-
-
- Program settings -
-
-
- - - -
+
+

+ In order to enable scrobbling, you first have to connect and + authorize nuclear on Last.fm, then click log in. +

+ + {this.renderLastFmLoginButtons()} + {this.renderLastFmOptionRadio()} +
+ ); + } + + handleSliderChange (value, option) { + this.props.actions.setNumberOption(option.name, _.parseInt(value)); + } + + renderRadioOption (option, settings) { + return ( this.props.actions.toggleOption(option, settings)} + checked={this.isChecked(option)} + />); + } + + renderStringOption (option) { + return ( this.props.actions.setStringOption(option.name, e.target.value) + } + />); + } + + renderSliderOption (option) { + return (
+ Value : {this.getOptionValue(option) || option.default} {option.unit} + this.handleSliderChange(e, option)} + /> +
); + } + renderNumberOption (option) { + if (typeof option.unit === 'string') { + return this.renderSliderOption(option); + } else { + const value = this.getOptionValue(option); + + return ( !!e.target.value && this.validateNumberInput(value) && this.props.actions.setNumberOption(option.name, _.parseInt(e.target.value)) + } + />); + } + } + renderOption (settings, option, key) { + return ( +
+ + + { + option.type === settingsEnum.BOOLEAN && + this.renderRadioOption(option, settings) + } + { + option.type === settingsEnum.STRING && + this.renderStringOption(option) + } + { + option.type === settingsEnum.NUMBER && + this.renderNumberOption(option) + }
+ ); + } + + render () { + let { options, settings } = this.props; + let optionsGroups = _.groupBy(options, 'category'); + return ( +
+ {this.renderSocialSettings()} + {_.map(optionsGroups, (group, i) => { + return ( +
+
{i}
+
+ {_.map(group, (option, j) => + this.renderOption(settings, option, j) + )} +
+ ); + })} +
); } } diff --git a/app/components/Settings/styles.scss b/app/components/Settings/styles.scss index ca5b7fef32..820c795707 100644 --- a/app/components/Settings/styles.scss +++ b/app/components/Settings/styles.scss @@ -16,12 +16,22 @@ } .settings_item { + position: relative; display: flex; flex-flow: row; align-items: baseline; justify-content: left; margin: 2rem 0; + &.string, + &.number { + flex-flow: column; + + label { + margin-bottom: 1em; + } + } + p { font-size: 14px; font-weight: lighter; @@ -35,6 +45,17 @@ align-self: flex-end; flex: 0 0 auto; } + + .ui.fluid.input { + width: 100%; + + input { + color: $grey; + border: none; + border-radius: 0px 2px 2px 0px; + background-color: $background3; + } + } } .lastfm_icon { @@ -51,4 +72,8 @@ hr { width: 100%; } + + .slider_container{ + width:100%; + } } diff --git a/app/components/SidebarMenu/SidebarMenuItem/index.js b/app/components/SidebarMenu/SidebarMenuItem/index.js index 302d0ef04e..7aa71beccc 100644 --- a/app/components/SidebarMenu/SidebarMenuItem/index.js +++ b/app/components/SidebarMenu/SidebarMenuItem/index.js @@ -1,6 +1,6 @@ import React from 'react'; -import styles from './styles.css'; +import styles from './styles.scss'; class SidebarMenuItem extends React.Component { constructor(props){ diff --git a/app/components/SidebarMenu/SidebarMenuItem/styles.css b/app/components/SidebarMenu/SidebarMenuItem/styles.css deleted file mode 100644 index 2ddef0b773..0000000000 --- a/app/components/SidebarMenu/SidebarMenuItem/styles.css +++ /dev/null @@ -1,5 +0,0 @@ -.sidebar_menu_item_container { - margin: 12px; - - flex: 0 0 auto; -} diff --git a/app/components/SidebarMenu/SidebarMenuItem/styles.scss b/app/components/SidebarMenu/SidebarMenuItem/styles.scss new file mode 100644 index 0000000000..9b0a3532bc --- /dev/null +++ b/app/components/SidebarMenu/SidebarMenuItem/styles.scss @@ -0,0 +1,24 @@ +@import '../../../vars.scss'; + +.sidebar_menu_item_container { + display: flex; + flex: 0 0 auto; + flex-flow: row; + justify-content: flex-start; + + padding: 0.75rem; + + transition: $short-duration; + letter-spacing: 0.5px; + + font-size: 1.25rem; + font-variant: small-caps; + + &:hover { + background: $background3; + } + + span { + margin-right: 0.75rem; + } +} diff --git a/app/components/SidebarMenu/index.js b/app/components/SidebarMenu/index.js index 3c72688530..79c01d25e7 100644 --- a/app/components/SidebarMenu/index.js +++ b/app/components/SidebarMenu/index.js @@ -1,6 +1,6 @@ import React from 'react'; -import styles from './styles.css'; +import styles from './styles.scss'; import SidebarMenuItem from './SidebarMenuItem'; @@ -9,20 +9,13 @@ class SidebarMenu extends React.Component { super(props); } - renderItems(){ - return this.props.children.map((el, i) => { - return ( - - {el} - - ); - }); - } - render() { + let { + children + } = this.props; return (
- { this.renderItems() } + { children }
); } diff --git a/app/components/SidebarMenu/styles.css b/app/components/SidebarMenu/styles.scss similarity index 56% rename from app/components/SidebarMenu/styles.css rename to app/components/SidebarMenu/styles.scss index 11b1c567fe..c760ad074f 100644 --- a/app/components/SidebarMenu/styles.css +++ b/app/components/SidebarMenu/styles.scss @@ -1,5 +1,11 @@ +@import '../../vars'; + .sidebar_menu_container { flex: 1 1 auto; display: flex; flex-flow: column; + + a { + color: rgba($white, 0.5); + } } diff --git a/app/components/Spacer/index.js b/app/components/Spacer/index.js index 1f5a5eda5c..5b24fc2ab4 100644 --- a/app/components/Spacer/index.js +++ b/app/components/Spacer/index.js @@ -3,7 +3,7 @@ import React from 'react'; class Spacer extends React.Component { render() { return ( -
+
); } } diff --git a/app/components/TagView/TagDescription/index.js b/app/components/TagView/TagDescription/index.js new file mode 100644 index 0000000000..f5d49339ae --- /dev/null +++ b/app/components/TagView/TagDescription/index.js @@ -0,0 +1,23 @@ +import React from 'react'; + +import styles from './styles.scss'; + +class TagDescription extends React.Component { + constructor(props) { + super(props); + } + + render() { + let { + tagInfo + } = this.props; + + return ( +
+ {tagInfo.wiki.summary.split('.').slice(0, -5).join('.')+'...'} +
+ ); + } +} + +export default TagDescription; diff --git a/app/components/TagView/TagDescription/styles.scss b/app/components/TagView/TagDescription/styles.scss new file mode 100644 index 0000000000..1d3d4fb55f --- /dev/null +++ b/app/components/TagView/TagDescription/styles.scss @@ -0,0 +1,9 @@ +@import '../../../vars'; + +.tag_description { + display: flex; + padding: 1rem; + margin: 1rem 0; + border-radius: 0.25rem; + background-color: $background2; +} diff --git a/app/components/TagView/TagHeader/index.js b/app/components/TagView/TagHeader/index.js new file mode 100644 index 0000000000..34dcf4bd73 --- /dev/null +++ b/app/components/TagView/TagHeader/index.js @@ -0,0 +1,31 @@ +import React from 'react'; +import _ from 'lodash'; + +import styles from './styles.scss'; + +class TagHeader extends React.Component { + constructor(props) { + super(props); + } + + render() { + let { + tag, + tagInfo, + topArtists + } = this.props; + return ( +
+
+
+ #{tag} +
+
+ ); + } +} + +export default TagHeader; diff --git a/app/components/TagView/TagHeader/styles.scss b/app/components/TagView/TagHeader/styles.scss new file mode 100644 index 0000000000..0e6599c662 --- /dev/null +++ b/app/components/TagView/TagHeader/styles.scss @@ -0,0 +1,30 @@ +.tag_header_container { + position: relative; + display: flex; + flex-flow: column; + height: 25rem; + width: 100%; + + .tag_header_background { + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; + background-size: cover; + background-position: center; + filter: brightness(40%); + } + + .tag_header_name { + display: flex; + flex-flow: row; + justify-content: flex-start; + align-items: flex-end; + height: 100%; + font-size: 48px; + font-weight: normal; + z-index: 10; + padding: 2rem; + } +} diff --git a/app/components/TagView/TagTopList/index.js b/app/components/TagView/TagTopList/index.js new file mode 100644 index 0000000000..6584879d8f --- /dev/null +++ b/app/components/TagView/TagTopList/index.js @@ -0,0 +1,57 @@ +import React from 'react'; +import Img from 'react-image-smooth-loading'; +import classnames from 'classnames'; +import _ from 'lodash'; + +import styles from './styles.scss'; + +class TagTopList extends React.Component { + constructor(props) { + super(props); + } + + render() { + let { + topList, + onClick, + header + } = this.props; + + return ( +
+

{header}

+
+
onClick && onClick(topList[0].name)}> + +
+
{topList[0].name}
+
+
+
+ { + topList.slice(1, 5).map((item, i) => { + return ( +
onClick && onClick(item.name)} + > + +
+
{item.name}
+
+
+ ); + }) + } +
+
+
+ ); + } +} + +export default TagTopList; diff --git a/app/components/TagView/TagTopList/styles.scss b/app/components/TagView/TagTopList/styles.scss new file mode 100644 index 0000000000..b558f60c60 --- /dev/null +++ b/app/components/TagView/TagTopList/styles.scss @@ -0,0 +1,105 @@ +@import '../../../vars'; + +.tag_top_list { + position: relative; + display: flex; + flex-flow: column; + margin-bottom: 2rem; + flex: 1 1 auto; + + h4 { + font-size: 2rem; + } + + .top_list_items { + position: relative; + display: flex; + flex-flow: row; + width: 100%; + padding: 0 1rem 0 0.5rem; + } + + .top_item { + position: relative; + width: 50%; + height: 0; + padding-bottom: 50%; + border-radius: 0.25rem; + overflow: hidden; + cursor: pointer; + } + + .top_item_photo { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-size: cover; + background-position: center; + } + + .item_overlay { + position: absolute; + display: flex; + flex-flow: row; + justify-content: flex-end; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding: 1rem; + background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.65) 100%); + z-index: 10; + + &:hover { + background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(#202020, 0.65) 100%); + } + } + + .item_name { + position: relative; + display: flex; + flex-flow: row; + align-items: flex-end; + width: 100%; + padding: 1rem; + font-size: 3rem; + line-height: 3rem; + z-index: 20; + color: $white; + } + + .other_items { + position: relative; + display: flex; + flex-flow: row wrap; + width: 50%; + height: 0; + padding-bottom: 50%; + } + + .other_item { + position: relative; + width: 50%; + height: 0; + padding-bottom: 50%; + cursor: pointer; + } + + .other_item_photo { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-size: cover; + background-position: center; + } + + .other_item_name { + font-size: 1.5rem; + line-height: 1.5rem; + padding: 0.5rem; + } +} diff --git a/app/components/TagView/TagTopTracks/index.js b/app/components/TagView/TagTopTracks/index.js new file mode 100644 index 0000000000..7e9a3492dc --- /dev/null +++ b/app/components/TagView/TagTopTracks/index.js @@ -0,0 +1,75 @@ +import React from 'react'; +import FontAwesome from 'react-fontawesome'; + +import styles from './styles.scss'; +import TrackRow from '../../TrackRow'; + +class TagTopTracks extends React.Component { + constructor(props) { + super(props); + } + + renderAddAllButton () { + let { tracks, addToQueue, musicSources } = this.props; + return ( + { + tracks.map((track, i) => { + addToQueue(musicSources, { + artist: track.artist.name, + name: track.name, + thumbnail: track.image[1]['#text'] + }); + }); + }} + aria-label='Add all tracks to queue' + > + Add all to queue + + ); + } + + render () { + let { tracks } = this.props; + return ( +
+ {this.renderAddAllButton()} + + + + + + + + + + + {tracks.map((track, index) => { + return < TrackRow + key={'tag-track-row-' + index} + track={track} + index={'popular-track-' + index} + artist={track.artist} + clearQueue={this.props.clearQueue} + addToQueue={this.props.addToQueue} + startPlayback={this.props.startPlayback} + selectSong={this.props.selectSong} + musicSources={this.props.musicSources} + displayCover + displayArtist + displayDuration + />; + })} + +
+ + ArtistTitleDuration
+
+ ); + } +} + +export default TagTopTracks; diff --git a/app/components/TagView/TagTopTracks/styles.scss b/app/components/TagView/TagTopTracks/styles.scss new file mode 100644 index 0000000000..f842d0c156 --- /dev/null +++ b/app/components/TagView/TagTopTracks/styles.scss @@ -0,0 +1,43 @@ +@import '../../../vars'; + +.tag_top_tracks { + position: relative; + display: flex; + flex-flow: column; + width: 100%; + + .track { + transition: $short-duration; + } + + .track_thumbnail { + width: 3rem; + height: 3rem; + } + + table { + width: 100%; + } + + thead { + background: $background2; + } + + th { + padding: 1rem; + text-align: left; + } + + tr { + transition: $short-duration; + } + + tr:hover { + background: rgba($background2, 0.25); + } + + td { + padding: 1rem 1rem; + border-bottom: 1px solid $background2; + } +} diff --git a/app/components/TagView/index.js b/app/components/TagView/index.js new file mode 100644 index 0000000000..2f9df75234 --- /dev/null +++ b/app/components/TagView/index.js @@ -0,0 +1,113 @@ +import React from 'react'; +import { Dimmer, Loader } from 'semantic-ui-react'; + +import TagDescription from './TagDescription'; +import TagHeader from './TagHeader'; +import TagTopList from './TagTopList'; +import TagTopTracks from './TagTopTracks'; +import styles from './styles.scss'; + +class TagView extends React.Component { + constructor(props) { + super(props); + } + + componentDidMount () { + this.props.loadTagInfo(this.props.tag); + } + + artistInfoSearchByName (artistName) { + this.props.artistInfoSearchByName(artistName, this.props.history); + } + + + albumInfoSearchByName (albumName) { + this.props.albumInfoSearchByName(albumName, this.props.history); + } + + renderTagHeader (tagInfo, topArtists) { + let { tag } = this.props; + return ; + } + + renderTopArtists (topArtists) { + return ( + + ); + } + + + renderTopAlbums (topAlbums) { + return ( + + ); + } + + renderTagTopTracks (topTracks, addToQueue, musicSources) { + + return ( + + ); + } + + renderTopArtistsAndTopAlbums (topArtists, topAlbums) { + return ( +
+ {this.renderTopArtists(topArtists)} + {this.renderTopAlbums(topAlbums)} +
+ ); + } + + renderDimmer () { + let { tag, tags } = this.props; + return ( + + + + ); + } + + render () { + let { addToQueue, tag, tags, musicSources } = this.props; + let tagInfo, topTracks, topAlbums, topArtists; + if (tags[tag] && tags[tag].loading !== true) { + tagInfo = tags[tag][0].tag; + topTracks = tags[tag][1].tracks.track; + topAlbums = tags[tag][2].albums.album; + topArtists = tags[tag][3].topartists.artist; + } + return ( +
+ + {this.renderDimmer()} + {typeof tags[tag] === 'undefined' || tags[tag].loading ? null : ( +
+ {this.renderTagHeader(tagInfo, topArtists)} + + {this.renderTopArtistsAndTopAlbums(topArtists, topAlbums)} + {this.renderTagTopTracks(topTracks, addToQueue, musicSources)} +
+ )} +
+
+ ); + } +} + +export default TagView; diff --git a/app/components/TagView/styles.scss b/app/components/TagView/styles.scss new file mode 100644 index 0000000000..90fb168876 --- /dev/null +++ b/app/components/TagView/styles.scss @@ -0,0 +1,25 @@ +.tag_view_container { + width: 100%; + height: 100%; + + .dimmable { + width: 100%; + height: 100%; + } + + .tag-view { + width: 100%; + height: 100%; + } + + .lists_container { + display: flex; + flex-flow: column; + } + + @media screen and (min-width: 1800px) { + .lists_container { + flex-flow: row; + } + } +} diff --git a/app/components/ToastComponent/index.js b/app/components/ToastComponent/index.js new file mode 100644 index 0000000000..bd3afabd90 --- /dev/null +++ b/app/components/ToastComponent/index.js @@ -0,0 +1,23 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ui from 'nuclear-ui'; + +import styles from './styles.scss'; + +const ToastComponent = props => { + return ( + + ); +}; + +ToastComponent.propTypes = { + toasts: PropTypes.array +}; + +ToastComponent.defaultProps = { + toasts: [] +}; + +export default ToastComponent; diff --git a/app/components/ToastComponent/styles.scss b/app/components/ToastComponent/styles.scss new file mode 100644 index 0000000000..bcf24ddcbe --- /dev/null +++ b/app/components/ToastComponent/styles.scss @@ -0,0 +1,13 @@ +.nuclear.toast_container { + height: auto; + top: 0; + bottom: 0; + z-index: 10; + pointer-events: none; + + .toast { + min-width: 20rem; + height: auto; + pointer-events: all; + } +} diff --git a/app/components/TrackInfo/index.js b/app/components/TrackInfo/index.js index 5a56a8caed..cc3513aace 100644 --- a/app/components/TrackInfo/index.js +++ b/app/components/TrackInfo/index.js @@ -5,10 +5,23 @@ import styles from './styles.scss'; class TrackInfo extends React.Component { render() { + let { + track, + artist, + artistInfoSearchByName, + history + } = this.props; return (
-
{this.props.track}
-
{this.props.artist}
+
{track}
+ artistInfoSearchByName(artist, history)} + href='#' + > +
+ {artist} +
+
); } diff --git a/app/components/TrackInfo/styles.scss b/app/components/TrackInfo/styles.scss index 51981703de..222f865a5d 100644 --- a/app/components/TrackInfo/styles.scss +++ b/app/components/TrackInfo/styles.scss @@ -1,32 +1,28 @@ +@import '../../vars'; .track_info_container { - display: flex; - + display: flex 1; margin: 10px 20px; - color: #f8f8f2; - flex-flow: column; + flex-wrap: wrap; + overflow: hidden; } .track_name { margin-bottom: 10px; - font-size: 22px; - - flex: 1 1 auto; - - overflow: hidden; + overflow: visible; white-space: nowrap; text-overflow: ellipsis; } .artist_name { - color: rgba(248, 248, 242, 0.5); - + color: $lightbg; + &:hover { + color: $white; + } font-size: 16px; - flex: 1 1 auto; - overflow: hidden; white-space: nowrap; text-overflow: ellipsis; diff --git a/app/components/TrackRow/index.js b/app/components/TrackRow/index.js new file mode 100644 index 0000000000..7fd6a4ea8e --- /dev/null +++ b/app/components/TrackRow/index.js @@ -0,0 +1,132 @@ +import React from 'react'; +import _ from 'lodash'; +import numeral from 'numeral'; +import FontAwesome from 'react-fontawesome'; +import artPlaceholder from '../../../resources/media/art_placeholder.png'; +import ContextPopup from '../ContextPopup'; +import { formatDuration } from '../../utils'; + +import styles from './styles.scss'; + +class TrackRow extends React.Component { + renderAddTrackToQueueButton (track, index, addToQueue, musicSources) { + return ( + + addToQueue(musicSources, { + artist: track.artist.name, + name: track.name, + thumbnail: _.get(track, 'image[0][\'#text\']', artPlaceholder) + }) + } + className={styles.add_button} + aria-label='Add track to queue' + > + Add to queue + + ); + } + + renderPlayTrackButton ( + index, + ) { + return ( + + + Play now + + ); + } + + playTrack = () => { + const { + track, + clearQueue, + addToQueue, + selectSong, + startPlayback, + musicSources + } = this.props; + + clearQueue(); + addToQueue(musicSources, { + artist: track.artist.name, + name: track.name, + thumbnail: _.get(track, 'image[0][\'#text\']', artPlaceholder) + }); + selectSong(0); + startPlayback(); + } + + renderDuration (track) { + if (track.duration === 0) { + return ; + } + return ( + + {formatDuration(track.duration)} + + ); + } + + renderTrigger (track) { + return ( + + {this.props.displayCover ? : null} + {this.props.displayTrackNumber ? {track.position} : null} + {this.props.displayArtist ? {track.artist.name} : null} + {track.name} + {this.props.displayDuration ? this.renderDuration(track) : null} + {this.props.displayPlayCount ? {numeral(track.playcount).format('0,0')} : null} + + ); + } + + render () { + let { + index, + track, + addToQueue, + musicSources + } = this.props; + + let popupContents = [ + this.renderAddTrackToQueueButton( + track, + index, + addToQueue, + musicSources + ), + this.renderPlayTrackButton( + index, + ) + ]; + return ( + + {popupContents} + + ); + } +} + +export default TrackRow; diff --git a/app/components/TrackRow/styles.scss b/app/components/TrackRow/styles.scss new file mode 100644 index 0000000000..085f85506c --- /dev/null +++ b/app/components/TrackRow/styles.scss @@ -0,0 +1,104 @@ +@import '../../vars'; + +.tracks_container { + display: flex; + flex: 1 1 auto; + flex-flow: column; + + margin: 0 0.5rem; + + .header { + margin-bottom: 1rem; + + font-size: 16px; + font-variant: small-caps; + } + + .track_row { + display: flex; + align-items: center; + flex-flow: row; + + transition: $short-duration ease-in-out; + + border-bottom: 1px solid rgba($background2, 0.2); + + &:hover { + background: lighten($background, 10%); + } + + img { + flex: 0 0 auto; + + width: 3rem; + height: 3rem; + } + + .row_track_name { + flex: 1 1 auto; + margin: 0.25rem 0.5rem; + text-align: left; + } + + .playcount { + flex: 1 1 auto; + margin: 0.25rem 0.5rem; + text-align: right; + text-transform: uppercase; + } + } + + .expand_button { + display: flex; + justify-content: center; + padding: 0.5rem; + margin-top: 0.5rem; + transition: $short-duration; + cursor: pointer; + + &:hover { + background: lighten($background, 5%); + } + } +} + +.add_button { + text-align: left; +} + +.track { + transition: $short-duration; + cursor: pointer; +} + +.track_thumbnail { + width: 3rem; + height: 3rem; + background-repeat: no-repeat +} + +table { + width: 100%; +} + +thead { + background: $background2; +} + +th { + padding: 1rem; + text-align: left; +} + +tr { + transition: $short-duration; +} + +tr:hover { + background: rgba($background2, 0.25); +} + +td { + padding: 1rem 1rem; + border-bottom: 1px solid $background2; +} \ No newline at end of file diff --git a/app/components/VolumeControls/VolumeSlider/index.js b/app/components/VolumeControls/VolumeSlider/index.js index c38d939512..c32d9b24ab 100644 --- a/app/components/VolumeControls/VolumeSlider/index.js +++ b/app/components/VolumeControls/VolumeSlider/index.js @@ -1,15 +1,32 @@ import React from 'react'; -import FontAwesome from 'react-fontawesome'; import styles from './styles.scss'; +import Range from 'react-range-progress'; + +const volumeSliderColors = { + fillColor: { r: 248, g: 248, b: 242, a: 1 }, + trackColor: { r: 68, g: 71, b: 90, a: 1 }, + thumbColor: { r: 248, g: 248, b: 242, a: 1 } +}; class VolumeSlider extends React.Component { - render() { + handleClick (value) { + this.props.handleClick(value); + } + + render () { return (
-
-
+
); } diff --git a/app/components/VolumeControls/VolumeSlider/styles.scss b/app/components/VolumeControls/VolumeSlider/styles.scss index cb19fab678..b4ef8b270d 100644 --- a/app/components/VolumeControls/VolumeSlider/styles.scss +++ b/app/components/VolumeControls/VolumeSlider/styles.scss @@ -1,28 +1,9 @@ +@import '../../../vars'; .volume_slider_container { position: relative; - height: 4px; - cursor: pointer; flex: 0 0 150px; } -.volume_slider_bg { - position: absolute; - - width: 100%; - height: 100%; - - border-radius: 2px; - background-color: rgba(248, 248, 242, 0.25); -} - -.volume_slider_fill { - top: 0; - - height: 4px; - - border-radius: 2px; - background-color: rgba(248, 248, 242, 1); -} diff --git a/app/components/VolumeControls/index.js b/app/components/VolumeControls/index.js index 7a2e79c26c..e7345f8180 100644 --- a/app/components/VolumeControls/index.js +++ b/app/components/VolumeControls/index.js @@ -3,15 +3,35 @@ import FontAwesome from 'react-fontawesome'; import styles from './styles.scss'; +import PlayOptionsControls from '../PlayOptionsControls'; import VolumeSlider from './VolumeSlider'; class VolumeControls extends React.Component { + handleClick(value) { + return this.props.updateVolume(value); + } + + toggleMute(){ + this.props.toggleMute(!this.props.muted); + } + render() { return (
- - + +
+ +
+ +
); } diff --git a/app/components/VolumeControls/styles.scss b/app/components/VolumeControls/styles.scss index 0d8589f459..a175312ef2 100644 --- a/app/components/VolumeControls/styles.scss +++ b/app/components/VolumeControls/styles.scss @@ -1,11 +1,10 @@ .volume_controls_container { display: flex; - width: 15rem; height: 100%; - margin: 1rem; + margin: 0 1rem; - flex: 1 1 33%; + flex: 0 0 33%; flex-flow: row; align-items: center; justify-content: flex-end; @@ -16,3 +15,12 @@ font-size: 24px; } + +.volume_speaker_control{ + display: flex; + + cursor: pointer; + + width: 50px; + height: 52px +} \ No newline at end of file diff --git a/app/components/WindowControls/index.js b/app/components/WindowControls/index.js index 2310053d30..0f0555338c 100644 --- a/app/components/WindowControls/index.js +++ b/app/components/WindowControls/index.js @@ -2,6 +2,11 @@ import React from 'react'; import styles from './styles.css'; +import { + sendClose, + sendMinimize, + sendMaximize +} from '../../mpris'; import WindowButton from './WindowButton'; class WindowControls extends React.Component { @@ -10,13 +15,16 @@ class WindowControls extends React.Component { return (
); diff --git a/app/constants/settings.js b/app/constants/settings.js new file mode 100644 index 0000000000..a2d83b5223 --- /dev/null +++ b/app/constants/settings.js @@ -0,0 +1,100 @@ +import settingType from './settingsEnum'; + +module.exports = [ + { + name: 'loopAfterQueueEnd', + category: 'Playback', + type: settingType.BOOLEAN, + prettyName: 'Loop after playing the last queue item', + default: false + }, + { + name: 'shuffleQueue', + category: 'Playback', + type: settingType.BOOLEAN, + prettyName: 'Shuffle songs', + default: false + }, + { + name: 'autoradio', + category: 'Playback', + type: settingType.BOOLEAN, + prettyName: 'Autoradio', + default: true + }, + { + name: 'notificationTimeout', + category: 'Program settings', + type: settingType.NUMBER, + prettyName: 'Notification timeout', + default: 3 + }, + { + name: 'autoradioCraziness', + category: 'Playback', + type: settingType.NUMBER, + prettyName: 'Autoradio craziness', + default: 10, + min: 1, + max: 100, + unit: '' + }, + { + name: 'disableGPU', + category: 'Program settings', + type: settingType.BOOLEAN, + prettyName: 'Disable hardware rendering (might fix issues with dragging elements and flashing screen)', + default: false + }, + { + name: 'framelessWindow', + category: 'Program settings', + type: settingType.BOOLEAN, + prettyName: 'Frameless window (requires restart)', + default: true + }, + { + name: 'compactMenuBar', + category: 'Display', + type: settingType.BOOLEAN, + prettyName: 'Use compact style for menu bar', + default: false + }, + { + name: 'compactQueueBar', + category: 'Display', + type: settingType.BOOLEAN, + prettyName: 'Use compact style for queue bar', + default: false + }, + { + name: 'mpd.host', + category: 'MPD', + type: settingType.STRING, + prettyName: 'MPD host address', + default: 'localhost:6600' + }, + { + name: 'mpd.httpstream', + category: 'MPD', + type: settingType.STRING, + prettyName: 'MPD HTTP stream address', + default: 'localhost:8888' + }, + { + name: 'api.enabled', + category: 'HTTP API', + type: settingType.BOOLEAN, + prettyName: 'Enable the api', + default: true + }, + { + name: 'api.port', + category: 'HTTP API', + type: settingType.NUMBER, + prettyName: 'Port used by the api', + default: 8080, + min: 1024, + max: 49151 + } +]; diff --git a/app/constants/settingsEnum.js b/app/constants/settingsEnum.js new file mode 100644 index 0000000000..7754700a84 --- /dev/null +++ b/app/constants/settingsEnum.js @@ -0,0 +1,5 @@ +export default { + BOOLEAN: 'boolean', + STRING: 'string', + NUMBER: 'number' +}; diff --git a/app/containers/AlbumViewContainer/index.js b/app/containers/AlbumViewContainer/index.js index 539c29d6d2..6632d1ffdc 100644 --- a/app/containers/AlbumViewContainer/index.js +++ b/app/containers/AlbumViewContainer/index.js @@ -4,10 +4,11 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as Actions from '../../actions'; import * as QueueActions from '../../actions/queue'; +import * as PlayerActions from '../../actions/player'; import AlbumView from '../../components/AlbumView'; -var _ = require('lodash'); +let _ = require('lodash'); class AlbumViewContainer extends React.Component { constructor(props) { @@ -15,13 +16,23 @@ class AlbumViewContainer extends React.Component { } render() { + let { + actions, + match, + history, + albumDetails, + musicSources + } = this.props; return ( ); } @@ -31,12 +42,12 @@ function mapStateToProps(state) { return { albumDetails: state.search.albumDetails, musicSources: state.plugin.plugins.musicSources - } + }; } function mapDispatchToProps(dispatch) { return { - actions: bindActionCreators(Object.assign({}, Actions, QueueActions), dispatch) + actions: bindActionCreators(Object.assign({}, Actions, QueueActions, PlayerActions), dispatch) }; } diff --git a/app/containers/ArtistViewContainer/index.js b/app/containers/ArtistViewContainer/index.js index 2663bb0254..372fe65eab 100644 --- a/app/containers/ArtistViewContainer/index.js +++ b/app/containers/ArtistViewContainer/index.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as Actions from '../../actions'; import * as QueueActions from '../../actions/queue'; +import * as PlayerActions from '../../actions/player'; import ArtistView from '../../components/ArtistView'; @@ -17,16 +18,20 @@ class ArtistViewContainer extends React.Component { } render() { + let { actions, match, history, artistDetails, musicSources } = this.props; return ( - ) + ); } } @@ -34,13 +39,21 @@ function mapStateToProps(state) { return { artistDetails: state.search.artistDetails, musicSources: state.plugin.plugins.musicSources - } + }; } function mapDispatchToProps(dispatch) { return { - actions: bindActionCreators(Object.assign({}, Actions, QueueActions), dispatch) + actions: bindActionCreators( + Object.assign({}, Actions, QueueActions, PlayerActions), + dispatch + ) }; } -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ArtistViewContainer)); +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(ArtistViewContainer) +); diff --git a/app/containers/DashboardContainer/index.js b/app/containers/DashboardContainer/index.js index 7aa95b1362..a71c60a2da 100644 --- a/app/containers/DashboardContainer/index.js +++ b/app/containers/DashboardContainer/index.js @@ -1,32 +1,57 @@ import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import * as Actions from '../../actions'; import * as DashboardActions from '../../actions/dashboard'; +import * as QueueActions from '../../actions/queue'; +import * as PlayerActions from '../../actions/player'; + import Dashboard from '../../components/Dashboard'; class DashboardContainer extends React.Component { - render() { + render () { + let { actions, dashboard, history } = this.props; + return ( + albumInfoSearch={actions.albumInfoSearch} + albumInfoSearchByName={actions.albumInfoSearchByName} + artistInfoSearchByName={actions.artistInfoSearchByName} + loadBestNewAlbums={actions.loadBestNewAlbums} + loadBestNewTracks={actions.loadBestNewTracks} + loadNuclearNews={actions.loadNuclearNews} + loadTopTags={actions.loadTopTags} + loadTopTracks={actions.loadTopTracks} + dashboardData={dashboard} + history={history} + addToQueue={actions.addToQueue} + musicSources={this.props.musicSources} + startPlayback={actions.startPlayback} + clearQueue={actions.clearQueue} + selectSong={actions.selectSong} + /> ); } } -function mapStateToProps(state) { +function mapStateToProps (state) { return { - dashboard: state.dashboard - } + dashboard: state.dashboard, + musicSources: state.plugin.plugins.musicSources + }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps (dispatch) { return { - actions: bindActionCreators(DashboardActions, dispatch) + actions: bindActionCreators( + Object.assign({}, Actions, DashboardActions, QueueActions, PlayerActions), + dispatch + ) }; } -export default connect(mapStateToProps, mapDispatchToProps)(DashboardContainer); +export default connect( + mapStateToProps, + mapDispatchToProps +)(DashboardContainer); diff --git a/app/containers/ErrorBoundary/index.js b/app/containers/ErrorBoundary/index.js new file mode 100644 index 0000000000..2ed974aafb --- /dev/null +++ b/app/containers/ErrorBoundary/index.js @@ -0,0 +1,54 @@ +import logger from 'electron-timber'; +import React from 'react'; +import { withRouter } from 'react-router'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; + +import * as ToastActions from '../../actions/toasts'; + +const initialState = { hasError: false }; + +class ErrorBoundary extends React.Component { + state = initialState; + + static getDerivedStateFromError() { + return { hasError: true }; + } + + componentDidCatch(error) { + logger.error(error); + this.props.actions.error('error', 'Something wrong happened'); + + this.setState({ hasError: true }, () => { + this.props.history.goBack(); + this.setState(initialState); + }); + } + + render() { + if (this.state.hasError) { + return null; + } + + return this.props.children; + } +} + +ErrorBoundary.propTypes = { + actions: PropTypes.shape({ + error: PropTypes.func + }) +}; + +function mapStateToProps() { + return {}; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators(ToastActions, dispatch) + }; +} + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ErrorBoundary)); diff --git a/app/containers/IpcContainer/index.js b/app/containers/IpcContainer/index.js index 1b29acc14a..2ff68138b7 100644 --- a/app/containers/IpcContainer/index.js +++ b/app/containers/IpcContainer/index.js @@ -1,19 +1,33 @@ import React from 'react'; -import { Route, Switch, Link, withRouter } from 'react-router-dom'; -import { RouteTransition } from 'react-router-transition'; +import { withRouter } from 'react-router-dom'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { ipcRenderer } from 'electron'; import * as PlayerActions from '../../actions/player'; import * as QueueActions from '../../actions/queue'; +import * as SettingsActions from '../../actions/settings'; +import * as PlaylistActions from '../../actions/playlists'; -import { onNext, onPrevious, onPause, onPlayPause, onStop, onPlay, onSongChange} from '../../mpris'; +import { + onNext, + onPrevious, + onPause, + onPlayPause, + onStop, + onPlay, + onSongChange, + onSettings, + onVolume, + onSeek, + sendPlayingStatus, + sendQueueItems, + onMute, + onEmptyQueue, + onCreatePlaylist, + onRefreshPlaylists +} from '../../mpris'; class IpcContainer extends React.Component { - constructor(props) { - super(props); - } - componentDidMount() { ipcRenderer.send('started'); ipcRenderer.on('next', event => onNext(event, this.props.actions)); @@ -22,11 +36,20 @@ class IpcContainer extends React.Component { ipcRenderer.on('playpause', event => onPlayPause(event, this.props.actions, this.props.player)); ipcRenderer.on('stop', event => onStop(event, this.props.actions)); ipcRenderer.on('play', event => onPlay(event, this.props.actions)); + ipcRenderer.on('settings', (event, data) => onSettings(event, data, this.props.actions)); + ipcRenderer.on('mute', event => onMute(event, this.props.actions, this.props.player)); + ipcRenderer.on('volume', (event, data) => onVolume(event, data, this.props.actions)); + ipcRenderer.on('seek', (event, data) => onSeek(event, data, this.props.actions)); + ipcRenderer.on('playing-status', event => sendPlayingStatus(event, this.props.player, this.props.queue)); + ipcRenderer.on('empty-queue', event => onEmptyQueue(event, this.props.actions)); + ipcRenderer.on('queue', event => sendQueueItems(event, this.props.queue.queueItems)); + ipcRenderer.on('create-playlist', (event, name) => onCreatePlaylist(event, { name, tracks: this.props.queue.queueItems }, this.props.actions)); + ipcRenderer.on('refresh-playlists', (event) => onRefreshPlaylists(event, this.props.actions)); } componentWillReceiveProps(nextProps){ - if (this.props != nextProps) { - let currentSong = nextProps.queue.queueItems[nextProps.queue.currentSong]; + if (this.props !== nextProps) { + const currentSong = nextProps.queue.queueItems[nextProps.queue.currentSong]; onSongChange(currentSong); } } @@ -40,12 +63,12 @@ function mapStateToProps(state) { return { player: state.player, queue: state.queue - } + }; } function mapDispatchToProps(dispatch) { return { - actions: bindActionCreators(Object.assign({}, PlayerActions, QueueActions), dispatch) + actions: bindActionCreators(Object.assign({}, PlayerActions, QueueActions, SettingsActions, PlaylistActions), dispatch) }; } diff --git a/app/containers/LyricsContainer/index.js b/app/containers/LyricsContainer/index.js new file mode 100644 index 0000000000..81b4788280 --- /dev/null +++ b/app/containers/LyricsContainer/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import _ from 'lodash'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import LyricsView from '../../components/LyricsView'; +import * as LyricsActions from '../../actions/lyrics'; + +class LyricsContainer extends React.Component { + constructor(props) { + super(props); + } + render () { + return ( + + ); + } +} + +function mapStateToProps (state) { + return { + queue: state.queue, + lyrics: state.lyrics + }; +} + +function mapDispatchToProps (dispatch) { + return { + actions: bindActionCreators(LyricsActions, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(LyricsContainer); diff --git a/app/containers/MainContentContainer/index.js b/app/containers/MainContentContainer/index.js index d9502ea9a9..b530fa09bc 100644 --- a/app/containers/MainContentContainer/index.js +++ b/app/containers/MainContentContainer/index.js @@ -1,6 +1,5 @@ import React from 'react'; -import { Route, Switch, Link, withRouter } from 'react-router-dom'; -import { RouteTransition } from 'react-router-transition'; +import { Route, Switch, withRouter } from 'react-router-dom'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as Actions from '../../actions'; @@ -12,53 +11,55 @@ import ArtistViewContainer from '../ArtistViewContainer'; import DashboardContainer from '../DashboardContainer'; import PlaylistsContainer from '../PlaylistsContainer'; import PlaylistViewContainer from '../PlaylistViewContainer'; +import PluginsContainer from '../PluginsContainer'; import SearchResultsContainer from '../SearchResultsContainer'; import SettingsContainer from '../SettingsContainer'; +import TagViewContainer from '../TagViewContainer'; +import LyricsContainer from '../LyricsContainer'; - -import styles from './styles.scss'; +import Downloads from '../../components/Downloads'; class MainContentContainer extends React.Component { + componentDidMount () { + if (this.props.history + && this.props.location + && this.props.location.pathname === '/') { + this.props.history.push('/dashboard'); + } + } - render() { - - return( - { + render () { + return ( + { return ( - - - - - - - - - - - + + + + + + + + + + + + + ); } - }> - - - ); -} + } /> + ); + } } -function mapStateToProps(state) { +function mapStateToProps (state) { return { - } + }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps (dispatch) { return { actions: bindActionCreators(Actions, dispatch) }; diff --git a/app/containers/MainContentContainer/styles.scss b/app/containers/MainContentContainer/styles.scss deleted file mode 100644 index 62b1698b3a..0000000000 --- a/app/containers/MainContentContainer/styles.scss +++ /dev/null @@ -1,6 +0,0 @@ -.transition { - position: absolute; - width: 100%; - height: 100%; - margin: 1rem; -} diff --git a/app/containers/PlayQueueContainer/index.js b/app/containers/PlayQueueContainer/index.js index 5217242546..d5529f254c 100644 --- a/app/containers/PlayQueueContainer/index.js +++ b/app/containers/PlayQueueContainer/index.js @@ -5,42 +5,36 @@ import { bindActionCreators } from 'redux'; import * as QueueActions from '../../actions/queue'; import * as PluginsActions from '../../actions/plugins'; import * as PlaylistsActions from '../../actions/playlists'; - +import * as SettingsActions from '../../actions/settings'; +import * as ToastActions from '../../actions/toasts'; import PlayQueue from '../../components/PlayQueue'; - -class PlayQueueContainer extends React.Component { - constructor(props) { - super(props); - } - - render() { - return( - - ); - } -} +const PlayQueueContainer = props => { + return ( + + ); +}; function mapStateToProps(state) { return { queue: state.queue, - plugins: state.plugin.plugins, - playlists: state.playlists.playlists - } + plugins: state.plugin, + playlists: state.playlists.playlists, + settings: state.settings + }; } function mapDispatchToProps(dispatch) { return { - actions: bindActionCreators(Object.assign({}, PluginsActions, QueueActions, PlaylistsActions), dispatch) + actions: bindActionCreators(Object.assign({}, PluginsActions, QueueActions, PlaylistsActions, SettingsActions, ToastActions), dispatch) }; } diff --git a/app/containers/PlaylistViewContainer/index.js b/app/containers/PlaylistViewContainer/index.js index 9cb3dbb2d6..9c06ac806f 100644 --- a/app/containers/PlaylistViewContainer/index.js +++ b/app/containers/PlaylistViewContainer/index.js @@ -5,36 +5,35 @@ import { bindActionCreators } from 'redux'; import * as QueueActions from '../../actions/queue'; import * as PlayerActions from '../../actions/player'; -import PlaylistView from '../../components/PlaylistView'; +import * as ToastActions from '../../actions/toasts'; -class PlaylistViewContainer extends React.Component { - constructor(props) { - super(props); - } +import PlaylistView from '../../components/PlaylistView'; - render() { - return ( - - ); - } -} +const PlaylistViewContainer = props => { + return ( + + ); +}; -function mapStateToProps(state) { +function mapStateToProps (state) { return { playlists: state.playlists, musicSources: state.plugin.plugins.musicSources - } + }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps (dispatch) { return { - actions: bindActionCreators(Object.assign({}, QueueActions, PlayerActions), dispatch) + actions: bindActionCreators(Object.assign({}, QueueActions, PlayerActions, ToastActions), dispatch) }; } diff --git a/app/containers/PlaylistsContainer/index.js b/app/containers/PlaylistsContainer/index.js index 3d58054458..935b7034f8 100644 --- a/app/containers/PlaylistsContainer/index.js +++ b/app/containers/PlaylistsContainer/index.js @@ -14,7 +14,7 @@ class PlaylistsContainer extends React.Component { render() { return ( ); @@ -22,15 +22,15 @@ class PlaylistsContainer extends React.Component { } function mapStateToProps(state) { - return { - playlists: state.playlists.playlists - } + return { + playlists: state.playlists.playlists + }; } function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators(Object.assign({}, PlaylistsActions), dispatch) - } + return { + actions: bindActionCreators(Object.assign({}, PlaylistsActions), dispatch) + }; } export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PlaylistsContainer)); diff --git a/app/containers/PluginsContainer/index.js b/app/containers/PluginsContainer/index.js new file mode 100644 index 0000000000..cf5b00d2bd --- /dev/null +++ b/app/containers/PluginsContainer/index.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; + +import PluginsView from '../../components/PluginsView'; +import * as PluginsActions from '../../actions/plugins'; + +class PluginsContainer extends React.Component { + constructor(props) { + super(props); + } + + render() { + let { + actions, + plugin + } = this.props; + + return ( + + ); + } +} + +function mapStateToProps(state) { + return { + plugin: state.plugin + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators(Object.assign({}, PluginsActions), dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(PluginsContainer); diff --git a/app/containers/SearchBoxContainer/index.js b/app/containers/SearchBoxContainer/index.js index 675386292a..ab2b2b84e0 100644 --- a/app/containers/SearchBoxContainer/index.js +++ b/app/containers/SearchBoxContainer/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import { withRouter } from 'react-router-dom'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as Actions from '../../actions'; @@ -7,24 +8,26 @@ import SearchBox from '../../components/SearchBox'; class SearchBoxContainer extends React.Component { - handleSearch(event) { - this.props.actions.unifiedSearch(event.target.value); + handleSearch(history) { + return event => { + this.props.actions.unifiedSearch(event.target.value, history); + }; } render() { - return( + return ( - ) + ); } } function mapStateToProps(state) { return { unifiedSearchStarted: state.search.unifiedSearchStarted - } + }; } function mapDispatchToProps(dispatch) { @@ -33,4 +36,4 @@ function mapDispatchToProps(dispatch) { }; } -export default connect(mapStateToProps, mapDispatchToProps)(SearchBoxContainer); +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(SearchBoxContainer)); diff --git a/app/containers/SearchResultsContainer/index.js b/app/containers/SearchResultsContainer/index.js index e1a779ac6a..f8bfcf05f5 100644 --- a/app/containers/SearchResultsContainer/index.js +++ b/app/containers/SearchResultsContainer/index.js @@ -2,36 +2,61 @@ import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as Actions from '../../actions'; +import * as QueueActions from '../../actions/queue'; +import * as PlayerActions from '../../actions/player'; import SearchResults from '../../components/SearchResults'; class SearchResultsContainer extends React.Component { - render() { + constructor(props) { + super(props); + } + render () { + let { actions, musicSources } = this.props; + return ( ); } } -function mapStateToProps(state) { +function mapStateToProps (state) { return { artistSearchResults: state.search.artistSearchResults, albumSearchResults: state.search.albumSearchResults, - unifiedSearchStarted: state.search.unifiedSearchStarted - } + trackSearchResults: state.search.trackSearchResults, + playlistSearchResults: state.search.playlistSearchResults, + unifiedSearchStarted: state.search.unifiedSearchStarted, + playlistSearchStarted: state.search.playlistSearchStarted, + musicSources: state.plugin.plugins.musicSources + }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps (dispatch) { return { - actions: bindActionCreators(Actions, dispatch) + actions: bindActionCreators( + Object.assign({}, Actions, QueueActions, PlayerActions), + dispatch + ) }; } -export default connect(mapStateToProps, mapDispatchToProps)(SearchResultsContainer); +export default connect( + mapStateToProps, + mapDispatchToProps +)(SearchResultsContainer); diff --git a/app/containers/SettingsContainer/index.js b/app/containers/SettingsContainer/index.js index 9d54fb04db..2ec860c993 100644 --- a/app/containers/SettingsContainer/index.js +++ b/app/containers/SettingsContainer/index.js @@ -2,6 +2,8 @@ import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as ScrobblingActions from '../../actions/scrobbling'; +import * as SettingsActions from '../../actions/settings'; +import options from '../../constants/settings'; import Settings from '../../components/Settings'; @@ -11,6 +13,8 @@ class SettingsContainer extends React.Component { ); } @@ -18,13 +22,14 @@ class SettingsContainer extends React.Component { function mapStateToProps(state) { return { - scrobbling: state.scrobbling - } + scrobbling: state.scrobbling, + settings: state.settings + }; } function mapDispatchToProps(dispatch) { return { - actions: bindActionCreators(ScrobblingActions, dispatch) + actions: bindActionCreators(Object.assign({}, ScrobblingActions, SettingsActions), dispatch) }; } diff --git a/app/containers/ShortcutsContainer/index.js b/app/containers/ShortcutsContainer/index.js new file mode 100644 index 0000000000..734679f72e --- /dev/null +++ b/app/containers/ShortcutsContainer/index.js @@ -0,0 +1,146 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import Sound from 'react-sound'; +import * as Mousetrap from 'mousetrap'; + +import * as PlayerActions from '../../actions/player'; +import * as QueueActions from '../../actions/queue'; + +const VOLUME_ITERATION = 1; +const SEEK_ITERATION = 100; +const COEF_ITERATION = 0.1; +const BASE_COEF = 1; + +class Shortcuts extends React.Component { + coef = BASE_COEF; + timeout = null; + + incrementCoef() { + clearTimeout(this.timeout); + this.coef = this.coef + COEF_ITERATION; + this.timeout = setTimeout(() => { + this.coef = BASE_COEF; + }, 100); + } + + handleSpaceBar = () => { + const { queue, player, actions } = this.props; + + if (queue.queueItems.length > 0) { + if (player.playbackStatus === Sound.status.PLAYING) { + actions.pausePlayback(); + } else { + actions.startPlayback(); + } + } + return false; + } + + playCurrentSong = () => { + const { queue, player, actions } = this.props; + + if ( + queue.queueItems.length > 0 && + player.playbackStatus !== Sound.status.PLAYING + ) { + actions.startPlayback(); + } + return false; + } + + increaseVolume = () => { + const { player, actions } = this.props; + + if (player.volume < 100) { + actions.updateVolume(player.volume + VOLUME_ITERATION * this.coef); + this.incrementCoef(); + } + return false; + } + + decreaseVolume = () => { + const { player, actions } = this.props; + + if (player.volume > 0) { + actions.updateVolume(player.volume - VOLUME_ITERATION * this.coef); + this.incrementCoef(); + } + return false; + } + + increaseSeek = () => { + const { player, actions } = this.props; + + if (player.playbackProgress < 100) { + actions.updateSeek(player.seek + SEEK_ITERATION * this.coef); + this.incrementCoef(); + } + return false; + } + + decreaseSeek = () => { + const { player, actions} = this.props; + + if (player.playbackProgress > 0) { + actions.updateSeek(player.seek - SEEK_ITERATION * this.coef); + this.incrementCoef(); + } + return false; + } + + componentDidMount() { + Mousetrap.bind('space', this.handleSpaceBar); + Mousetrap.bind('enter', this.playCurrentSong); + Mousetrap.bind('up', this.increaseVolume); + Mousetrap.bind('down', this.decreaseVolume); + Mousetrap.bind('left', this.decreaseSeek); + Mousetrap.bind('right', this.increaseSeek); + Mousetrap.bind(['ctrl+right', 'command+right'], this.props.actions.nextSong); + Mousetrap.bind(['ctrl+left', 'command+left'], this.props.actions.previousSong); + Mousetrap.bind(['ctrl+top', 'command+top'], this.props.actions.unmute); + Mousetrap.bind(['ctrl+down', 'command+down'], this.props.actions.mute); + } + + componentWillUnmount() { + Mousetrap.unbind([ + 'space', + 'enter', + 'left', + 'right', + 'up', + 'down', + 'ctrl+right', + 'command+right', + 'ctrl+left', + 'command+left', + 'ctrl+top', + 'command+top', + 'ctrl+down', + 'command+down' + ]); + } + + shouldComponentUpdate() { + return false; + } + + render() { + return null; + } +} + +function mapStateToProps({ player, queue }) { + return { + player, + queue + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators(Object.assign({}, PlayerActions, QueueActions), dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(Shortcuts); diff --git a/app/containers/SoundContainer/autoradio.js b/app/containers/SoundContainer/autoradio.js new file mode 100644 index 0000000000..9304003a72 --- /dev/null +++ b/app/containers/SoundContainer/autoradio.js @@ -0,0 +1,207 @@ +import _ from 'lodash'; +import logger from 'electron-timber'; + +import globals from '../../globals'; +import core from 'nuclear-core'; + +let lastfm = new core.LastFmApi(globals.lastfmApiKey, globals.lastfmApiSecret); + +/* + * The following const will determine how random will be the next track compared to + * the previous ones. + * The biggest impact are : + * Very similar track < 0 --- AUTORADIO_TRACKS_DEVIATION --- 1 > Different track + * Small variety of track < 0 --- SIMILAR_TRACKS_RESULTS_LIMIT --- 1 > Large variety + */ + +/* + * Will determine wether when looking for similar tracks we stay close to the current tracks + * Min = 0 - Max = 1 (0 will only accept the most similar track / 1 will go further down the list) + * Example : + * If set to 1 : autoradio is likely to play different styles of music + * If set to 0.1 : autoradio is quite conservative and will stay in the same style + */ +let autoradioTracksDeviation = 0.15; + +/* + * No maximum + * Will determine how many tracks in the queue do we take into account to get a similar track + * Example : + * If set to 1 : autoradio will select next track only based on the current track in queue + * If set to 10 : autoradio will select next track based on the 10 latest tracks in the queue + */ +let autoradioImpactingTrackNumber = 10; + +/* + * No maximum + * Will determine how many similar track we will be looking for each queue element. + * The higher, the highest is the chance of changing a lot the style of the future track + * Example : + * If set to 10 : for each element in the queue, we will look for 10 similar tracks + * The next track will be chosen pseudo randomly between + * AUTORADIO_IMPACTING_TRACK_NUMBER * SIMILAR_TRACKS_RESULTS_LIMIT tracks + * The more tracks, the more likely is the style to be changed + */ +let similarTracksResultsLimit = 10; + +/* + * Min = 0 - Max = 1 (0 will only accept the most similar artist / 1 will go further down the list) + * Will determine wether when looking for similar artists we stay close to the current artist + * This is only used in the case we cannot find similar tracks => we fall back to similar artist search + */ +let autoradioArtistDeviation = 0.20; + +function computeParameters (crazinessScore = 10) { + autoradioArtistDeviation = crazinessScore / 100; + similarTracksResultsLimit = crazinessScore; + autoradioImpactingTrackNumber = 101 - crazinessScore; + autoradioTracksDeviation = crazinessScore; +} + +let props; +/** + * addAutoradioTrackToQueue will first try to find tracks similar to the + * current queue. + * If no track is found, it will look for similar artists and choose a + * random track to play. + * It will remove all tracks which are already present in the queue. + */ +export function addAutoradioTrackToQueue (callProps) { + props = callProps; + let currentSong = props.queue.queueItems[props.queue.currentSong]; + computeParameters(props.settings.autoradioCraziness); + + return getSimilarTracksToQueue(autoradioImpactingTrackNumber) + .then(track => { + if (track === null) { + track = getNewTrack('artist', currentSong); + } + return track; + }) + .then(selectedTrack => { + if (selectedTrack === null) { + return Promise.reject(new Error('No similar track or artist were found.')); + } + return addToQueue(selectedTrack.artist, selectedTrack); + }) + .catch(function (err) { + logger.error('error', err); + }); +} + +function getSimilarTracksToQueue (number) { + let similarTracksPromises = []; + + for (let i = props.queue.currentSong; i >= Math.max(0, props.queue.currentSong - number); i--) { + similarTracksPromises.push(getSimilarTracks(props.queue.queueItems[i], similarTracksResultsLimit)); + } + return Promise.all(similarTracksPromises) + .then(results => { + let flattenResults = _.flatten(results); + _.flatten(results).sort((a, b) => { + return b.match - a.match; + }); + let notInQueueResults = flattenResults.filter((track) => !isTrackInQueue(track)); + if (notInQueueResults.length > 0) { + return getScoredRandomTrack(getArraySlice(notInQueueResults, autoradioTracksDeviation)); + } else { + return null; + } + }); +} + +function getScoredRandomTrack (tracks) { + let sum = 0; + let cumulativeBias = tracks.map(function (track) { + sum += track.match; return sum; + }); + let choice = Math.random() * sum; + let chosenIndex = null; + cumulativeBias.some(function (el, i) { + return el >= choice ? ((chosenIndex = i), true) : false; + }); + return Promise.resolve(tracks[chosenIndex]); +} + +function getTrackNotInQueue (tracks, deviation) { + let newtracks = tracks.filter((track) => !isTrackInQueue(track)); + return getRandomElement(getArraySlice(newtracks, deviation)); +} + +function getArraySlice (arr, ratio) { + return arr.slice(0, Math.round((arr.length - 1) * ratio) + 1); +} + +function getNewTrack (getter, track) { + let getTrack; + if (getter === 'track') { + getTrack = getSimilarTracks(track); + } else { + getTrack = getTracksFromSimilarArtist(track.artist); + } + return getTrack + .then(similarTracks => { + return (getTrackNotInQueue(similarTracks, autoradioTracksDeviation) || null); + }); +} + +function isTrackInQueue (track) { + let queue = props.queue.queueItems; + for (let i in queue) { + if (queue[i].artist === track.artist.name && queue[i].name === track.name) { + return true; + } + } + return false; +} + +function getSimilarTracks (currentSong, limit = 100) { + return lastfm.getSimilarTracks(currentSong.artist, currentSong.name, limit) + .then(tracks => tracks.json()) + .then(trackJson => { + return _.get(trackJson, 'similartracks.track', []); + }); +} + +function getTracksFromSimilarArtist (artist) { + return lastfm + .getArtistInfo(artist) + .then(artist => artist.json()) + .then(artistJson => getSimilarArtists(artistJson)) + .then(similarArtists => { + let similarArtist = getRandomElement(getArraySlice(similarArtists, autoradioArtistDeviation)); + return similarArtist; + }) + .then(selectedArtist => getArtistTopTracks(selectedArtist)) + .then(topTracks => _.get(topTracks, 'toptracks.track', [])); +} + +function getSimilarArtists (artistJson) { + return Promise.resolve(artistJson.artist.similar.artist); +} + +function getRandomElement (arr) { + return arr[Math.round(Math.random() * (arr.length - 1))]; +} + +function getArtistTopTracks (artist) { + return lastfm + .getArtistTopTracks(_.get(artist, 'name', artist)) + .then(topTracks => { + + return topTracks.json(); + }); +} + +function addToQueue (artist, track) { + return new Promise((resolve) => { + let musicSources = props.plugins.plugins.musicSources; + props.actions.addToQueue(musicSources, { + artist: artist.name, + name: track.name, + thumbnail: track.image[0]['#text'] + }); + resolve(true); + }); +} + diff --git a/app/containers/SoundContainer/index.js b/app/containers/SoundContainer/index.js index 2df38b57c0..e8ffd67d11 100644 --- a/app/containers/SoundContainer/index.js +++ b/app/containers/SoundContainer/index.js @@ -1,74 +1,203 @@ import React from 'react'; -import { Route, Switch, Link, withRouter } from 'react-router-dom'; -import { RouteTransition } from 'react-router-transition'; +import { withRouter } from 'react-router-dom'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import _ from 'lodash'; + import * as Actions from '../../actions'; import * as PlayerActions from '../../actions/player'; import * as QueueActions from '../../actions/queue'; import * as ScrobblingActions from '../../actions/scrobbling'; +import * as LyricsActions from '../../actions/lyrics'; import Sound from 'react-sound'; +import { getSelectedStream } from '../../utils'; +import * as Autoradio from './autoradio'; +import globals from '../../globals'; +import core from 'nuclear-core'; + +let lastfm = new core.LastFmApi(globals.lastfmApiKey, globals.lastfmApiSecret); class SoundContainer extends React.Component { - handlePlaying(update) { + handlePlaying (update) { let seek = update.position; - let progress = (update.position/update.duration) * 100; + let progress = (update.position / update.duration) * 100; this.props.actions.updatePlaybackProgress(progress, seek); + this.props.actions.updateStreamLoading(false); + } + + handleLoading () { + this.props.actions.updateStreamLoading(true); } - handleFinishedPlaying() { - if(this.props.scrobbling.lastFmScrobblingEnabled && this.props.scrobbling.lastFmSessionKey) { - let currentSong = this.props.queue.queueItems[this.props.queue.currentSong]; - this.props.actions.scrobbleAction(currentSong.artist, currentSong.name, this.props.scrobbling.lastFmSessionKey); + handleLoaded () { + this.handleLoadLyrics(); + this.handleAutoRadio(); + this.props.actions.updateStreamLoading(false); + } + + handleLoadLyrics () { + let currentSong = this.props.queue.queueItems[ + this.props.queue.currentSong + ]; + + if (typeof currentSong.lyrics === 'undefined') { + this.props.actions.lyricsSearch(currentSong); + } + } + + handleAutoRadio () { + if ( + this.props.settings.autoradio && + this.props.queue.currentSong === this.props.queue.queueItems.length - 1 + ) { + Autoradio.addAutoradioTrackToQueue(this.props); } + } + + nextSong () { + if (this.props.settings.shuffleQueue) { + let index = _.random(0, this.props.queue.queueItems.length - 1); + this.props.actions.selectSong(index); + } else { this.props.actions.nextSong(); + } + } + + handleFinishedPlaying () { + if ( + this.props.scrobbling.lastFmScrobblingEnabled && + this.props.scrobbling.lastFmSessionKey + ) { + let currentSong = this.props.queue.queueItems[ + this.props.queue.currentSong + ]; + this.props.actions.scrobbleAction( + currentSong.artist, + currentSong.name, + this.props.scrobbling.lastFmSessionKey + ); + } + if ( + this.props.queue.currentSong <= this.props.queue.queueItems.length - 1 || + this.props.settings.loopAfterQueueEnd + ) { + this.nextSong(); + } else { + this.props.actions.pausePlayback(); + } } - shouldComponentUpdate(nextProps) { + addAutoradioTrackToQueue () { + let currentSong = this.props.queue.queueItems[this.props.queue.currentSong]; + return lastfm + .getArtistInfo(currentSong.artist) + .then(artist => artist.json()) + .then(artistJson => this.getSimilarArtists(artistJson.artist)) + .then(similarArtists => this.getRandomElement(similarArtists)) + .then(selectedArtist => this.getArtistTopTracks(selectedArtist)) + .then(topTracks => this.getRandomElement(topTracks.toptracks.track)) + .then(track => { + return this.addToQueue(track.artist, track); + }); + } + + getSimilarArtists (artistJson) { + return new Promise((resolve, reject) => { + resolve(artistJson.similar.artist); + }); + } + + getRandomElement (arr) { + let devianceParameter = 0.2; // We will select one of the 20% most similar artists + let randomElement = + arr[Math.round(Math.random() * (devianceParameter * (arr.length - 1)))]; + return new Promise((resolve, reject) => resolve(randomElement)); + } + + getArtistTopTracks (artist) { + return lastfm + .getArtistTopTracks(artist.name) + .then(topTracks => topTracks.json()); + } + + addToQueue (artist, track) { + return new Promise((resolve, reject) => { + let musicSources = this.props.plugins.plugins.musicSources; + this.props.actions.addToQueue(musicSources, { + artist: artist.name, + name: track.name, + thumbnail: track.image[0]['#text'] + }); + resolve(true); + }); + } + + shouldComponentUpdate (nextProps) { + const currentSong = nextProps.queue.queueItems[nextProps.queue.currentSong]; + return ( - this.props.queue.currentSong != nextProps.queue.currentSong || - this.props.player.playbackStatus != nextProps.player.playbackStatus || - this.props.player.seek != nextProps.player.seek + this.props.queue.currentSong !== nextProps.queue.currentSong || + this.props.player.playbackStatus !== nextProps.player.playbackStatus || + this.props.player.seek !== nextProps.player.seek || + (!!currentSong && !!currentSong.streams && currentSong.streams.length > 0) ); } - render() { - let { - player, - queue - } = this.props; + render () { + let { player, queue, plugins } = this.props; let streamUrl = ''; + if (queue.queueItems.length > 0) { let currentSong = queue.queueItems[queue.currentSong]; - if (currentSong.streams && currentSong.streams.length > 0) { - streamUrl = currentSong.streams[0].stream; - } + streamUrl = ( + getSelectedStream(currentSong.streams, plugins.defaultMusicSource) || {} + ).stream; } - - return ( + + return !!streamUrl && ( ); } } -function mapStateToProps(state) { +function mapStateToProps (state) { return { queue: state.queue, + plugins: state.plugin, player: state.player, - scrobbling: state.scrobbling - } + scrobbling: state.scrobbling, + settings: state.settings + }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps (dispatch) { return { - actions: bindActionCreators(Object.assign({}, Actions, PlayerActions, QueueActions, ScrobblingActions), dispatch) + actions: bindActionCreators( + Object.assign( + {}, + Actions, + PlayerActions, + QueueActions, + ScrobblingActions, + LyricsActions + ), + dispatch + ) }; } -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(SoundContainer)); +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(SoundContainer) +); diff --git a/app/containers/TagViewContainer/index.js b/app/containers/TagViewContainer/index.js new file mode 100644 index 0000000000..6050e0bdbc --- /dev/null +++ b/app/containers/TagViewContainer/index.js @@ -0,0 +1,57 @@ +import React from 'react'; +import { withRouter } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import * as Actions from '../../actions'; +import * as TagActions from '../../actions/tag'; +import * as QueueActions from '../../actions/queue'; +import * as PlayerActions from '../../actions/player'; + +import TagView from '../../components/TagView'; + +class TagViewContainer extends React.Component { + constructor(props) { + super(props); + } + + render () { + let { + actions, + match, + history, + tags, + musicSources + } = this.props; + + return ( + + ); + } +} + +function mapStateToProps (state) { + return { + tags: state.tags, + musicSources: state.plugin.plugins.musicSources + }; +} + +function mapDispatchToProps (dispatch) { + return { + actions: bindActionCreators(Object.assign({}, Actions, TagActions, QueueActions, PlayerActions), dispatch) + }; +} + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(TagViewContainer)); diff --git a/app/containers/ToastContainer/index.js b/app/containers/ToastContainer/index.js new file mode 100644 index 0000000000..8b6ba2f193 --- /dev/null +++ b/app/containers/ToastContainer/index.js @@ -0,0 +1,44 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import * as ToastActions from '../../actions/toasts'; + +import ToastComponent from '../../components/ToastComponent'; + +class ToastContainer extends React.Component { + constructor(props) { + super(props); + } + + render() { + let { + actions, + toasts + } = this.props; + + return ( + + ); + } +} +function mapStateToProps(state) { + return { + toasts: state.toasts + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators( + Object.assign({}, ToastActions), + dispatch + ) + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(ToastContainer); diff --git a/app/globals.js b/app/globals.js index 235164a6a5..496d3b7937 100644 --- a/app/globals.js +++ b/app/globals.js @@ -1,5 +1,7 @@ module.exports = { - ytApiKey: "AIzaSyCIM4EzNqi1in22f4Z3Ru3iYvLaY8tc3bo", - lastfmApiKey: "2b75dcb291e2b0c9a2c994aca522ac14", - lastfmApiSecret: "2ee49e35f08b837d43b2824198171fc8" + ytApiKey: 'AIzaSyCIM4EzNqi1in22f4Z3Ru3iYvLaY8tc3bo', + lastfmApiKey: '2b75dcb291e2b0c9a2c994aca522ac14', + lastfmApiSecret: '2ee49e35f08b837d43b2824198171fc8', + soundcloudApiKey: 'xkpqYPmDf6KG7aL1xM4qfWaJQrHBLSOh', + jamendoClientId: '836523a7' }; diff --git a/app/index.html b/app/index.html index 5d59781b6f..42bd39e449 100644 --- a/app/index.html +++ b/app/index.html @@ -3,6 +3,16 @@ nuclear music player + diff --git a/app/index.js b/app/index.js index 1d691a16fe..2d347ce1e4 100644 --- a/app/index.js +++ b/app/index.js @@ -1,3 +1,4 @@ +import 'babel-polyfill'; import React from 'react'; import ReactDOM from 'react-dom'; import { MemoryRouter } from 'react-router-dom'; @@ -9,6 +10,10 @@ import configureStore from './store/configureStore'; const store = configureStore(); +// Sentry +process.env.NODE_ENV === 'production' && + Raven.config('https://2fb5587831994721a8b5f77bf6010679@sentry.io/1256142').install(); + const render = Component => { ReactDOM.render( @@ -20,12 +25,12 @@ const render = Component => { , document.getElementById('react_root') ); -} +}; render(App); if (module.hot) { module.hot.accept( () => { render(App); - }); - } + }); +} diff --git a/app/mixin.scss b/app/mixin.scss new file mode 100644 index 0000000000..596d49ac8c --- /dev/null +++ b/app/mixin.scss @@ -0,0 +1,3 @@ +@mixin transition { + transition: 0.25s; +} diff --git a/app/mocks/queueMock.js b/app/mocks/queueMock.js index 4dc2474fa0..336d8ef4bf 100644 --- a/app/mocks/queueMock.js +++ b/app/mocks/queueMock.js @@ -1,17 +1,17 @@ export const queueData = [ - { - thumbnail: "https://classicalbumcovers.files.wordpress.com/2013/03/black_sabbath-vol_4.jpg", - artist: "Black Sabbath", - name: "Supernaut" - }, - { - thumbnail: "https://upload.wikimedia.org/wikipedia/en/9/9a/Zappa_Joe%27s_Garage.jpg", - artist: "Frank Zappa", - name: "Joe's Garage" - }, - { - thumbnail: "http://3.bp.blogspot.com/_aNTsUIQhmf0/TJD02nFCD0I/AAAAAAAADyc/nEs2_ttp98c/s1600/Neutral+Milk+Hotel+-+In+the+Aeroplane+Over+the+Sea+-+1998.jpg", - artist: "Neutral Milk Hotel", - name: "The King of Carrot Flowers Pts. Two & Three" - }, - ]; + { + thumbnail: 'https://classicalbumcovers.files.wordpress.com/2013/03/black_sabbath-vol_4.jpg', + artist: 'Black Sabbath', + name: 'Supernaut' + }, + { + thumbnail: 'https://upload.wikimedia.org/wikipedia/en/9/9a/Zappa_Joe%27s_Garage.jpg', + artist: 'Frank Zappa', + name: 'Joe\'s Garage' + }, + { + thumbnail: 'http://3.bp.blogspot.com/_aNTsUIQhmf0/TJD02nFCD0I/AAAAAAAADyc/nEs2_ttp98c/s1600/Neutral+Milk+Hotel+-+In+the+Aeroplane+Over+the+Sea+-+1998.jpg', + artist: 'Neutral Milk Hotel', + name: 'The King of Carrot Flowers Pts. Two & Three' + } +]; diff --git a/app/mpris.js b/app/mpris.js index 2aaa838281..8f60124cd4 100644 --- a/app/mpris.js +++ b/app/mpris.js @@ -1,40 +1,133 @@ import { ipcRenderer } from 'electron'; -function onNext(event, actions) { +export function onNext(event, actions) { actions.nextSong(); } -function onPrevious(event, actions) { +export function onPrevious(event, actions) { actions.previousSong(); } -function onPause(event, actions) { +export function onPause(event, actions) { actions.pausePlayback(); } -function onPlayPause(event, actions, state) { +export function onPlayPause(event, actions, state) { actions.togglePlayback(state.playbackStatus); } -function onStop(event, actions) { +export function onStop(event, actions) { actions.pausePlayback(); } -function onPlay(event, actions) { +export function onPlay(event, actions) { actions.startPlayback(); } -function onSongChange(song) { +export function onSettings(event, data, actions) { + const key = Object.keys(data).pop(); + const value = Object.values(data).pop(); + + switch (typeof value) { + case 'boolean': + actions.setBooleanOption(key, value); + break; + case 'number': + actions.setNumberOption(key, value); + break; + case 'string': + default: + actions.setStringOption(key, value); + break; + } +} + +export function onMute(event, actions, playerState) { + if (playerState.muted) { + actions.unMute(); + } else { + actions.mute(); + } +} + +export function onVolume(event, data, actions) { + actions.updateVolume(data); +} + +export function onSeek(event, data, actions) { + actions.updateSeek(data); +} + +export function onEmptyQueue(event, actions) { + actions.clearQueue(); +} + +export function onCreatePlaylist(event, { tracks, name }, actions) { + actions.addPlaylist(tracks, name); +} + +export function onRefreshPlaylists(event, actions) { + actions.loadPlaylists(); +} + +export function onSongChange(song) { ipcRenderer.send('songChange', song); } +export function sendPlay() { + ipcRenderer.send('play'); +} + +export function sendPaused() { + ipcRenderer.send('paused'); +} -module.exports = { - onNext, - onPrevious, - onPause, - onPlayPause, - onStop, - onPlay, - onSongChange -}; +export function sendClose() { + ipcRenderer.send('close'); +} + +export function sendMinimize() { + ipcRenderer.send('minimize'); +} + +export function sendMaximize() { + ipcRenderer.send('maximize'); +} + +export function restartApi() { + ipcRenderer.send('restart-api'); +} + +export function stopApi() { + ipcRenderer.send('stop-api'); +} + +export function sendPlayingStatus(event, playerState, queueState) { + try { + const { artist, name, thumbnail } = queueState.queueItems[queueState.currentSong]; + + ipcRenderer.send('playing-status', { ...playerState, artist, name, thumbnail }); + } catch (err) { + ipcRenderer.send('playing-status', playerState); + } +} + +export function sendQueueItems(event, queueItems) { + ipcRenderer.send('queue', queueItems); +} + +export function sendPlaybackProgress(progress) { + ipcRenderer.send('playbackProgress', progress); +} + +export function sendVolume(volume) { + ipcRenderer.send('volume', volume); +} + +export function sendSeek(position) { + ipcRenderer.send('seek', position); +} + +export function sendSetOption(key, value) { + ipcRenderer.send('set-option', {key, value}); +} diff --git a/app/persistence/store.js b/app/persistence/store.js index 9a1cb9a045..7de09c5baf 100644 --- a/app/persistence/store.js +++ b/app/persistence/store.js @@ -1,15 +1,57 @@ -import low from 'lowdb'; -import FileSync from 'lowdb/adapters/FileSync'; +import _ from 'lodash'; +import electronStore from 'electron-store'; + +import options from '../constants/settings'; +import { restartApi, stopApi, sendSetOption } from '../mpris'; + +const store = new electronStore(); + +function initStore () { + if (!store.get('lastFm')) { + store.set('lastFm', {}); + } + + if (!store.get('settings')) { + store.set('settings', {}); + } + + if (!store.get('playlists')) { + store.set('playlists', []); + } +} -const store = low(new FileSync('nuclear.json')); initStore(); -function initStore() { - store.defaults({lastFm: {}, settings: {}}).write(); - store.write(); +function getOption (key) { + const settings = store.get('settings') || {}; + let value = settings[key]; + + if (typeof value === 'undefined') { + value = _.find(options, { name: key }).default; + } + + return value; +} + +function isValidPort(value) { + return typeof value === 'number' && value > 1024 && value < 49151; } +function setOption (key, value) { + const settings = store.get('settings') || {}; -module.exports = { - store + store.set('settings', Object.assign({}, settings, { [`${key}`]: value })); + + if ( + (key === 'api.port' && isValidPort(value) && getOption('api.enabled')) || + (key === 'api.enabled' && value) + ) { + restartApi(); + } else if (key === 'api.enabled' && !value) { + stopApi(); + } + + sendSetOption(key, value); } + +export { store, getOption, setOption }; diff --git a/app/plugins/Lyrics/index.js b/app/plugins/Lyrics/index.js new file mode 100644 index 0000000000..b5559e0de9 --- /dev/null +++ b/app/plugins/Lyrics/index.js @@ -0,0 +1,9 @@ +import logger from 'electron-timber'; +import lyrics from 'simple-get-lyrics'; + +export function search (artistName, trackName) { + return lyrics.search(artistName, trackName) + .then(result => result.lyrics).catch(function (err) { + logger.log('error', err); + }); +} diff --git a/app/plugins/MusicSources/JamendoPlugin.js b/app/plugins/MusicSources/JamendoPlugin.js new file mode 100644 index 0000000000..67fa015a25 --- /dev/null +++ b/app/plugins/MusicSources/JamendoPlugin.js @@ -0,0 +1,51 @@ +import _ from 'lodash'; + +import globals from '../../globals'; +import MusicSourcePlugin from '../musicSources'; +import * as Jamendo from '../../rest/Jamendo'; + +class JamendoPlugin extends MusicSourcePlugin { + constructor() { + super(); + this.name = 'Jamendo Plugin'; + this.sourceName = 'Jamendo'; + this.description = 'Allows Nuclear to find music streams on Jamendo'; + } + + search (query) { + return this.getSearchResults(query).then(responseJson => { + if (responseJson.results.length === 0) { + return null; + } + + const track = responseJson.results[0].tracks[0]; + + return { + source: this.sourceName, + id: track.id, + stream: track.audio, + duration: track.duration, + title: track.name, + thumbnail: track.image + }; + }); + } + + getSearchResults (query) { + return Jamendo.search(query) + .then(response => response.json()) + .catch(err => { + console.error(`Error looking up streams for ${query.artist} ${query.track} on Jamendo`); + console.error(err); + }); + } + + getAlternateStream (query, currentStream) { + return this.getSearchResults(query).then(results => { + let info = _.find(results, result => result && result.id !== currentStream.id); + return info ? this.resultToStream(info) : null; + }); + } +} + +export default JamendoPlugin; diff --git a/app/plugins/MusicSources/SoundcloudPlugin.js b/app/plugins/MusicSources/SoundcloudPlugin.js new file mode 100644 index 0000000000..cbf0b5d98e --- /dev/null +++ b/app/plugins/MusicSources/SoundcloudPlugin.js @@ -0,0 +1,55 @@ +import _ from 'lodash'; + +import globals from '../../globals'; +import MusicSourcePlugin from '../musicSources'; +import * as Soundcloud from '../../rest/Soundcloud'; + +class SoundcloudPlugin extends MusicSourcePlugin { + constructor() { + super(); + this.name = 'Soundcloud Plugin'; + this.sourceName = 'Soundcloud'; + this.description = 'Allows Nuclear to find music streams on Soundcloud'; + } + + resultToStream(result) { + return { + source: this.sourceName, + id: result.id, + stream: result.stream_url + `?client_id=${globals.soundcloudApiKey}`, + duration: result.duration, + title: result.title, + thumbnail: result.user.avatar_url + }; + } + + search(query) { + let terms = query.artist + ' ' + query.track; + return Soundcloud.soundcloudSearch(terms) + .then(data => data.json()) + .then(results => { + let info = results[0]; + return info ? this.resultToStream(info) : null; + }) + .catch(err => { + console.error(`Error looking up streams for ${terms} on Soundcloud`); + console.error(err); + }); + } + + getAlternateStream(query, currentStream) { + let terms = query.artist + ' ' + query.track; + return Soundcloud.soundcloudSearch(terms) + .then(data => data.json()) + .then(results => { + let info = _.find(results, result => result && result.id !== currentStream.id); + return info ? this.resultToStream(info) : null; + }) + .catch(err => { + console.error(`Error looking up streams for ${terms} on Soundcloud`); + console.error(err); + }); + } +} + +export default SoundcloudPlugin; diff --git a/app/plugins/MusicSources/YoutubePlugin.js b/app/plugins/MusicSources/YoutubePlugin.js index 499f2bd007..9f3c11c05e 100644 --- a/app/plugins/MusicSources/YoutubePlugin.js +++ b/app/plugins/MusicSources/YoutubePlugin.js @@ -1,28 +1,63 @@ +import _ from 'lodash'; +import ytdl from 'ytdl-core'; + import MusicSourcePlugin from '../musicSources'; import * as Youtube from '../../rest/Youtube'; -const _ = require('lodash'); -const ytdl = require('ytdl-core'); - class YoutubePlugin extends MusicSourcePlugin { constructor() { super(); this.name = 'Youtube Plugin'; - this.description = 'A plugin allowing nuclear to search for music and play it from youtube'; + this.sourceName = 'Youtube'; + this.description = 'A plugin allowing Nuclear to search for music and play it from youtube'; + } + + search(query) { + let terms = query.artist + ' ' + query.track; + return Youtube.trackSearch(terms) + .then(results => results.json()) + .then(results => { + let song = _.head(results.items); + let id = song.id.videoId; + return ytdl.getInfo(`http://www.youtube.com/watch?v=${id}`); + }) + .then(videoInfo => { + let thumbnail = _.get(videoInfo, 'player_response.videoDetails.thumbnail.thumbnails'); + thumbnail = _.find(thumbnail, { width: 246 }).url; + let formatInfo = _.head(videoInfo.formats.filter(e => e.itag === '140')); + return { + source: this.sourceName, + id: videoInfo.video_id, + stream: formatInfo.url, + duration: videoInfo.length_seconds, + title: videoInfo.title, + thumbnail, + }; + }); } - search(terms) { + getAlternateStream(query, currentStream) { + let terms = query.artist + ' ' + query.track; return Youtube.trackSearch(terms) - .then(results => results.json()) - .then(results => { - let song = _.head(results.items); - let id = song.id.videoId; - return ytdl.getInfo(`http://www.youtube.com/watch?v=${id}`, ) - }) - .then(videoInfo => { - let formatInfo = _.head(videoInfo.formats.filter(e => e.itag=='140')); - return {stream: formatInfo.url, duration: videoInfo.length_seconds}; - }); + .then(results => results.json()) + .then(results => { + let song = _(results.items).find(item => { + return item && item.id.videoId !== currentStream.id; + }); + let id = song.id.videoId; + return ytdl.getInfo(`http://www.youtube.com/watch?v=${id}`); + }) + .then(videoInfo => { + let formatInfo = _.head(videoInfo.formats.filter(e => e.itag === '140')); + return { + source: 'Youtube', + id: videoInfo.video_id, + stream: formatInfo.url, + duration: videoInfo.length_seconds, + title: videoInfo.title, + thumbnail: videoInfo.thumbnail_url, + }; + }); } } diff --git a/app/plugins/MusicSources/index.js b/app/plugins/MusicSources/index.js index 577260f31b..e6d92e16f3 100644 --- a/app/plugins/MusicSources/index.js +++ b/app/plugins/MusicSources/index.js @@ -1 +1,3 @@ -export {default as YoutubePlugin} from './YoutubePlugin'; +export { default as YoutubePlugin } from './YoutubePlugin'; +export { default as SoundcloudPlugin } from './SoundcloudPlugin'; +export { default as JamendoPlugin } from './JamendoPlugin'; diff --git a/app/plugins/musicSources.js b/app/plugins/musicSources.js index b9f7cbbca9..fcdc19f2b4 100644 --- a/app/plugins/musicSources.js +++ b/app/plugins/musicSources.js @@ -4,13 +4,25 @@ class MusicSourcePlugin extends Plugin { constructor() { super(); this.name = 'Music Source Plugin'; + this.sourceName = 'Generic Music Source'; this.description = 'A generic music source plugin. Should never be instantiated directly'; + this.image = null; } - search(terms) { + search(query) { + /* + query is an object : + { + artist : 'The artist name, + track : 'The track to search' + } + */ console.error('search not implemented in plugin ' + this.name); } + getAlternateStream(query, currentStream) { + console.error('getAlternateStream not implemented in plugin ' + this.name); + } } export default MusicSourcePlugin; diff --git a/app/plugins/plugin.js b/app/plugins/plugin.js index f7d0b3c44f..e2549de6a6 100644 --- a/app/plugins/plugin.js +++ b/app/plugins/plugin.js @@ -2,5 +2,6 @@ export default class Plugin { constructor() { this.name = 'Plugin'; this.description = 'A generic plugin class. Should never be instantiated.'; + this.image = null; } } diff --git a/app/reducers/dashboard.js b/app/reducers/dashboard.js index b325b1098f..bd8b6c02f0 100644 --- a/app/reducers/dashboard.js +++ b/app/reducers/dashboard.js @@ -2,19 +2,28 @@ import { LOAD_BEST_NEW_ALBUMS_START, LOAD_BEST_NEW_ALBUMS_SUCCESS, LOAD_BEST_NEW_ALBUMS_ERROR, - LOAD_BEST_NEW_TRACKS_START, LOAD_BEST_NEW_TRACKS_SUCCESS, - LOAD_BEST_NEW_TRACKS_ERROR + LOAD_BEST_NEW_TRACKS_ERROR, + LOAD_NUCLEAR_NEWS_START, + LOAD_NUCLEAR_NEWS_SUCCESS, + LOAD_NUCLEAR_NEWS_ERROR, + LOAD_TOP_TAGS_START, + LOAD_TOP_TAGS_SUCCESS, + LOAD_TOP_TAGS_ERROR, + LOAD_TOP_TRACKS_START, + LOAD_TOP_TRACKS_SUCCESS, + LOAD_TOP_TRACKS_ERROR } from '../actions/dashboard'; const initialState = { bestNewAlbums: [], - bestNewTracks: [] + bestNewTracks: [], + topTracks: [] }; -export default function DashboardReducer(state=initialState, action) { - switch(action.type) { +export default function DashboardReducer(state = initialState, action) { + switch (action.type) { case LOAD_BEST_NEW_ALBUMS_SUCCESS: return Object.assign({}, state, { bestNewAlbums: action.payload @@ -23,6 +32,18 @@ export default function DashboardReducer(state=initialState, action) { return Object.assign({}, state, { bestNewTracks: action.payload }); + case LOAD_NUCLEAR_NEWS_SUCCESS: + return Object.assign({}, state, { + news: action.payload + }); + case LOAD_TOP_TAGS_SUCCESS: + return Object.assign({}, state, { + topTags: action.payload + }); + case LOAD_TOP_TRACKS_SUCCESS: + return Object.assign({}, state, { + topTracks: action.payload + }); default: return state; } diff --git a/app/reducers/index.js b/app/reducers/index.js index 4f70cf9366..7323fa9de0 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -6,7 +6,11 @@ import PluginsReducer from './plugins'; import QueueReducer from './queue'; import ScrobblingReducer from './scrobbling'; import SearchReducer from './search'; +import SettingsReducer from './settings'; import DashboardReducer from './dashboard'; +import TagReducer from './tag'; +import ToastsReducer from './toasts'; +import LyricsReducer from './lyrics'; const rootReducer = combineReducers({ search: SearchReducer, @@ -15,7 +19,11 @@ const rootReducer = combineReducers({ player: PlayerReducer, scrobbling: ScrobblingReducer, playlists: PlaylistsReducer, - dashboard: DashboardReducer + dashboard: DashboardReducer, + tags: TagReducer, + settings: SettingsReducer, + toasts: ToastsReducer, + lyrics: LyricsReducer }); export default rootReducer; diff --git a/app/reducers/lyrics.js b/app/reducers/lyrics.js new file mode 100644 index 0000000000..01212cbd30 --- /dev/null +++ b/app/reducers/lyrics.js @@ -0,0 +1,25 @@ +import { + LYRIC_SEARCH_START, + LYRIC_SEARCH_SUCCESS +} from '../actions/lyrics'; + +const initialState = { + lyricsSearchStarted: false, + lyricsSearchResults: [] +}; + +export default function LyricsReducer (state = initialState, action) { + + switch (action.type) { + case LYRIC_SEARCH_START: + return Object.assign({}, state, { + lyricsSearchStarted: action.payload + }); + case LYRIC_SEARCH_SUCCESS: + return Object.assign({}, state, { + lyricsSearchResults: action.payload + }); + default: + return state; + } +} diff --git a/app/reducers/player.js b/app/reducers/player.js index 8e277014aa..4b65de89df 100644 --- a/app/reducers/player.js +++ b/app/reducers/player.js @@ -4,7 +4,11 @@ import { START_PLAYBACK, PAUSE_PLAYBACK, UPDATE_PLAYBACK_PROGRESS, - UPDATE_SEEK + UPDATE_SEEK, + UPDATE_VOLUME, + MUTE, + UNMUTE, + UPDATE_PLAYBACK_STREAM_LOADING } from '../actions/player'; import { @@ -15,37 +19,56 @@ import { const initialState = { playbackStatus: Sound.status.PAUSED, + playbackStreamLoading: false, playbackProgress: 0, - seek: 0 + seek: 0, + volume: 100, + muted: false }; export default function PlayerReducer(state=initialState, action) { - switch(action.type) { - case START_PLAYBACK: - return Object.assign({}, state, { - playbackStatus: Sound.status.PLAYING - }); - case PAUSE_PLAYBACK: - return Object.assign({}, state, { - playbackStatus: Sound.status.PAUSED - }); - case UPDATE_PLAYBACK_PROGRESS: - return Object.assign({}, state, { - playbackProgress: action.payload.progress, - seek: action.payload.seek - }); - case UPDATE_SEEK: - return Object.assign({}, state, { - seek: action.payload - }); - case NEXT_SONG: - case PREVIOUS_SONG: - case SELECT_SONG: - return Object.assign({}, state, { - playbackProgress: 0, - seek: 0 - }); - default: - return state; + switch (action.type) { + case START_PLAYBACK: + return Object.assign({}, state, { + playbackStatus: Sound.status.PLAYING + }); + case PAUSE_PLAYBACK: + return Object.assign({}, state, { + playbackStatus: Sound.status.PAUSED + }); + case UPDATE_PLAYBACK_PROGRESS: + return Object.assign({}, state, { + playbackProgress: action.payload.progress, + seek: action.payload.seek + }); + case UPDATE_SEEK: + return Object.assign({}, state, { + seek: action.payload + }); + case UPDATE_VOLUME: + return Object.assign({}, state, { + volume: action.payload + }); + case MUTE: + return Object.assign({}, state, { + muted: true + }); + case UNMUTE: + return Object.assign({}, state, { + muted: false + }); + case NEXT_SONG: + case PREVIOUS_SONG: + case SELECT_SONG: + return Object.assign({}, state, { + playbackProgress: 0, + seek: 0 + }); + case UPDATE_PLAYBACK_STREAM_LOADING: + return Object.assign({}, state, { + playbackStreamLoading: action.payload + }); + default: + return state; } } diff --git a/app/reducers/playlists.js b/app/reducers/playlists.js index 257ebde5d0..918f245078 100644 --- a/app/reducers/playlists.js +++ b/app/reducers/playlists.js @@ -8,7 +8,7 @@ const initialState = { }; export default function PlaylistsReducer(state=initialState, action) { - switch(action.type) { + switch (action.type) { case LOAD_PLAYLISTS: case ADD_PLAYLIST: return Object.assign({}, state, { diff --git a/app/reducers/plugins.js b/app/reducers/plugins.js index 56ec31c479..b34d18fb1b 100644 --- a/app/reducers/plugins.js +++ b/app/reducers/plugins.js @@ -1,18 +1,24 @@ import { - CREATE_PLUGINS + CREATE_PLUGINS, + SELECT_DEFAULT_MUSIC_SOURCE } from '../actions/plugins'; const initialState = { - plugins: [] + plugins: [], + defaultMusicSource: null }; export default function PluginsReducer(state=initialState, action) { - switch(action.type) { - case CREATE_PLUGINS: - return Object.assign({}, state, { - plugins: action.payload - }); - default: - return state; + switch (action.type) { + case CREATE_PLUGINS: + return Object.assign({}, state, { + plugins: action.payload + }); + case SELECT_DEFAULT_MUSIC_SOURCE: + return Object.assign({}, state, { + defaultMusicSource: action.payload + }); + default: + return state; } } diff --git a/app/reducers/queue.js b/app/reducers/queue.js index 923eaaed2b..e96b5eeb33 100644 --- a/app/reducers/queue.js +++ b/app/reducers/queue.js @@ -1,49 +1,126 @@ import { ADD_TO_QUEUE, + REMOVE_FROM_QUEUE, CLEAR_QUEUE, ADD_STREAMS_TO_QUEUE_ITEM, + REPLACE_STREAMS_IN_QUEUE_ITEM, NEXT_SONG, PREVIOUS_SONG, - SELECT_SONG + SELECT_SONG, + SWAP_SONGS } from '../actions/queue'; -var _ = require('lodash'); +let _ = require('lodash'); const initialState = { queueItems: [], currentSong: 0 }; -export default function QueueReducer(state=initialState, action) { +function findQueueItemIndex(queueItems, item) { + return _.findIndex(queueItems, i => i.uuid === item.uuid); +} + +function reduceAddToQueue(state, action) { + return Object.assign({}, state, { + queueItems: _.union(state.queueItems, [action.payload]) + }); +} + +function reduceRemoveFromQueue(state, action) { + let removeIx, newQueue; + let newCurrent = state.currentSong; + removeIx = findQueueItemIndex(state.queueItems, action.payload); + newQueue = _.cloneDeep(state.queueItems); + newQueue = _.filter(newQueue, item => action.payload.uuid !== item.uuid); + if (removeIx < state.currentSong) { + newCurrent--; + } + return Object.assign({}, state, { + queueItems: newQueue, + currentSong: newCurrent + }); +} + +function reduceClearQueue(state, action) { + return Object.assign({}, state, { + queueItems: [] + }); +} + +function reduceAddStreamsToQueueItem(state, action) { + let replaceIx, newQueue; + replaceIx = findQueueItemIndex(state.queueItems, action.payload); + newQueue = _.cloneDeep(state.queueItems); + newQueue[replaceIx] = Object.assign({}, newQueue[replaceIx], action.payload); + + return Object.assign({}, state, { + queueItems: newQueue + }); +} + +function reduceReplaceStreamsInQueueItem(state, action) { + let replaceIx, newQueue; + replaceIx = findQueueItemIndex(state.queueItems, action.payload); + newQueue = _.cloneDeep(state.queueItems); + newQueue[replaceIx] = action.payload; + return Object.assign({}, state, { + queueItems: newQueue + }); +} + +function reduceSelectSong(state, action) { + return Object.assign({}, state, { + currentSong: action.payload + }); +} + +function reduceSwapSongs(state, action) { + let newQueue; + newQueue = _.cloneDeep(state.queueItems); + let temp = newQueue[action.payload.itemFrom]; + newQueue[action.payload.itemFrom] = newQueue[action.payload.itemTo]; + newQueue[action.payload.itemTo] = temp; + return Object.assign({}, state, { + queueItems: newQueue + }); +} + +function reduceNextSong(state, action) { + return Object.assign({}, state, { + currentSong: (state.currentSong + 1) % state.queueItems.length + }); +} + +function reducePreviousSong(state, action) { + return Object.assign({}, state, { + currentSong: + (((state.currentSong - 1) % state.queueItems.length) + + state.queueItems.length) % + state.queueItems.length + }); +} + +export default function QueueReducer(state = initialState, action) { switch (action.type) { case ADD_TO_QUEUE: - return Object.assign({}, state, { - queueItems: _.union(state.queueItems, [action.payload]) - }); + return reduceAddToQueue(state, action); + case REMOVE_FROM_QUEUE: + return reduceRemoveFromQueue(state, action); case CLEAR_QUEUE: - return Object.assign({}, state, { - queueItems: [] - }); + return reduceClearQueue(state, action); case ADD_STREAMS_TO_QUEUE_ITEM: - let replaceIx = _.findIndex(state.queueItems, item => action.payload.artist===item.artist && action.payload.name ===item.name); - let newQueue = _.cloneDeep(state.queueItems); - newQueue[replaceIx] = Object.assign({}, newQueue[replaceIx], action.payload); - - return Object.assign({}, state, { - queueItems: newQueue - }); + return reduceAddStreamsToQueueItem(state, action); + case REPLACE_STREAMS_IN_QUEUE_ITEM: + return reduceReplaceStreamsInQueueItem(state, action); case NEXT_SONG: - return Object.assign({}, state, { - currentSong: (state.currentSong+1) % state.queueItems.length - }); + return reduceNextSong(state, action); case PREVIOUS_SONG: - return Object.assign({}, state, { - currentSong: ((state.currentSong-1) % state.queueItems.length + state.queueItems.length) % state.queueItems.length - }); + return reducePreviousSong(state, action); case SELECT_SONG: - return Object.assign({}, state, { - currentSong: action.payload - }) + return reduceSelectSong(state, action); + case SWAP_SONGS: + return reduceSwapSongs(state, action); default: return state; } diff --git a/app/reducers/scrobbling.js b/app/reducers/scrobbling.js index b420cc05c0..b2a231384b 100644 --- a/app/reducers/scrobbling.js +++ b/app/reducers/scrobbling.js @@ -14,34 +14,34 @@ const initialState = { }; export default function ScrobblingReducer(state=initialState, action) { - switch(action.type) { - case LASTFM_CONNECT: + switch (action.type) { + case LASTFM_CONNECT: + return Object.assign({}, state, { + lastFmAuthToken: action.payload + }); + case LASTFM_LOGIN: + return Object.assign({}, state, { + lastFmName: action.payload.name, + lastFmSessionKey: action.payload.sessionKey + }); + case LASTFM_READ_SETTINGS: + if (action.payload) { return Object.assign({}, state, { - lastFmAuthToken: action.payload + lastFmName: action.payload.lastFmName, + lastFmAuthToken: action.payload.lastFmAuthToken, + lastFmSessionKey: action.payload.lastFmSessionKey, + lastFmScrobblingEnabled: action.payload.lastFmScrobblingEnabled }); - case LASTFM_LOGIN: - return Object.assign({}, state, { - lastFmName: action.payload.name, - lastFmSessionKey: action.payload.sessionKey - }); - case LASTFM_READ_SETTINGS: - if (action.payload) { - return Object.assign({}, state, { - lastFmName: action.payload.lastFmName, - lastFmAuthToken: action.payload.lastFmAuthToken, - lastFmSessionKey: action.payload.lastFmSessionKey, - lastFmScrobblingEnabled: action.payload.lastFmScrobblingEnabled - }); - } - case LASTFM_ENABLE_SCROBBLING: - return Object.assign({}, state, { - lastFmScrobblingEnabled: true - }); - case LASTFM_DISABLE_SCROBBLING: - return Object.assign({}, state, { - lastFmScrobblingEnabled: false - }); - default: - return state; + } + case LASTFM_ENABLE_SCROBBLING: + return Object.assign({}, state, { + lastFmScrobblingEnabled: true + }); + case LASTFM_DISABLE_SCROBBLING: + return Object.assign({}, state, { + lastFmScrobblingEnabled: false + }); + default: + return state; } } diff --git a/app/reducers/search.js b/app/reducers/search.js index d4fc431953..1dfab75a3c 100644 --- a/app/reducers/search.js +++ b/app/reducers/search.js @@ -4,126 +4,205 @@ import { UNIFIED_SEARCH_ERROR, ARTIST_SEARCH_SUCCESS, ALBUM_SEARCH_SUCCESS, - ALBUM_INFO_SEARCH_START, ALBUM_INFO_SEARCH_SUCCESS, - ARTIST_INFO_SEARCH_START, ARTIST_INFO_SEARCH_SUCCESS, - ARTIST_RELEASES_SEARCH_START, ARTIST_RELEASES_SEARCH_SUCCESS, - LASTFM_ARTIST_INFO_SEARCH_START, - LASTFM_ARTIST_INFO_SEARCH_SUCCESS - } from '../actions'; + LASTFM_ARTIST_INFO_SEARCH_SUCCESS, + LASTFM_TRACK_SEARCH_START, + LASTFM_TRACK_SEARCH_SUCCESS, + YOUTUBE_PLAYLIST_SEARCH_START, + YOUTUBE_PLAYLIST_SEARCH_SUCCESS +} from '../actions'; -var _ = require('lodash'); +let _ = require('lodash'); const initialState = { plugins: [], artistSearchResults: [], albumSearchResults: [], + trackSearchResults: [], + playlistSearchResults: [], albumDetails: {}, artistDetails: {}, - unifiedSearchStarted: false + unifiedSearchStarted: false, + playlistSearchStarted: false }; -export default function SearchReducer(state=initialState, action) { +function reduceUnifiedSearchStart (state, action) { + return Object.assign({}, state, { + unifiedSearchStarted: action.payload + }); +} +function reduceAlbumSearchSuccess (state, action) { + return Object.assign({}, state, { + albumSearchResults: action.payload + }); +} + +function reduceArtistSearchSuccess (state, action) { + return Object.assign({}, state, { + artistSearchResults: action.payload + }); +} + +function reduceUnifiedSearchSuccess (state, action) { + return Object.assign({}, state, { + unifiedSearchStarted: action.payload + }); +} + +function reduceAlbumInfoSearchStart (state, action) { + return Object.assign({}, state, { + albumDetails: Object.assign({}, state.albumDetails, { + [`${action.payload}`]: Object.assign({}, { loading: true }) + }) + }); +} + +function reduceAlbumInfoSearchSuccess (state, action) { + return Object.assign({}, state, { + albumDetails: Object.assign({}, state.albumDetails, { + [`${action.payload.id}`]: Object.assign({}, action.payload.info, { + loading: false + }) + }) + }); +} + +function reduceArtistInfoSearchStart (state, action) { + return Object.assign({}, state, { + artistDetails: Object.assign({}, state.artistDetails, { + [`${action.payload}`]: { + loading: true + } + }) + }); +} + +function reduceArtistInfoSearchSuccess (state, action) { + return Object.assign({}, state, { + artistDetails: Object.assign({}, state.artistDetails, { + [`${action.payload.id}`]: Object.assign({}, action.payload.info, { + loading: false + }) + }) + }); +} +function reduceArtistReleasesSearchStart (state, action) { + return Object.assign({}, state, { + artistDetails: Object.assign({}, state.artistDetails, { + [`${action.payload}`]: Object.assign( + {}, + state.artistDetails[`${action.payload}`], + { releases: [] } + ) + }) + }); +} + +function reduceArtistReleasesSearchSuccess (state, action) { + return Object.assign({}, state, { + artistDetails: Object.assign({}, state.artistDetails, { + [`${action.payload.id}`]: Object.assign( + {}, + state.artistDetails[`${action.payload.id}`], + { releases: action.payload.releases.releases } + ) + }) + }); +} + +function reduceLastfmArtistInfoSearchStart (state, action) { + return Object.assign({}, state, { + artistDetails: Object.assign({}, state.artistDetails, { + [`${action.payload}`]: Object.assign( + {}, + state.artistDetails[`${action.payload}`], + { lastfm: { loading: true } } + ) + }) + }); +} + +function reduceLastfmArtistInfoSearchSuccess (state, action) { + return Object.assign({}, state, { + artistDetails: Object.assign({}, state.artistDetails, { + [`${action.payload.id}`]: Object.assign( + {}, + state.artistDetails[`${action.payload.id}`], + { + lastfm: Object.assign({}, action.payload.info, { + loading: false + }) + } + ) + }) + }); +} + +function reduceLastfmTrackSearchStart (state, action) { + return Object.assign({}, state, { + trackSearchResults: action.payload + }); +} + +function reduceLastfmTrackSearchSuccess (state, action) { + return Object.assign({}, state, { + trackSearchResults: action.payload + }); +} + +function reduceYoutubePlaylistSearchStart (state, action) { + return Object.assign({}, state, { + playlistSearchStarted: action.payload, + playlistSearchResults: [] + }); +} + +function reduceYoutubePlaylistSearchSuccess (state, action) { + return Object.assign({}, state, { + playlistSearchResults: action.payload + }); +} + +export default function SearchReducer (state = initialState, action) { switch (action.type) { case UNIFIED_SEARCH_START: - return Object.assign({}, state, { - unifiedSearchStarted: action.payload - }); + return reduceUnifiedSearchStart(state, action); case ALBUM_SEARCH_SUCCESS: - return Object.assign({}, state, { - albumSearchResults: action.payload - }); + return reduceAlbumSearchSuccess(state, action); case ARTIST_SEARCH_SUCCESS: - return Object.assign({}, state, { - artistSearchResults: action.payload - }); + return reduceArtistSearchSuccess(state, action); case UNIFIED_SEARCH_SUCCESS: - return Object.assign({}, state, { - unifiedSearchStarted: action.payload - }); + return reduceUnifiedSearchSuccess(state, action); case ALBUM_INFO_SEARCH_START: - return Object.assign({}, state, { - albumDetails: Object.assign({}, state.albumDetails, { - [`${action.payload}`]: Object.assign( - {}, - {loading: true} - ) - }) - }); + return reduceAlbumInfoSearchStart(state, action); case ALBUM_INFO_SEARCH_SUCCESS: - return Object.assign({}, state, { - albumDetails: Object.assign({}, state.albumDetails, { - [`${action.payload.id}`]: Object.assign( - {}, - action.payload.info, - {loading: false} - ) - }) - }); + return reduceAlbumInfoSearchSuccess(state, action); case ARTIST_INFO_SEARCH_START: - return Object.assign({}, state, { - artistDetails: Object.assign({}, state.artistDetails, { - [`${action.payload}`]: { - loading: true - } - }) - }); + return reduceArtistInfoSearchStart(state, action); case ARTIST_INFO_SEARCH_SUCCESS: - return Object.assign({}, state, { - artistDetails: Object.assign({}, state.artistDetails, { - [`${action.payload.id}`]: Object.assign( - {}, - action.payload.info, - {loading: false} - ) - }) - }); + return reduceArtistInfoSearchSuccess(state, action); case ARTIST_RELEASES_SEARCH_START: - return Object.assign({}, state, { - artistDetails: Object.assign({}, state.artistDetails, { - [`${action.payload}`]: Object.assign( - {}, - state.artistDetails[`${action.payload}`], - {releases: []} - ) - }) - }); + return reduceArtistReleasesSearchStart(state, action); case ARTIST_RELEASES_SEARCH_SUCCESS: - return Object.assign({}, state, { - artistDetails: Object.assign({}, state.artistDetails, { - [`${action.payload.id}`]: Object.assign( - {}, - state.artistDetails[`${action.payload.id}`], - {releases: action.payload.releases.releases} - ) - }) - }); - case LASTFM_ARTIST_INFO_SEARCH_START: - return Object.assign({}, state, { - artistDetails: Object.assign({}, state.artistDetails, { - [`${action.payload}`]: Object.assign ( - {}, - state.artistDetails[`${action.payload}`], - {lastfm: {loading: true}} - ) - }) - }); - case LASTFM_ARTIST_INFO_SEARCH_SUCCESS: - return Object.assign({}, state, { - artistDetails: Object.assign({}, state.artistDetails, { - [`${action.payload.id}`]: Object.assign( - {}, - state.artistDetails[`${action.payload.id}`], - {lastfm: Object.assign({}, action.payload.info, {loading: false}) - }) - }) - }); + return reduceArtistReleasesSearchSuccess(state, action); + case LASTFM_ARTIST_INFO_SEARCH_START: + return reduceLastfmArtistInfoSearchStart(state, action); + case LASTFM_ARTIST_INFO_SEARCH_SUCCESS: + return reduceLastfmArtistInfoSearchSuccess(state, action); + case LASTFM_TRACK_SEARCH_START: + return reduceLastfmTrackSearchStart(state, action); + case LASTFM_TRACK_SEARCH_SUCCESS: + return reduceLastfmTrackSearchSuccess(state, action); + case YOUTUBE_PLAYLIST_SEARCH_START: + return reduceYoutubePlaylistSearchStart(state, action); + case YOUTUBE_PLAYLIST_SEARCH_SUCCESS: + return reduceYoutubePlaylistSearchSuccess(state, action); default: return state; } diff --git a/app/reducers/settings.js b/app/reducers/settings.js new file mode 100644 index 0000000000..50c1705f2f --- /dev/null +++ b/app/reducers/settings.js @@ -0,0 +1,28 @@ +import { + READ_SETTINGS, + SET_BOOLEAN_OPTION, + SET_STRING_OPTION, + SET_NUMBER_OPTION +} from '../actions/settings'; +import settingsOptions from '../constants/settings'; + +const initialState = {}; +const defaultSettings = settingsOptions.reduce((acc, option) => ({ + ...acc, + [option.name]: option.default +}), {}); + +export default function SettingsReducer(state=initialState, action) { + switch (action.type) { + case READ_SETTINGS: + return Object.assign(defaultSettings, action.payload); + case SET_BOOLEAN_OPTION: + case SET_STRING_OPTION: + case SET_NUMBER_OPTION: + return Object.assign({}, state, { + [`${action.payload.option}`]: action.payload.state + }); + default: + return state; + } +} diff --git a/app/reducers/tag.js b/app/reducers/tag.js new file mode 100644 index 0000000000..54b8a83d0f --- /dev/null +++ b/app/reducers/tag.js @@ -0,0 +1,28 @@ +import { + LOAD_TAG_INFO_START, + LOAD_TAG_INFO_SUCCESS, + LOAD_TAG_INFO_ERROR +} from '../actions/tag'; + +const initialState = { + +}; + +export default function TagReducer(state=initialState, action) { + switch (action.type) { + case LOAD_TAG_INFO_START: + return Object.assign({}, state, { + [`${action.payload}`]: { loading: true } + }); + case LOAD_TAG_INFO_ERROR: + return Object.assign({}, state, { + [`${action.payload}`]: { error: true } + }); + case LOAD_TAG_INFO_SUCCESS: + return Object.assign({}, state, { + [`${action.payload.tag}`]: action.payload.data + }); + default: + return state; + } +} diff --git a/app/reducers/toasts.js b/app/reducers/toasts.js new file mode 100644 index 0000000000..e9991297a8 --- /dev/null +++ b/app/reducers/toasts.js @@ -0,0 +1,25 @@ +import _ from 'lodash'; + +import { + ADD_NOTIFICATION, + REMOVE_NOTIFICATION +} from '../actions/toasts'; + +const initialState = { + notifications: [] +}; + +export default function ToastsReducer(state=initialState, action) { + switch (action.type) { + case ADD_NOTIFICATION: + return Object.assign({}, state, { + notifications: _.concat(state.notifications, action.payload) + }); + case REMOVE_NOTIFICATION: + return Object.assign({}, state, { + notifications: _.filter(state.notifications, n => n.id !== action.payload) + }); + default: + return state; + } +} diff --git a/app/rest/Billboard.js b/app/rest/Billboard.js new file mode 100644 index 0000000000..fd62e9d373 --- /dev/null +++ b/app/rest/Billboard.js @@ -0,0 +1,31 @@ +import { getChart } from 'billboard-top-100'; + +export var lists = { + genres: [ + { + name: 'Mainstream Top 40', + link: 'pop-songs' + }, + { + name: 'Adult Contemporary', + link: 'adult-contemporary' + }, + { + name: 'Adult Top 40', + link: 'adult-pop-songs' + } + + ] +}; + +export function getTop(list) { + return new Promise((resolve, reject) => { + getChart(list, (songs, err) => { + if (err) { + reject(err); + } else { + resolve(songs); + } + }); + }); +} diff --git a/app/rest/CoverArtArchive.js b/app/rest/CoverArtArchive.js index af1ee7057c..3ec115efaa 100644 --- a/app/rest/CoverArtArchive.js +++ b/app/rest/CoverArtArchive.js @@ -1,24 +1,24 @@ -const apiUrl = "http://coverartarchive.org/" +const apiUrl = 'http://coverartarchive.org/'; function releaseGroupFront(group, size=250) { return new Promise((fulfill, reject) => { fetch(apiUrl + 'release-group/' + group.id + '/' + 'front-' + size) - .then(cover => { - if (!cover.ok) { + .then(cover => { + if (!cover.ok) { + group.cover = null; + fulfill(group); + } else { + group.cover = cover.url; + fulfill(group); + } + }) + .catch(error => { group.cover = null; fulfill(group); - } else { - group.cover = cover.url; - fulfill(group); - } - }) - .catch(error => { - group.cover = null; - fulfill(group); - }); + }); }); } module.exports = { releaseGroupFront -} +}; diff --git a/app/rest/Discogs.js b/app/rest/Discogs.js index 4e86756eeb..17b91283d2 100644 --- a/app/rest/Discogs.js +++ b/app/rest/Discogs.js @@ -3,80 +3,57 @@ const userToken = 'QDUeFOZNwIwOePlxpVziEHzamhbIHUdfENAJTnLR'; const key = 'EZaGPpKGBbTkjwmpjmNY'; const secret = 'uluhDSPtelRtLUvjrvQhRBnNwpZMtkZq'; -function addToken(query, first=false) { - var newQuery = query + '&token=' + userToken; - if (first) - return newQuery.replace('&', '?'); - else - return newQuery; +function addToken (query, first = false) { + let newQuery = query + '&token=' + userToken; + return first ? newQuery.replace('&', '?') : newQuery; } -function addKeys(query, first=false) { - var newQuery = query + '&key=' + key + '&secret=' + secret; - if (first) - return newQuery.replace('&', '?'); - else - return newQuery; +function addKeys (query, first = false) { + let newQuery = query + '&key=' + key + '&secret=' + secret; + return first ? newQuery.replace('&', '?') : newQuery; } -function searchQuery(terms, count=15) { +function searchQuery (terms, count = 15) { + // Strip # manually to prevent it being interpreted as anchor separator + terms = terms.replace('#', ''); + return addToken( - apiUrl - + 'database/search' - + '?q=' - + encodeURI(terms) - + '&per_page=' - + count + apiUrl + 'database/search' + '?q=' + encodeURIComponent(terms) + '&per_page=' + count ); } -function searchArtists(terms, count=15) { - return fetch(searchQuery(terms, count) - + '&type=artist' - ); +function search (terms, type, count = 15) { + return fetch(searchQuery(terms, count) + '&type=' + type); } -function searchReleases(terms, count=15) { - return fetch(searchQuery(terms, count) - + '&type=master' - ); +function releaseInfo (releaseId, releaseType) { + if (releaseType === 'master') { + return fetch(addToken(apiUrl + 'masters/' + releaseId, true)); + } else if (releaseType === 'release') { + return fetch(addToken(apiUrl + 'releases/' + releaseId, true)); + } } -function releaseInfo(releaseId) { - return fetch( - addToken( - apiUrl - + 'masters/' - + releaseId, - true)); - } - -function artistInfo(artistId) { - return fetch( - addToken( - apiUrl - + 'artists/' - + artistId, - true) - ); - } +function artistInfo (artistId) { + return fetch(addToken(apiUrl + 'artists/' + artistId, true)); +} -function artistReleases(artistId) { +function artistReleases (artistId) { return fetch( addToken( - apiUrl - + 'artists/' - + artistId - + '/releases', - true + apiUrl + + 'artists/' + + artistId + + '/releases' + + '?sort=year&sort_order=desc', + false ) ); } module.exports = { - searchArtists, - searchReleases, + search, releaseInfo, artistInfo, artistReleases -} +}; diff --git a/app/rest/Jamendo.js b/app/rest/Jamendo.js new file mode 100644 index 0000000000..37efc4a86c --- /dev/null +++ b/app/rest/Jamendo.js @@ -0,0 +1,18 @@ +import globals from '../globals'; + +export function search (query) { + const limit = 10; + const url = + 'https://api.jamendo.com/v3.0/artists/tracks/' + + '?client_id=' + + globals.jamendoClientId + + '&format=jsonpretty' + + '&limit=' + + limit + + '&name=' + + encodeURIComponent(query.artist) + + '&track_name=' + + encodeURIComponent(query.track); + + return fetch(url); +} diff --git a/app/rest/Lastfm.js b/app/rest/Lastfm.js deleted file mode 100644 index b944d6a1b0..0000000000 --- a/app/rest/Lastfm.js +++ /dev/null @@ -1,87 +0,0 @@ -import globals from '../globals'; -const _ = require('lodash'); - -const apiUrl = 'http://www.last.fm/api/'; -const scrobblingApiUrl = 'http://ws.audioscrobbler.com/2.0/'; - -function sign(url) { - let tokens = decodeURIComponent((url.split('?')[1].split('&').sort().join()).replace(/,/g, '').replace(/=/g,'')); - - return require('md5')(tokens+globals.lastfmApiSecret); -} - -function prepareUrl(url) { - var withApiKey = `${url}&api_key=${globals.lastfmApiKey}`; - return `${withApiKey}&api_sig=${sign(withApiKey)}` ; -} - -function addApiKey(url) { - return `${url}&api_key=${globals.lastfmApiKey}`; -} - -function lastFmLoginConnect() { - return fetch(prepareUrl(scrobblingApiUrl + '?method=auth.getToken&format=json')); -} - -function lastFmLogin(authToken) { - return fetch(prepareUrl(scrobblingApiUrl + '?method=auth.getSession&token=' + authToken)+'&format=json'); -} - -function scrobble(artist, track, session) { - return fetch(prepareUrl( - scrobblingApiUrl + - '?method=track.scrobble&sk=' + - session + - '&artist=' + - encodeURIComponent(artist) + - '&track=' + - encodeURIComponent(track) + - '×tamp=' + - (Math.floor(new Date()/1000 - 540))), - { - method: 'POST' - } - ); -} - -function updateNowPlaying(artist, track, session) { - return fetch(prepareUrl( - scrobblingApiUrl + - '?method=track.updateNowPlaying&sk=' + - session + - '&artist=' + - encodeURIComponent(artist) + - '&track=' + - encodeURIComponent(track)), - { - method: 'POST' - } - ); -} - -function getArtistInfo(artist) { - return fetch (addApiKey( - scrobblingApiUrl + - '?method=artist.getinfo&artist=' + - encodeURIComponent(artist) + - '&format=json' - )); -} - -function getArtistTopTracks(artist) { - return fetch (addApiKey( - scrobblingApiUrl + - '?method=artist.gettoptracks&artist=' + - encodeURIComponent(artist) + - '&format=json' - )); -} - -module.exports = { - lastFmLoginConnect, - lastFmLogin, - scrobble, - updateNowPlaying, - getArtistInfo, - getArtistTopTracks -}; diff --git a/app/rest/Musicbrainz.js b/app/rest/Musicbrainz.js index 40044fd4f3..0a70dc49f1 100644 --- a/app/rest/Musicbrainz.js +++ b/app/rest/Musicbrainz.js @@ -1,30 +1,36 @@ const NB = require('nodebrainz'); -const nb = new NB({userAgent:'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0'}); +const nb = new NB({userAgent: 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0'}); const covers = require('./CoverArtArchive'); function artistSearch(terms) { return new Promise((fulfill, reject) => { nb.search('artist', {artist: terms}, (err, response) => { - if (err) reject(err); - else fulfill(response); + if (err) { + reject(err); + } else { + fulfill(response); + } }); }); } function addCoversToReleases(searchResults) { - var coverPromises = searchResults['release-groups'].map(group => { - return covers.releaseGroupFront(group); - }); + let coverPromises = searchResults['release-groups'].map(group => { + return covers.releaseGroupFront(group); + }); - return Promise.all(coverPromises); + return Promise.all(coverPromises); } function releaseSearch(terms) { - var nbSearch = new Promise((fulfill, reject) => { + let nbSearch = new Promise((fulfill, reject) => { nb.search('release-group', {release: terms}, (err, response) => { - if (err) reject(err); - else fulfill(response); - }) + if (err) { + reject(err); + } else { + fulfill(response); + } + }); }); return nbSearch; @@ -33,8 +39,11 @@ function releaseSearch(terms) { function trackSearch(terms) { return new Promise((fulfill, reject) => { nb.search('work', {work: terms}, (err, response) => { - if (err) reject(err); - else fulfill(response); + if (err) { + reject(err); + } else { + fulfill(response); + } }); }); } @@ -44,4 +53,4 @@ module.exports = { releaseSearch, trackSearch, addCoversToReleases -} +}; diff --git a/app/rest/Nuclear.js b/app/rest/Nuclear.js new file mode 100644 index 0000000000..b696462e14 --- /dev/null +++ b/app/rest/Nuclear.js @@ -0,0 +1,23 @@ +const nuclearNewsUrl = 'http://nuclear.gumblert.tech/news/'; + +export function getNewsIndex() { + return fetch(nuclearNewsUrl) + .then(response => response.json()) + .then(response => { + return Promise.resolve(response); + }) + .catch(err => { + console.error(err); + }); +} + +export function getNewsItem(itemName) { + return fetch(nuclearNewsUrl + itemName) + .then(response => response.json()) + .then(response => { + return Promise.resolve(response); + }) + .catch(err => { + console.error(err); + }); +} diff --git a/app/rest/Soundcloud.js b/app/rest/Soundcloud.js new file mode 100644 index 0000000000..f934ef0108 --- /dev/null +++ b/app/rest/Soundcloud.js @@ -0,0 +1,10 @@ +import globals from '../globals'; +const apiUrl = 'https://api.soundcloud.com'; + +function prepareUrl(url) { + return `${url}&client_id=${globals.soundcloudApiKey}`; +} + +export function soundcloudSearch(terms) { + return fetch(prepareUrl(apiUrl + '/tracks?limit=50&q=' + terms)); +} diff --git a/app/rest/Youtube.js b/app/rest/Youtube.js index b30d297f73..ae2bf37119 100644 --- a/app/rest/Youtube.js +++ b/app/rest/Youtube.js @@ -1,13 +1,110 @@ +import core from 'nuclear-core'; +import ytdl from 'ytdl-core'; + import globals from '../globals'; +import ytlist from 'youtube-playlist'; +import getArtistTitle from 'get-artist-title'; + +const lastfm = new core.LastFmApi( + globals.lastfmApiKey, + globals.lastfmApiSecret +); -function prepareUrl(url) { +function prepareUrl (url) { return `${url}&key=${globals.ytApiKey}`; } -function trackSearch(track) { - return fetch(prepareUrl("https://www.googleapis.com/youtube/v3/search?part=id,snippet&type=video&maxResults=50&q="+encodeURIComponent(track))); +export function trackSearch (track) { + return fetch(prepareUrl('https://www.googleapis.com/youtube/v3/search?part=id,snippet&type=video&maxResults=50&q=' + encodeURIComponent(track))); +} + +function isValidURL (str) { + let pattern = new RegExp('^(https?:\\/\\/)' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name and extension + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?' + // port + '(\\/[-a-z\\d%@_.~+&:]*)*' + // path + '(\\?[;&a-z\\d%@_.,~+&:=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator + return pattern.test(str); +} + +function analyseUrlType (url) { + let analysisResult = { + url: url, + isValid: false, + isYoutube: false, + isYoutubePlaylist: false, + isYoutubeVideo: false + }; + analysisResult.isValid = isValidURL(url); + let isYoutubeRegex = /https\:\/\/www.youtube.com\/*./g; + let isYoutubePlaylistRegex = /[?&]list=([a-zA-Z0-9-_]*)/g; + let isYoutubeVideoRegex = /[?&]v=([a-zA-Z0-9-_]{11})[^0-9a-zA-Z_-]{0,1}/g; + analysisResult.isYoutube = url.match(isYoutubeRegex); + analysisResult.isYoutubePlaylist = analysisResult.isValid && analysisResult.isYoutube && url.match(isYoutubePlaylistRegex); + analysisResult.isYoutubeVideo = analysisResult.isValid && analysisResult.isYoutube && url.match(isYoutubeVideoRegex); + return analysisResult; +} + +function getTrackFromTitle (title) { + let result = getArtistTitle(title); + if (result) { + return lastfm.searchTracks(result[0] + ' ' + result[1], 1) + .then(tracks => tracks.json()) + .then(tracksJson => { + return new Promise((resolve) => { + resolve(tracksJson.results.trackmatches.track[0]); + }); + }); + } else { + return new Promise((resolve) => { + resolve({}); + }); + } } -module.exports = { - trackSearch -}; +function handleYoutubePlaylist (url) { + return ytlist(url, 'name') + .then(res => { + let allTracks = res.data.playlist.map((elt) => { + return getTrackFromTitle(elt); + }); + return Promise.all(allTracks); + }) + .catch(function () { + return new Promise((resolve) => { + resolve([]); + }); + }); +} + +function handleYoutubeVideo (url) { + return ytdl.getInfo(url) + .then(info => { + return getTrackFromTitle(info.title) + .then(track => { + return [track]; + }); + }) + .catch(function (err) { + // console.log('error', err); + return Promise.resolve([]); + }); +} + +export function urlSearch (url) { + let urlAnalysis = analyseUrlType(url); + // console.log(urlAnalysis); + if (urlAnalysis.isYoutubePlaylist) { + return handleYoutubePlaylist(url); + } else if (urlAnalysis.isYoutubeVideo) { + return handleYoutubeVideo(url); + } else { + return new Promise((resolve) => { + resolve([]); + }); + } +} + + diff --git a/app/store/configureStore.js b/app/store/configureStore.js index 553dff5e99..1449d429df 100644 --- a/app/store/configureStore.js +++ b/app/store/configureStore.js @@ -1,11 +1,14 @@ import { createStore, compose, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import ReduxPromise from 'redux-promise'; +import { composeWithDevTools } from 'remote-redux-devtools'; import rootReducer from '../reducers'; export default function configureStore(initialState) { - const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; + const composeEnhancers = process.env.NODE_ENV === 'production' + ? compose + : window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || composeWithDevTools; const store = createStore( rootReducer, @@ -15,7 +18,7 @@ export default function configureStore(initialState) { ) ); - if(module.hot) { + if (module.hot) { module.hot.accept('../reducers', () => { store.replaceReducer(require('../reducers').default); }); diff --git a/app/styles.scss b/app/styles.scss index 3b0a65014c..d8f3fb12de 100644 --- a/app/styles.scss +++ b/app/styles.scss @@ -1,23 +1,35 @@ -@import "vars"; +@import 'vars'; .app_container { position: absolute; display: flex; + flex-flow: column; width: 100%; height: 100%; color: $grey; - flex-flow: column; + animation: init $long-duration ease-in-out; +} + +@keyframes init { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } } .panel_container { + position: relative; display: flex; - flex: 1 1 auto; flex-flow: row; + height: 100%; } @@ -25,49 +37,49 @@ z-index: 20; display: flex; + flex-flow: row; height: 50px; background-color: $background; - box-shadow: 0px 5px 5px 0px rgba(0, 0, 0, 0.18), 0px 5px 20px 0px rgba(0, 0, 0, 0.09); + box-shadow: 0px 5px 5px 0px rgba(0, 0, 0, 0.18), + 0px 5px 20px 0px rgba(0, 0, 0, 0.09); -webkit-app-region: drag; - flex-flow: row; } .left_panel { z-index: 10; display: flex; + flex: 0 0 auto; + flex-flow: column; width: 300px; background-color: $background; - box-shadow: 5px 0px 5px 0px rgba(0, 0, 0, 0.18), 5px 0px 20px 0px rgba(0, 0, 0, 0.09); - - flex-flow: column; - flex: 0 0 auto; + box-shadow: 5px 0px 5px 0px rgba(0, 0, 0, 0.18), + 5px 0px 20px 0px rgba(0, 0, 0, 0.09); } .center_panel { - background-color: $background; - flex: 1 1 auto; + background-color: $background; } .right_panel { z-index: 10; display: flex; + flex: 0 0 auto; + flex-flow: column; width: 300px; background-color: $background; - box-shadow: -5px 0px 5px 0px rgba(0, 0, 0, 0.18), -5px 0px 20px 0px rgba(0, 0, 0, 0.09); - - flex-flow: column; - flex: 0 0 auto; + box-shadow: -5px 0px 5px 0px rgba(0, 0, 0, 0.18), + -5px 0px 20px 0px rgba(0, 0, 0, 0.09); } .footer { @@ -75,34 +87,74 @@ display: flex; overflow-y: hidden; + flex: 0 0 auto; + flex-flow: column; - height: 80px; + width: 100%; + max-width: 100%; background-color: $background3; - box-shadow: 0px -5px 5px 0px rgba(0, 0, 0, 0.18), 0px -10px 20px 0px rgba(0, 0, 0, 0.09); - - flex: 0 0 auto; - flex-flow: column; + box-shadow: 0px -5px 5px 0px rgba(0, 0, 0, 0.18), + 0px -10px 20px 0px rgba(0, 0, 0, 0.09); } .footer_horizontal { + position: relative; display: flex; - - flex-flow: row; - flex: 1 1 auto; align-items: center; - justify-content: space-between; + flex: 1 1 auto; + flex-flow: row; + justify-content: center; + width: 100%; + max-width: 100%; } .track_info_wrapper { display: flex; - + flex: 1 1 auto; flex-flow: row; - flex: 0 0 33%; + overflow: hidden; } .version_string { + margin: 0.5rem 0; + + text-align: center; + color: $lightbg; +} + +.active_nav_link { + color: $white !important; + border-left: 0.5rem solid $green; +} + +.sidebar_brand { text-align: center; - margin: 0.5rem 0; + padding: 1rem; +} + +.sidebar_footer { + display: flex; + flex-flow: row; + justify-content: flex-end; + margin: 0.5rem; + + a { + display: flex; + justify-content: center; + align-items: center; + background: $background2; + color: $white !important; + width: 2rem; + height: 2rem; + + &:hover { + background: lighten($background2, 10%); + } + + &:active { + background: lighten($background2, 20%); + } + } } diff --git a/app/utils.js b/app/utils.js index 1f765358fc..b8e27eefc6 100644 --- a/app/utils.js +++ b/app/utils.js @@ -1,16 +1,43 @@ -export function formatDuration(duration) { - var sec_num = parseInt(duration, 10); - var hours = Math.floor(sec_num / 3600); - var minutes = Math.floor((sec_num - (hours * 3600)) / 60); - var seconds = sec_num - (hours * 3600) - (minutes * 60); +import _ from 'lodash'; - if (hours < 10) {hours = "0"+hours;} - if (minutes < 10) {minutes = "0"+minutes;} - if (seconds < 10) {seconds = "0"+seconds;} +export function formatDuration (duration) { + let secNum = parseInt(duration, 10); + let hours = Math.floor(secNum / 3600); + let minutes = Math.floor((secNum - (hours * 3600)) / 60); + let seconds = secNum - (hours * 3600) - (minutes * 60); - if (hours==0) { - return minutes+':'+seconds; - } else { - return hours+':'+minutes+':'+seconds; + if (hours < 10) { + hours = '0' + hours; + } + if (minutes < 10) { + minutes = '0' + minutes; + } + if (seconds < 10) { + seconds = '0' + seconds; + } + + if (hours === '00') { + return minutes + ':' + seconds; + } else { + return hours + ':' + minutes + ':' + seconds; + } +} + +export function stringDurationToSeconds (duration) { + if (duration.length > 0) { + const parts = duration.split(':'); + if (parts.length === 2) { + parts.unshift(0); } + return parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[1]); + } + return 0; +} + +export function getSelectedStream (streams, defaultMusicSource) { + let selectedStream = _.find(streams, { source: defaultMusicSource }); + + return typeof selectedStream === 'undefined' + ? streams ? streams[0] : null + : selectedStream; } diff --git a/app/vars.scss b/app/vars.scss index b93bdab258..cd289922a0 100644 --- a/app/vars.scss +++ b/app/vars.scss @@ -1,7 +1,7 @@ $background: #282a36; $background2: #44475a; $background3: #21222c; -$lightbg: #7E83A6; +$lightbg: #7e83a6; $grey: #f8f8f2; $blue: #6272a4; $cyan: #8be9fd; @@ -11,5 +11,10 @@ $pink: #ff79c6; $purple: #bd93f9; $red: #ff5555; $yellow: #f1fa8c; -$white: #FAFAFA; +$white: #fafafa; $black: #202020; + +$very-short-duration: 0.1s; +$short-duration: 0.3s; +$medium-duration: 0.5s; +$long-duration: 1s; diff --git a/build/512.png b/build/512.png new file mode 100644 index 0000000000..645b73de29 Binary files /dev/null and b/build/512.png differ diff --git a/build/icon.icns b/build/icon.icns new file mode 100644 index 0000000000..f436bb5d73 Binary files /dev/null and b/build/icon.icns differ diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000000..645b73de29 Binary files /dev/null and b/build/icon.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..4ec1afda7f --- /dev/null +++ b/docs/index.md @@ -0,0 +1,29 @@ +## Nuclear Music Player Manual + +### About + +Nuclear is a free music streaming program that pulls content from free sources all over the internet. + +This means that you can search for your favourite artists, albums, and songs, and the player will find information about these, as well as song streams aggregating data from multiple sources. + +### Search + +The primary sources of data on music-related entities are Discogs and Last.fm, which offer rich APIs. + +Discogs powers the following: + + - Artist search + - Album search + +Last.fm augments data returned by Discogs by adding extra information. When you visit an artist, or album page, Discogs provides broad information such as the name, images, list of releases, album covers, and so on, while Last.fm is simultaneously asked for more detailed information such as top tracks, Musicbrainz id, tags, short bio, and similar artists. + +### Links + +| Site | Website | +|-------------|------------------------------------------------------| +| Github | | +| ReadTheDocs | | +| Mastodon | | +| Twitter | | +| Beerpay | | +| AUR | | diff --git a/index.html b/index.html index 5d59781b6f..54d6585f50 100644 --- a/index.html +++ b/index.html @@ -1,17 +1,98 @@ - - - nuclear music player - - - - - - -
- - - - + + + Nuclear Music Player + + + + + + +
+
+
+ + + +
+
+ diff --git a/index.prod.html b/index.prod.html index 480c713b71..dee97e3ad8 100644 --- a/index.prod.html +++ b/index.prod.html @@ -3,14 +3,26 @@ nuclear music player + + + + - + -
+
+
+
+ + + +
+
- + diff --git a/loader.css b/loader.css new file mode 100644 index 0000000000..b04402f4c2 --- /dev/null +++ b/loader.css @@ -0,0 +1,76 @@ +body { + background: #282a36; +} + +.loader-container { + position: absolute; + width: 100%; + height: 100%; + background: transparent; + display: flex; + justify-content: center; + align-items: center; + z-index: -1; +} + +.nuclear-loader { + position: relative; + display: flex; + flex-flow: column; + background: linear-gradient(45deg, #43CBFF 0%, #9708CC 100%); + background-size: 200% 200%; + width: 15rem; + height: 15rem; + border-radius: 2rem; + box-shadow: 0 0 38px rgba(0,0,0,0.30), 0 0 12px rgba(0,0,0,0.22); + animation: gradient-anim 2s ease-in-out infinite; +} + +@keyframes gradient-anim { + 0%, 100% { + background-position: 50% 50%; + } + + 25% { + background-position: 0% 100%; + } + + 75% { + background-position: 100% 0%; + } +} + +.nuclear-loader>span { + position: absolute; + width: 0; + height: 0; + border-top: 4rem solid #FAFAFA; + border-left: 3rem solid transparent; + border-right: 3rem solid transparent; +} + +.nuclear-loader>span:nth-child(1) { + right: 4.5rem; + top: 3.5rem; +} + +.nuclear-loader>span:nth-child(2) { + right: 7.5rem; + bottom: 3.5rem; + +} + +.nuclear-loader>span:nth-child(3) { + left: 7.5rem; + bottom: 3.5rem; +} + +@keyframes loader-anim { + 0%, 100% { + border-top: 4rem solid #FAFAFA; + } + + 50% { + border-top: 4rem solid transparent; + } +} diff --git a/main.js b/main.js deleted file mode 100644 index e997449f92..0000000000 --- a/main.js +++ /dev/null @@ -1,112 +0,0 @@ -const { default: installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } = require('electron-devtools-installer'); -const { app, ipcMain, nativeImage, BrowserWindow, Menu, Tray } = require('electron'); -const platform = require('electron-platform'); -const path = require('path'); -const url = require('url'); -const mpris = require('./mpris'); -var Player; - -// GNU/Linux-specific -if (!platform.isDarwin && !platform.isWin32) { - Player = require('mpris-service'); -} - -let win; -let player; -let tray; -let icon = nativeImage.createFromPath(path.resolve(__dirname, 'resources', 'media', 'icon.png')); - -function createWindow() { - win = new BrowserWindow({ - width: 1366, - height: 768, - frame: false, - icon: icon, - webPreferences: { - experimentalFeatures: true - } - }); - - installExtension(REACT_DEVELOPER_TOOLS) - .then((name) => console.log(`Added Extension: ${name}`)) - .catch((err) => console.log('An error occurred: ', err)); - - installExtension(REDUX_DEVTOOLS) - .then((name) => console.log(`Added Extension: ${name}`)) - .catch((err) => console.log('An error occurred: ', err)); - - win.loadURL(url.format({ - pathname: path.join(__dirname, 'index.html'), - protocol: 'file:', - slashes: true - })); - - win.webContents.openDevTools(); - - win.on('closed', () => { - win = null; - }); - - - // MacOS specific - if (platform.isDarwin) { - app.dock.setIcon(icon); - icon = nativeImage.createFromPath(path.resolve(__dirname, 'resources', 'media', 'icon_apple.png')); - } - - const trayMenu = Menu.buildFromTemplate([ - {label: 'Quit', type: 'normal', click: - (menuItem, browserWindow, event) => { - app.quit(); - } - } - ]); - - tray = new Tray(icon); - tray.setTitle('nuclear music player'); - tray.setToolTip('nuclear music player'); - tray.setContextMenu(trayMenu); - - // GNU/Linux-specific - if (!platform.isDarwin && !platform.isWin32) { - player = Player({ - name: 'nuclear', - identity: 'nuclear music player', - supportedUriSchemes: ['file'], - supportedMimeTypes: ['audio/mpeg', 'application/ogg'], - supportedInterfaces: ['player'], - desktopEntry: 'nuclear' - }); - - player.on('quit', function () { - win = null; - }); - - player.on('next', mpris.onNext); - player.on('previous', mpris.onPrevious); - player.on('pause', mpris.onPause); - player.on('playpause', mpris.onPlayPause); - player.on('stop', mpris.onStop); - player.on('play', mpris.onPlay); - - ipcMain.on('songChange', (event, arg) => { - if (arg === null) { - return; - } - - player.metadata = { - 'mpris:trackid': player.objectPath('track/0'), - 'mpris:length': arg.streams[0].duration * 1000 * 1000, // In microseconds - 'mpris:artUrl': '',// arg.thumbnail, - 'xesam:title': arg.name, - 'xesam:artist': arg.artist - }; - }); - } -} - -app.on('ready', createWindow); - -app.on('window-all-closed', () => { - app.quit(); -}); diff --git a/main.prod.js b/main.prod.js deleted file mode 100644 index 2864ad95ee..0000000000 --- a/main.prod.js +++ /dev/null @@ -1,71 +0,0 @@ -const { app, nativeImage, BrowserWindow } = require('electron'); -const platform = require('electron-platform'); -const path = require('path'); -const url = require('url'); -const mpris = require('./mpris'); -var Player; - -// GNU/Linux-specific -if (!platform.isDarwin && !platform.isWin32) { - Player = require('mpris-service'); -} - -let win; -let player; -let icon = nativeImage.createFromPath(path.resolve(__dirname, 'resources', 'media', 'icon.png')); - -function createWindow() { - win = new BrowserWindow({ - width: 1366, - height: 768, - frame: false, - icon: icon, - webPreferences: { - experimentalFeatures: true - } - }); - - win.loadURL(url.format({ - pathname: path.join(__dirname, 'index.prod.html'), - protocol: 'file:', - slashes: true - })); - - win.on('closed', () => { - win = null; - }); - - // MacOS specific - if (platform.isDarwin) { - app.dock.setIcon(icon); - } - - // GNU/Linux-specific - if (!platform.isDarwin && !platform.isWin32) { - player = Player({ - name: 'nuclear', - identity: 'nuclear music player', - supportedUriSchemes: ['file'], - supportedMimeTypes: ['audio/mpeg', 'application/ogg'], - supportedInterfaces: ['player'], - desktopEntry: 'nuclear' - }); - - player.on('quit', function () { - win = null; - }); - - player.on('next', mpris.onNext); - player.on('previous', mpris.onPrevious); - player.on('pause', mpris.onPause); - player.on('playpause', mpris.onPlayPause); - player.on('stop', mpris.onStop); - player.on('play', mpris.onplay); - } -} - -app.on('ready', createWindow); - -app.on('window-all-closed', () => { - app.quit(); -}); diff --git a/mpris.js b/mpris.js deleted file mode 100644 index a4bb62c275..0000000000 --- a/mpris.js +++ /dev/null @@ -1,44 +0,0 @@ -const { ipcMain } = require('electron'); - -var rendererWindow = null; - -var events = ['raise', 'quit', 'next', 'previous', 'pause', 'playpause', 'stop', 'play', 'seek', 'position', 'open', 'volume']; - -ipcMain.on('started', (event, arg) => { - console.log('Renderer process started and registered.'); - rendererWindow = event.sender; -}); - - -function onNext() { - rendererWindow.send('next'); -} - -function onPrevious() { - rendererWindow.send('previous'); -} - -function onPause() { - rendererWindow.send('pause'); -} - -function onPlayPause() { - rendererWindow.send('playpause'); -} - -function onStop() { - rendererWindow.send('stop'); -} - -function onPlay() { - rendererWindow.send('play'); -} - -module.exports = { - onNext, - onPrevious, - onPause, - onPlayPause, - onStop, - onPlay -} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 25b64b7438..0000000000 --- a/package-lock.json +++ /dev/null @@ -1,11419 +0,0 @@ -{ - "name": "nuclear", - "version": "0.4.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "7zip": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/7zip/-/7zip-0.0.6.tgz", - "integrity": "sha1-nK+xca+CMpSQNTtIFvAzR6oVCjA=", - "dev": true - }, - "7zip-bin": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-2.4.1.tgz", - "integrity": "sha512-QU3oR1dLLVrYGRkb7LU17jMCpIkWtXXW7q71ECXWXkR9vOv37VjykqpvFgs29HgSCNLZHnNKJzdG6RwAW0LwIA==", - "dev": true, - "requires": { - "7zip-bin-linux": "1.3.1" - } - }, - "7zip-bin-linux": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/7zip-bin-linux/-/7zip-bin-linux-1.3.1.tgz", - "integrity": "sha512-Wv1uEEeHbTiS1+ycpwUxYNuIcyohU6Y6vEqY3NquBkeqy0YhVdsNUGsj0XKSRciHR6LoJSEUuqYUexmws3zH7Q==", - "dev": true, - "optional": true - }, - "@types/node": { - "version": "7.0.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.52.tgz", - "integrity": "sha512-jjpyQsKGsOF/wUElNjfPULk+d8PKvJOIXk3IUeBYYmNCy5dMWfrI+JiixYNw8ppKOlcRwWTXFl0B+i5oGrf95Q==" - }, - "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 - }, - "accepts": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", - "dev": true, - "requires": { - "mime-types": "2.1.17", - "negotiator": "0.6.1" - } - }, - "acorn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", - "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", - "dev": true - }, - "acorn-dynamic-import": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", - "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", - "dev": true, - "requires": { - "acorn": "4.0.13" - }, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - } - } - }, - "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.1" - } - }, - "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 - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" - } - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "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-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", - "dev": true, - "requires": { - "string-width": "2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "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 - }, - "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" - } - }, - "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" - } - } - } - }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true - }, - "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 - }, - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "dev": true, - "requires": { - "micromatch": "2.3.11", - "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.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", - "dev": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.3" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "dev": true, - "requires": { - "sprintf-js": "1.0.3" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } - }, - "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-filter": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", - "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-flatten": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", - "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=", - "dev": true - }, - "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", - "dev": true, - "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.10.0" - } - }, - "array-map": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", - "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", - "dev": true - }, - "array-reduce": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", - "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", - "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.3" - } - }, - "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.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "asar-integrity": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asar-integrity/-/asar-integrity-0.2.4.tgz", - "integrity": "sha512-6UDOmyl4RUo8i/0Sem/UKFJ70XZrXLCDQcILTbjTjAKZrSA3JbXVnWRFi2ZFEbeZxQ2LVCc3CWHnDlqj2AyVXg==", - "dev": true, - "requires": { - "bluebird-lst": "1.0.5", - "fs-extra-p": "4.5.0" - } - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true - }, - "asn1.js": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", - "integrity": "sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "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" - } - }, - "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": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "dev": true, - "requires": { - "lodash": "4.17.4" - } - }, - "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-exit-hook": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", - "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", - "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 - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz", - "integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=", - "dev": true - }, - "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000793", - "normalize-range": "0.1.2", - "num2fraction": "1.2.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000793", - "electron-to-chromium": "1.3.30" - } - } - } - }, - "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.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", - "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" - } - }, - "babel-core": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", - "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-generator": "6.26.0", - "babel-helpers": "6.24.1", - "babel-messages": "6.23.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "convert-source-map": "1.5.1", - "debug": "2.6.9", - "json5": "0.5.1", - "lodash": "4.17.4", - "minimatch": "3.0.4", - "path-is-absolute": "1.0.1", - "private": "0.1.8", - "slash": "1.0.0", - "source-map": "0.5.7" - }, - "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" - } - } - } - }, - "babel-generator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz", - "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=", - "dev": true, - "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.4", - "source-map": "0.5.7", - "trim-right": "1.0.1" - } - }, - "babel-helper-builder-binary-assignment-operator-visitor": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", - "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", - "dev": true, - "requires": { - "babel-helper-explode-assignable-expression": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-builder-react-jsx": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", - "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "esutils": "2.0.2" - } - }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.4" - } - }, - "babel-helper-explode-assignable-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", - "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "dev": true, - "requires": { - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-regex": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", - "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.4" - } - }, - "babel-helper-remap-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", - "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true, - "requires": { - "babel-helper-optimise-call-expression": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.2.tgz", - "integrity": "sha512-jRwlFbINAeyDStqK6Dd5YuY0k5YuzQUvlz2ZamuXrXmxav3pNqe9vfJ402+2G+OmlJSXxCOpB6Uz0INM7RQe2A==", - "dev": true, - "requires": { - "find-cache-dir": "1.0.0", - "loader-utils": "1.1.0", - "mkdirp": "0.5.1" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", - "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", - "dev": true - }, - "babel-plugin-syntax-exponentiation-operator": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", - "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", - "dev": true - }, - "babel-plugin-syntax-flow": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", - "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", - "dev": true - }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "dev": true - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", - "dev": true - }, - "babel-plugin-transform-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", - "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "6.24.1", - "babel-plugin-syntax-async-functions": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.4" - } - }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "dev": true, - "requires": { - "babel-helper-define-map": "6.26.0", - "babel-helper-function-name": "6.24.1", - "babel-helper-optimise-call-expression": "6.24.1", - "babel-helper-replace-supers": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", - "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", - "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", - "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", - "dev": true, - "requires": { - "babel-plugin-transform-strict-mode": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", - "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", - "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", - "dev": true, - "requires": { - "babel-helper-replace-supers": "6.24.1", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", - "dev": true, - "requires": { - "babel-helper-call-delegate": "6.24.1", - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", - "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", - "dev": true, - "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", - "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", - "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", - "dev": true, - "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "regexpu-core": "2.0.0" - } - }, - "babel-plugin-transform-exponentiation-operator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", - "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", - "dev": true, - "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", - "babel-plugin-syntax-exponentiation-operator": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-flow-strip-types": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", - "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", - "dev": true, - "requires": { - "babel-plugin-syntax-flow": "6.18.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-react-display-name": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", - "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-react-jsx": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", - "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", - "dev": true, - "requires": { - "babel-helper-builder-react-jsx": "6.26.0", - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-react-jsx-self": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", - "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", - "dev": true, - "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-react-jsx-source": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", - "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", - "dev": true, - "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-regenerator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", - "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", - "dev": true, - "requires": { - "regenerator-transform": "0.10.1" - } - }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-preset-env": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", - "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-to-generator": "6.24.1", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-exponentiation-operator": "6.24.1", - "babel-plugin-transform-regenerator": "6.26.0", - "browserslist": "2.11.0", - "invariant": "2.2.2", - "semver": "5.4.1" - } - }, - "babel-preset-es2015": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", - "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-regenerator": "6.26.0" - } - }, - "babel-preset-flow": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", - "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", - "dev": true, - "requires": { - "babel-plugin-transform-flow-strip-types": "6.22.0" - } - }, - "babel-preset-react": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", - "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", - "dev": true, - "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-plugin-transform-react-display-name": "6.25.0", - "babel-plugin-transform-react-jsx": "6.24.1", - "babel-plugin-transform-react-jsx-self": "6.22.0", - "babel-plugin-transform-react-jsx-source": "6.22.0", - "babel-preset-flow": "6.23.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "6.26.0", - "babel-runtime": "6.26.0", - "core-js": "2.5.3", - "home-or-tmp": "2.0.0", - "lodash": "4.17.4", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" - }, - "dependencies": { - "core-js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", - "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=", - "dev": true - } - } - }, - "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.5.3", - "regenerator-runtime": "0.11.1" - }, - "dependencies": { - "core-js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", - "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" - } - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.2", - "lodash": "4.17.4" - }, - "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" - } - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.4", - "to-fast-properties": "1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "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.6", - "component-emitter": "1.2.1", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.0", - "pascalcase": "0.1.1" - } - }, - "base64-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", - "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=", - "dev": true - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "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.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", - "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.3" - } - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true - }, - "bluebird-lst": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.5.tgz", - "integrity": "sha512-Ey0bDNys5qpYPhZ/oQ9vOEvD0TYQDTILMXWP2iGfvMg7rSDde+oV4aQQgqRH+CvBFNz2BSDQnPGMUl6LKBUUQA==", - "dev": true, - "requires": { - "bluebird": "3.5.1" - } - }, - "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-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-type": "1.0.4", - "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.15" - }, - "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" - } - } - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "requires": { - "array-flatten": "2.1.1", - "deep-equal": "1.0.1", - "dns-equal": "1.0.0", - "dns-txt": "2.0.2", - "multicast-dns": "6.2.2", - "multicast-dns-service-types": "1.1.0" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "dev": true, - "requires": { - "hoek": "4.2.0" - } - }, - "boxen": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", - "dev": true, - "requires": { - "ansi-align": "2.0.0", - "camelcase": "4.1.0", - "chalk": "2.3.0", - "cli-boxes": "1.0.0", - "string-width": "2.1.1", - "term-size": "1.2.0", - "widest-line": "2.0.0" - }, - "dependencies": { - "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.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "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 - }, - "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" - } - }, - "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" - } - }, - "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" - } - } - } - }, - "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" - } - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.1.tgz", - "integrity": "sha512-UGnTYAnB2a3YuYKIRy1/4FB2HdM866E0qC46JXvVTYKlBlZlnvfpSfY6OKfXZAkv70eJ2a1SqzpAo5CRhZGDFg==", - "dev": true, - "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "browserify-cipher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", - "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", - "dev": true, - "requires": { - "browserify-aes": "1.1.1", - "browserify-des": "1.0.0", - "evp_bytestokey": "1.0.3" - } - }, - "browserify-des": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", - "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3" - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "randombytes": "2.0.6" - } - }, - "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.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "elliptic": "6.4.0", - "inherits": "2.0.3", - "parse-asn1": "5.1.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.6" - } - }, - "browserslist": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.0.tgz", - "integrity": "sha512-mNYp0RNeu1xueGuJFSXkU+K0nH+dBE/gcjtyhtNKfU8hwdrVIfoA7i5iFSjOmzkGdL2QaO7YX9ExiVPE7AY9JA==", - "dev": true, - "requires": { - "caniuse-lite": "1.0.30000789", - "electron-to-chromium": "1.3.30" - } - }, - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, - "requires": { - "base64-js": "1.2.0", - "ieee754": "1.1.8", - "isarray": "1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true - }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "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 - }, - "builder-util": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-4.1.7.tgz", - "integrity": "sha512-jObkpxMLUAG6iaTfXTWhx1iRY5e/4wFCJ2s1g9kIpWkBQZA41BTpRuzL1JGzl64wBNZIFJZG5rP2TQkAFl1AAA==", - "dev": true, - "requires": { - "7zip-bin": "2.4.1", - "bluebird-lst": "1.0.5", - "builder-util-runtime": "4.0.2", - "chalk": "2.3.0", - "debug": "3.1.0", - "fs-extra-p": "4.5.0", - "ini": "1.3.5", - "is-ci": "1.1.0", - "js-yaml": "3.10.0", - "lazy-val": "1.0.3", - "semver": "5.5.0", - "source-map-support": "0.5.1", - "stat-mode": "0.2.2", - "temp-file": "3.1.1", - "tunnel-agent": "0.6.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" - } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - }, - "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-support": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.1.tgz", - "integrity": "sha512-EZNecLNrsdRk9fcdOcjjy+Z/id7cr68sdmsYtR1gA45oQ81Ccea0UvM7DdSRblO0Ie5zWX31bvJTC7Y3QZVujg==", - "dev": true, - "requires": { - "source-map": "0.6.1" - } - }, - "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" - } - } - } - }, - "builder-util-runtime": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-4.0.2.tgz", - "integrity": "sha512-ccVDjvLIxXQHRczxX6ea68rCjkI1PQLjR7E3o48kW0t/OQl+Uwu89BXpJIhc0ea6FihkyM72litniJBoZ3UfKQ==", - "dev": true, - "requires": { - "bluebird-lst": "1.0.5", - "debug": "3.1.0", - "fs-extra-p": "4.5.0", - "sax": "1.2.4" - } - }, - "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": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "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" - } - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" - } - }, - "caniuse-api": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", - "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000793", - "lodash.memoize": "4.1.2", - "lodash.uniq": "4.5.0" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000793", - "electron-to-chromium": "1.3.30" - } - } - } - }, - "caniuse-db": { - "version": "1.0.30000793", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000793.tgz", - "integrity": "sha1-PADGbkI6ehkHx92Wdpp4sq+opy4=", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30000789", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000789.tgz", - "integrity": "sha1-Lj2TeyZxM/Y2Ne9/RB+sZjYPyIk=", - "dev": true - }, - "capture-stack-trace": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" - }, - "dependencies": { - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "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.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" - }, - "cheerio": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", - "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", - "requires": { - "css-select": "1.2.0", - "dom-serializer": "0.1.0", - "entities": "1.1.1", - "htmlparser2": "3.9.2", - "lodash": "4.17.4", - "parse5": "3.0.3" - } - }, - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "dev": true, - "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" - } - }, - "chromium-pickle-js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", - "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=", - "dev": true - }, - "ci-info": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.2.tgz", - "integrity": "sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA==", - "dev": true - }, - "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.3", - "safe-buffer": "5.1.1" - } - }, - "clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "requires": { - "chalk": "1.1.3" - } - }, - "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.1", - "static-extend": "0.1.2" - }, - "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.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "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.1.0" - } - }, - "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 - } - } - }, - "classnames": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz", - "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=" - }, - "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", - "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.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" - } - }, - "clone": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", - "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", - "dev": true - }, - "clone-deep": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.3.0.tgz", - "integrity": "sha1-NIxhrpzb4O3+BT2R/0zFIdeQ7eg=", - "dev": true, - "requires": { - "for-own": "1.0.0", - "is-plain-object": "2.0.4", - "kind-of": "3.2.2", - "shallow-clone": "0.1.2" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "coa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", - "dev": true, - "requires": { - "q": "1.5.1" - } - }, - "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.1" - } - }, - "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", - "dev": true, - "requires": { - "clone": "1.0.3", - "color-convert": "1.9.1", - "color-string": "0.3.0" - } - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, - "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=", - "dev": true - }, - "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "colormin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", - "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", - "dev": true, - "requires": { - "color": "0.11.4", - "css-color-names": "0.0.4", - "has": "1.0.1" - } - }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "commander": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "compare-version": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", - "integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=", - "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 - }, - "compressible": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.12.tgz", - "integrity": "sha1-xZpcmdt2dn6YdlAOJx72OzSTvWY=", - "dev": true, - "requires": { - "mime-db": "1.30.0" - } - }, - "compression": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.1.tgz", - "integrity": "sha1-7/JgPvwuIs+G810uuTWJ+YdTc9s=", - "dev": true, - "requires": { - "accepts": "1.3.4", - "bytes": "3.0.0", - "compressible": "2.0.12", - "debug": "2.6.9", - "on-headers": "1.0.1", - "safe-buffer": "5.1.1", - "vary": "1.1.2" - }, - "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" - } - } - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "typedarray": "0.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "configstore": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.1.tgz", - "integrity": "sha512-5oNkD/L++l0O6xGXxb1EWS7SivtjfGQlRyxJsYgE0Z495/L81e2h4/d3r969hoPXuFItzNOKMtsXgYG4c7dYvw==", - "dev": true, - "requires": { - "dot-prop": "4.2.0", - "graceful-fs": "4.1.11", - "make-dir": "1.1.0", - "unique-string": "1.0.0", - "write-file-atomic": "2.3.0", - "xdg-basedir": "3.0.0" - } - }, - "connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", - "dev": true - }, - "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 - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - }, - "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=" - }, - "create-ecdh": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", - "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.0" - } - }, - "create-error-class": { - "version": "3.0.2", - "resolved": "http://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dev": true, - "requires": { - "capture-stack-trace": "1.0.0" - } - }, - "create-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", - "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "sha.js": "2.4.9" - } - }, - "create-hmac": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", - "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.9" - } - }, - "create-react-class": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz", - "integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=", - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1" - } - }, - "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.1.1", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, - "cross-unzip": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/cross-unzip/-/cross-unzip-0.0.2.tgz", - "integrity": "sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8=", - "dev": true - }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" - }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "dev": true, - "requires": { - "boom": "5.2.0" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "dev": true, - "requires": { - "hoek": "4.2.0" - } - } - } - }, - "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.4", - "create-ecdh": "4.0.0", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "diffie-hellman": "5.0.2", - "inherits": "2.0.3", - "pbkdf2": "3.0.14", - "public-encrypt": "4.0.0", - "randombytes": "2.0.6", - "randomfill": "1.0.3" - } - }, - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", - "dev": true - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true - }, - "css-loader": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.9.tgz", - "integrity": "sha512-r3dgelMm/mkPz5Y7m9SeiGE46i2VsEU/OYbez+1llfxtv8b2y5/b5StaeEvPK3S5tlNQI+tDW/xDIhKJoZgDtw==", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "css-selector-tokenizer": "0.7.0", - "cssnano": "3.10.0", - "icss-utils": "2.1.0", - "loader-utils": "1.1.0", - "lodash.camelcase": "4.3.0", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-modules-extract-imports": "1.2.0", - "postcss-modules-local-by-default": "1.2.0", - "postcss-modules-scope": "1.1.0", - "postcss-modules-values": "1.3.0", - "postcss-value-parser": "3.3.0", - "source-list-map": "2.0.0" - } - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "requires": { - "boolbase": "1.0.0", - "css-what": "2.1.0", - "domutils": "1.5.1", - "nth-check": "1.0.1" - } - }, - "css-selector-tokenizer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", - "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", - "dev": true, - "requires": { - "cssesc": "0.1.0", - "fastparse": "1.1.1", - "regexpu-core": "1.0.0" - }, - "dependencies": { - "regexpu-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", - "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", - "dev": true, - "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" - } - } - } - }, - "css-what": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" - }, - "cssesc": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", - "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", - "dev": true - }, - "cssnano": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", - "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", - "dev": true, - "requires": { - "autoprefixer": "6.7.7", - "decamelize": "1.2.0", - "defined": "1.0.0", - "has": "1.0.1", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-calc": "5.3.1", - "postcss-colormin": "2.2.2", - "postcss-convert-values": "2.6.1", - "postcss-discard-comments": "2.0.4", - "postcss-discard-duplicates": "2.1.0", - "postcss-discard-empty": "2.1.0", - "postcss-discard-overridden": "0.1.1", - "postcss-discard-unused": "2.2.3", - "postcss-filter-plugins": "2.0.2", - "postcss-merge-idents": "2.1.7", - "postcss-merge-longhand": "2.0.2", - "postcss-merge-rules": "2.1.2", - "postcss-minify-font-values": "1.0.5", - "postcss-minify-gradients": "1.0.5", - "postcss-minify-params": "1.2.2", - "postcss-minify-selectors": "2.1.1", - "postcss-normalize-charset": "1.1.1", - "postcss-normalize-url": "3.0.8", - "postcss-ordered-values": "2.2.3", - "postcss-reduce-idents": "2.4.0", - "postcss-reduce-initial": "1.0.1", - "postcss-reduce-transforms": "1.0.4", - "postcss-svgo": "2.1.6", - "postcss-unique-selectors": "2.0.2", - "postcss-value-parser": "3.3.0", - "postcss-zindex": "2.2.0" - } - }, - "csso": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", - "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", - "dev": true, - "requires": { - "clap": "1.2.3", - "source-map": "0.5.7" - } - }, - "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.2" - } - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "0.10.37" - } - }, - "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" - } - }, - "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 - }, - "dbus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/dbus/-/dbus-1.0.2.tgz", - "integrity": "sha1-pgYea5uI9w6z/j9qNiSeQtVXnZo=", - "optional": true, - "requires": { - "nan": "2.8.0" - } - }, - "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-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true - }, - "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", - "dev": true - }, - "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "dev": true, - "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.11" - }, - "dependencies": { - "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", - "dev": true - } - } - }, - "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.2" - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", - "dev": true, - "requires": { - "globby": "6.1.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.0", - "p-map": "1.2.0", - "pify": "3.0.0", - "rimraf": "2.6.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 - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "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.3", - "minimalistic-assert": "1.0.0" - } - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, - "detect-node": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", - "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", - "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.1", - "randombytes": "2.0.6" - } - }, - "dmg-builder": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-3.1.1.tgz", - "integrity": "sha512-NdZS3ErgoGwnxzF74nemZlQaYqZZTrErLxspB+nQNU8iwwQLl6Xclkb0y5w0BLcmCwcd9bToxX3ghvolrqx3OQ==", - "dev": true, - "requires": { - "bluebird-lst": "1.0.5", - "builder-util": "4.1.7", - "fs-extra-p": "4.5.0", - "iconv-lite": "0.4.19", - "js-yaml": "3.10.0", - "parse-color": "1.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" - } - } - } - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, - "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "dev": true, - "requires": { - "ip": "1.1.5", - "safe-buffer": "5.1.1" - } - }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "requires": { - "buffer-indexof": "1.1.1" - } - }, - "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "requires": { - "domelementtype": "1.1.3", - "entities": "1.1.1" - }, - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" - } - } - }, - "dom-walk": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", - "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", - "dev": true - }, - "domain-browser": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", - "dev": true - }, - "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" - }, - "domhandler": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", - "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", - "requires": { - "domelementtype": "1.3.0" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" - } - }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, - "requires": { - "is-obj": "1.0.1" - } - }, - "dotenv": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", - "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=", - "dev": true - }, - "dotenv-expand": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-4.0.1.tgz", - "integrity": "sha1-aP3cFWGBTgoQlkERBX/xOM7X16g=", - "dev": true - }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", - "dev": true - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "ejs": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", - "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=", - "dev": true - }, - "electron": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/electron/-/electron-1.7.10.tgz", - "integrity": "sha1-Oj6D2WX9f6/kc76N349HJWG2JT0=", - "dev": true, - "requires": { - "@types/node": "7.0.52", - "electron-download": "3.3.0", - "extract-zip": "1.6.6" - } - }, - "electron-builder": { - "version": "19.54.0", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-19.54.0.tgz", - "integrity": "sha512-tsSz9s8lFysasNK2wwKEP7ILthz++RVDAQ+D7LxtzOfQbekf91AXfYmjLIhxcgj9wavTAuHJ7QU24G4KmISn3Q==", - "dev": true, - "requires": { - "bluebird-lst": "1.0.5", - "builder-util": "4.1.7", - "builder-util-runtime": "4.0.2", - "chalk": "2.3.0", - "electron-builder-lib": "19.54.0", - "electron-download-tf": "4.3.4", - "fs-extra-p": "4.5.0", - "is-ci": "1.1.0", - "lazy-val": "1.0.3", - "read-config-file": "2.1.1", - "sanitize-filename": "1.6.1", - "update-notifier": "2.3.0", - "yargs": "10.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "electron-download-tf": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/electron-download-tf/-/electron-download-tf-4.3.4.tgz", - "integrity": "sha512-SQYDGMLpTgty1bx3NycuDb7dNPzktVSdK2sqPZjyRocauq/uN/V4S2lcpFVLupaHhKlD8zozm9fTpm5UdohvTg==", - "dev": true, - "requires": { - "debug": "3.1.0", - "env-paths": "1.0.0", - "fs-extra": "4.0.3", - "minimist": "1.2.0", - "nugget": "2.0.1", - "path-exists": "3.0.0", - "rc": "1.2.2", - "semver": "5.4.1", - "sumchecker": "2.0.2" - } - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "sumchecker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", - "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", - "dev": true, - "requires": { - "debug": "2.6.9" - }, - "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" - } - } - } - }, - "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" - } - } - } - }, - "electron-builder-lib": { - "version": "19.54.0", - "resolved": "https://registry.npmjs.org/electron-builder-lib/-/electron-builder-lib-19.54.0.tgz", - "integrity": "sha512-lu5uPJEcpqmFbXUSDpdT4WP6Njl3Ohxw2xfGaKr/TJ+sasB44Xo3yNanpfg6Up0oN5SXq4fQYXUL/Y6cn8d9ww==", - "dev": true, - "requires": { - "7zip-bin": "2.4.1", - "asar-integrity": "0.2.4", - "async-exit-hook": "2.0.1", - "bluebird-lst": "1.0.5", - "builder-util": "4.1.7", - "builder-util-runtime": "4.0.2", - "chromium-pickle-js": "0.2.0", - "debug": "3.1.0", - "dmg-builder": "3.1.1", - "ejs": "2.5.7", - "electron-osx-sign": "0.4.8", - "electron-publish": "19.54.0", - "fs-extra-p": "4.5.0", - "hosted-git-info": "2.5.0", - "is-ci": "1.1.0", - "isbinaryfile": "3.0.2", - "js-yaml": "3.10.0", - "lazy-val": "1.0.3", - "minimatch": "3.0.4", - "normalize-package-data": "2.4.0", - "plist": "2.1.0", - "read-config-file": "2.1.1", - "sanitize-filename": "1.6.1", - "semver": "5.5.0", - "temp-file": "3.1.1" - }, - "dependencies": { - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" - } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - } - } - }, - "electron-devtools-installer": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/electron-devtools-installer/-/electron-devtools-installer-2.2.3.tgz", - "integrity": "sha512-KFVP2lt3guvhXsUKE3YxbddMOJtpdvTsWfloV/8395Df5Td9Z+YvNl8LFW864mVqdDJsiy2qQ8y95NT5C+avSA==", - "dev": true, - "requires": { - "7zip": "0.0.6", - "cross-unzip": "0.0.2", - "rimraf": "2.6.2", - "semver": "5.4.1" - } - }, - "electron-download": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-3.3.0.tgz", - "integrity": "sha1-LP1U1pZsAZxNSa1l++Zcyc3vaMg=", - "dev": true, - "requires": { - "debug": "2.6.9", - "fs-extra": "0.30.0", - "home-path": "1.0.5", - "minimist": "1.2.0", - "nugget": "2.0.1", - "path-exists": "2.1.0", - "rc": "1.2.2", - "semver": "5.4.1", - "sumchecker": "1.3.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" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "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.1" - } - } - } - }, - "electron-osx-sign": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.8.tgz", - "integrity": "sha1-8Ln63e2eHlTsNfqJh3tcbDTHvEA=", - "dev": true, - "requires": { - "bluebird": "3.5.1", - "compare-version": "0.1.2", - "debug": "2.6.9", - "isbinaryfile": "3.0.2", - "minimist": "1.2.0", - "plist": "2.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" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "electron-platform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/electron-platform/-/electron-platform-1.2.0.tgz", - "integrity": "sha1-L8cLzlP7cy7IF2m9bEjhDEClUao=" - }, - "electron-publish": { - "version": "19.54.0", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-19.54.0.tgz", - "integrity": "sha512-xCckDDlbcyfGVPxqenARVo+2ms+BogUsMb923IQyOJgMAk5/tdcGyYBJfbIWqsQETnfKGnk0uCdJ/mLI33wmNA==", - "dev": true, - "requires": { - "bluebird-lst": "1.0.5", - "builder-util": "4.1.7", - "builder-util-runtime": "4.0.2", - "chalk": "2.3.0", - "fs-extra-p": "4.5.0", - "mime": "2.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "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" - } - } - } - }, - "electron-releases": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/electron-releases/-/electron-releases-2.1.0.tgz", - "integrity": "sha512-cyKFD1bTE/UgULXfaueIN1k5EPFzs+FRc/rvCY5tIynefAPqopQEgjr0EzY+U3Dqrk/G4m9tXSPuZ77v6dL/Rw==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.30", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.30.tgz", - "integrity": "sha512-zx1Prv7kYLfc4OA60FhxGbSo4qrEjgSzpo1/37i7l9ltXPYOoQBtjQxY9KmsgfHnBxHlBGXwLlsbt/gub1w5lw==", - "dev": true, - "requires": { - "electron-releases": "2.1.0" - } - }, - "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.3", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "encodeurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", - "dev": true - }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "0.4.19" - } - }, - "enhanced-resolve": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", - "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "memory-fs": "0.4.1", - "object-assign": "4.1.1", - "tapable": "0.2.8" - } - }, - "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" - }, - "env-paths": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", - "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", - "dev": true - }, - "errno": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.6.tgz", - "integrity": "sha512-IsORQDpaaSwcDP4ZZnHxgE85werpo34VYn1Ud3mq+eUsF593faR8oCZNXrROVkpFu2TsbrNhHin0aUrTsQ9vNw==", - "dev": true, - "requires": { - "prr": "1.0.1" - } - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, - "error-stack-parser": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-1.3.6.tgz", - "integrity": "sha1-4Oc7k+QXE40c18C3RrGkoUhUwpI=", - "dev": true, - "requires": { - "stackframe": "0.3.1" - } - }, - "es-abstract": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz", - "integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==", - "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.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "dev": true, - "requires": { - "is-callable": "1.1.3", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" - } - }, - "es5-ext": { - "version": "0.10.37", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", - "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=", - "dev": true, - "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37", - "es6-symbol": "3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37", - "es6-iterator": "2.0.3", - "es6-set": "0.1.5", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-promise": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.2.tgz", - "integrity": "sha512-LSas5vsuA6Q4nEdf9wokY5/AJYXry98i0IzXsv49rYsgDGDNDPbqAYR1Pe23iFxygfbGZNR/5VrHXBCh2BhvUQ==", - "dev": true - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "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 - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "0.1.5", - "es6-weak-map": "2.0.2", - "esrecurse": "4.2.0", - "estraverse": "4.2.0" - } - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", - "dev": true, - "requires": { - "estraverse": "4.2.0", - "object-assign": "4.1.1" - } - }, - "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 - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37" - } - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "dev": true, - "requires": { - "duplexer": "0.1.1", - "from": "0.1.7", - "map-stream": "0.1.0", - "pause-stream": "0.0.11", - "split": "0.3.3", - "stream-combiner": "0.0.4", - "through": "2.3.8" - } - }, - "eventemitter3": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", - "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", - "dev": true - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", - "dev": true - }, - "eventsource": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", - "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", - "dev": true, - "requires": { - "original": "1.0.0" - } - }, - "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.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "2.2.3" - } - }, - "express": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", - "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", - "dev": true, - "requires": { - "accepts": "1.3.4", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.1", - "finalhandler": "1.1.0", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.2", - "qs": "6.5.1", - "range-parser": "1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.1", - "serve-static": "1.13.1", - "setprototypeof": "1.1.0", - "statuses": "1.3.1", - "type-is": "1.6.15", - "utils-merge": "1.0.1", - "vary": "1.1.2" - }, - "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "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" - } - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - } - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true - }, - "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.1" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "extract-text-webpack-plugin": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz", - "integrity": "sha512-bt/LZ4m5Rqt/Crl2HiKuAl/oqg0psx1tsTLkvWbJen1CtD+fftkZhMaQ9HOtY2gWsl2Wq+sABmMVi9z3DhKWQQ==", - "dev": true, - "requires": { - "async": "2.6.0", - "loader-utils": "1.1.0", - "schema-utils": "0.3.0", - "webpack-sources": "1.1.0" - } - }, - "extract-zip": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.6.tgz", - "integrity": "sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=", - "dev": true, - "requires": { - "concat-stream": "1.6.0", - "debug": "2.6.9", - "mkdirp": "0.5.0", - "yauzl": "2.4.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" - } - }, - "mkdirp": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", - "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - } - } - }, - "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": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", - "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.1", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", - "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", - "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.7.0" - } - }, - "fbjs": { - "version": "0.8.16", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", - "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", - "requires": { - "core-js": "1.2.7", - "isomorphic-fetch": "2.2.1", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "promise": "7.3.1", - "setimmediate": "1.0.5", - "ua-parser-js": "0.7.17" - } - }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "dev": true, - "requires": { - "pend": "1.2.0" - } - }, - "file-loader": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.6.tgz", - "integrity": "sha512-873ztuL+/hfvXbLDJ262PGO6XjERnybJu2gW1/5j8HUfxSiFJI9Hj/DhZ50ZGRUxBvuNiazb/cM2rh9pqrxP6Q==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.3.0" - } - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true, - "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "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" - } - } - } - }, - "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.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" - } - } - } - }, - "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.1.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" - } - }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", - "dev": true - }, - "flux-standard-action": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/flux-standard-action/-/flux-standard-action-0.6.1.tgz", - "integrity": "sha1-bzQhG5SDTqHDzDD056+tPQ+/caI=", - "dev": true, - "requires": { - "lodash.isplainobject": "3.2.0" - } - }, - "font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" - }, - "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.2" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "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.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", - "dev": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true - }, - "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" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, - "fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "2.4.0", - "klaw": "1.3.1", - "path-is-absolute": "1.0.1", - "rimraf": "2.6.2" - } - }, - "fs-extra-p": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.0.tgz", - "integrity": "sha512-V/sdZmV+Yx3+nfXmjRTdBP4mVWCt7hZ0+ZOv+IZo+6fdkBxafaGsI7mYeNv/J3rWyz+mIToCFQORFSwt1bZw8Q==", - "dev": true, - "requires": { - "bluebird-lst": "1.0.5", - "fs-extra": "5.0.0" - }, - "dependencies": { - "fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11" - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "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.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.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 - }, - "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.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "gaze": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", - "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=", - "dev": true, - "requires": { - "globule": "1.2.0" - } - }, - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", - "dev": true - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "1.0.2" - } - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "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": "https://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.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "2.0.1" - } - }, - "global": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", - "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", - "dev": true, - "requires": { - "min-document": "2.19.0", - "process": "0.5.2" - } - }, - "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", - "dev": true, - "requires": { - "ini": "1.3.5" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "globule": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", - "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", - "dev": true, - "requires": { - "glob": "7.1.2", - "lodash": "4.17.4", - "minimatch": "3.0.4" - } - }, - "google-fonts-webpack-plugin": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/google-fonts-webpack-plugin/-/google-fonts-webpack-plugin-0.4.4.tgz", - "integrity": "sha512-+e2D9/DVBG9EDydRovzoqMZ658SsTBGbC0c65GyZqkwNvdj8vRSYQKXqbz7/yt7QaXsCPT1MpH45r3ivWOitcw==", - "dev": true, - "requires": { - "lodash": "4.17.4", - "node-fetch": "1.7.3", - "webpack-sources": "0.2.3", - "yauzl": "2.9.1" - }, - "dependencies": { - "source-list-map": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-1.1.2.tgz", - "integrity": "sha1-mIkBnRAkzOVc3AaUmDN+9hhqEaE=", - "dev": true - }, - "webpack-sources": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.2.3.tgz", - "integrity": "sha1-F8Yr+vE8cH+dAsR54Nzd6DgGl/s=", - "dev": true, - "requires": { - "source-list-map": "1.1.2", - "source-map": "0.5.7" - } - }, - "yauzl": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz", - "integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=", - "dev": true, - "requires": { - "buffer-crc32": "0.2.13", - "fd-slicer": "1.0.1" - } - } - } - }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "dev": true, - "requires": { - "create-error-class": "3.0.2", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "is-redirect": "1.0.0", - "is-retry-allowed": "1.1.0", - "is-stream": "1.1.0", - "lowercase-keys": "1.0.0", - "safe-buffer": "5.1.1", - "timed-out": "4.0.1", - "unzip-response": "2.0.1", - "url-parse-lax": "1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "handle-thing": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", - "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=", - "dev": true - }, - "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.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "dev": true, - "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" - } - }, - "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", - "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.1.1" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "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.1" - } - }, - "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": { - "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.2.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.6" - } - } - } - }, - "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.6" - } - } - } - }, - "hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" - } - }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "dev": true, - "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.0", - "sntp": "2.1.0" - } - }, - "history": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", - "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", - "requires": { - "invariant": "2.2.2", - "loose-envify": "1.3.1", - "resolve-pathname": "2.2.0", - "value-equal": "0.4.0", - "warning": "3.0.0" - } - }, - "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.1.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", - "dev": true - }, - "hoist-non-react-statics": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz", - "integrity": "sha1-ND24TGAYxlB3iJgkATWhQg7iLOA=" - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "home-path": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.5.tgz", - "integrity": "sha1-eIspgVsS1Tus9XVkhHbm+QQdEz8=", - "dev": true - }, - "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", - "dev": true - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "obuf": "1.1.1", - "readable-stream": "2.3.3", - "wbuf": "1.7.2" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "html-comment-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", - "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", - "dev": true - }, - "html-entities": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" - }, - "htmlparser2": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", - "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", - "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.4.1", - "domutils": "1.5.1", - "entities": "1.1.1", - "inherits": "2.0.3", - "readable-stream": "2.3.3" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "dev": true, - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.3.1" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "dev": true - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", - "dev": true - } - } - }, - "http-parser-js": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.9.tgz", - "integrity": "sha1-6hoE+2St/wJC6ZdPKX3Uw8rSceE=", - "dev": true - }, - "http-proxy": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", - "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", - "dev": true, - "requires": { - "eventemitter3": "1.2.0", - "requires-port": "1.0.0" - } - }, - "http-proxy-middleware": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz", - "integrity": "sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=", - "dev": true, - "requires": { - "http-proxy": "1.16.2", - "is-glob": "3.1.0", - "lodash": "4.17.4", - "micromatch": "2.3.11" - }, - "dependencies": { - "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-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.1" - } - } - } - }, - "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.4.1", - "sshpk": "1.13.1" - } - }, - "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.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, - "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": "2.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", - "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", - "dev": true, - "requires": { - "postcss": "6.0.16" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - }, - "dependencies": { - "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" - } - } - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "postcss": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.16.tgz", - "integrity": "sha512-m758RWPmSjFH/2MyyG3UOW1fgYbR9rtdzz5UNJnlm7OLtu4B2h9C6gi+bE4qFKghsBRFfZT8NzoQBs6JhLotoA==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "5.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 - }, - "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "ieee754": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", - "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", - "dev": true - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "2.0.0", - "resolve-cwd": "2.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.1" - } - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "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.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "internal-ip": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", - "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", - "dev": true, - "requires": { - "meow": "3.7.0" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", - "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", - "requires": { - "loose-envify": "1.3.1" - } - }, - "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 - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ipaddr.js": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", - "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=", - "dev": true - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "dev": true - }, - "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.2" - }, - "dependencies": { - "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 - } - } - }, - "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.11.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==" - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "1.1.1" - } - }, - "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", - "dev": true - }, - "is-ci": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", - "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", - "dev": true, - "requires": { - "ci-info": "1.1.2" - } - }, - "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.2" - }, - "dependencies": { - "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 - } - } - }, - "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": "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" - }, - "dependencies": { - "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 - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "2.0.0" - } - }, - "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": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "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.1" - } - }, - "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.1" - } - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "is-installed-globally": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", - "dev": true, - "requires": { - "global-dirs": "0.1.1", - "is-path-inside": "1.0.1" - } - }, - "is-my-json-valid": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz", - "integrity": "sha512-Q2khNw+oBlWuaYvEEHtKSw/pCxD2L5Rc1C+UQme9X6JdRDh7m5D7HkozA0qa3DUkQ6VzCnEm8mVIQPyIRkI5sQ==", - "dev": true, - "requires": { - "generate-function": "2.0.0", - "generate-object-property": "1.2.0", - "jsonpointer": "4.0.1", - "xtend": "4.0.1" - }, - "dependencies": { - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - } - } - }, - "is-npm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", - "dev": true - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-odd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-1.0.0.tgz", - "integrity": "sha1-O4qTLrAos3dcObsJ6RdnrM22kIg=", - "dev": true, - "requires": { - "is-number": "3.0.0" - }, - "dependencies": { - "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.2.2" - } - } - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", - "dev": true, - "requires": { - "is-path-inside": "1.0.1" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "1.0.2" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "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-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "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-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-svg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", - "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", - "dev": true, - "requires": { - "html-comment-regex": "1.1.1" - } - }, - "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true - }, - "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-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "isbinaryfile": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", - "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", - "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 - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "1.7.3", - "whatwg-fetch": "2.0.3" - } - }, - "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.4.0", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.0.tgz", - "integrity": "sha512-Wehd+7Pf9tFvGb+ydPm9TjYjV8X1YHOVyG8QyELZxEMqOhemVwGRmoG8iQ/soqI3n8v4xn59zaLxiCJiaaRzKA==", - "dev": true - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" - }, - "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "2.7.3" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz", - "integrity": "sha512-xyQpxeWWMKyJps9CuGJYeng6ssI5bpqS9ltQpdVQ90t4ql6NdnxFKh95JcRt2cun/DjMVNrdjniLPuMA69xmCw==", - "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.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "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 - }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, - "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" - } - }, - "killable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz", - "integrity": "sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=", - "dev": true - }, - "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.6" - } - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11" - } - }, - "latest-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", - "dev": true, - "requires": { - "package-json": "4.0.1" - } - }, - "lazy-cache": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", - "dev": true - }, - "lazy-val": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz", - "integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==", - "dev": true - }, - "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" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "loader-runner": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", - "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", - "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.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1" - } - }, - "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.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" - }, - "lodash-es": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz", - "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=", - "dev": true - }, - "lodash._basefor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", - "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=", - "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.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", - "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.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.isplainobject": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz", - "integrity": "sha1-moI4rhayAEMpYM1zRlEtASP79MU=", - "dev": true, - "requires": { - "lodash._basefor": "3.0.3", - "lodash.isarguments": "3.1.0", - "lodash.keysin": "3.0.8" - } - }, - "lodash.keysin": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz", - "integrity": "sha1-IsRJPrvtsUJ5YqVLRFssinZ/tH8=", - "dev": true, - "requires": { - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" - } - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.mergewith": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz", - "integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU=", - "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 - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, - "loglevel": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", - "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", - "dev": true - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "requires": { - "js-tokens": "3.0.2" - } - }, - "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.2" - } - }, - "lowdb": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-1.0.0.tgz", - "integrity": "sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==", - "requires": { - "graceful-fs": "4.1.11", - "is-promise": "2.1.0", - "lodash": "4.17.4", - "pify": "3.0.0", - "steno": "0.4.4" - } - }, - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", - "dev": true - }, - "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "m3u8stream": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.2.1.tgz", - "integrity": "sha512-kESIvCcoDOZ2ozD6wGxB962E24nPLDTezIBdqfJH1HxoY/dMTRFXOfq7sXtqPQM3nQihKXlv6pYmUpf01S/tVQ==", - "requires": { - "miniget": "1.1.0" - } - }, - "macaddress": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz", - "integrity": "sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=", - "dev": true - }, - "make-dir": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz", - "integrity": "sha512-0Pkui4wLJ7rxvmfUvs87skoEaxmu0hCUApF8nonzpl7q//FWp9zu8W61Scz4sd/kUiqDxvUhtoam2efDyiBzcA==", - "dev": true, - "requires": { - "pify": "3.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-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "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.1" - } - }, - "math-expression-evaluator": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", - "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", - "dev": true - }, - "md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", - "requires": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "1.1.6" - } - }, - "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", - "dev": true, - "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" - }, - "dependencies": { - "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.3", - "safe-buffer": "5.1.1" - } - } - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "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.6", - "readable-stream": "2.3.3" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", - "dev": true - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.6.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.4.0", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" - } - }, - "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.11.8", - "brorand": "1.1.0" - } - }, - "mime": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.2.0.tgz", - "integrity": "sha512-0Qz9uF1ATtl8RKJG4VRfOymh7PyEor6NbrI/61lRfuRe4vx9SNATrvAeTj2EWVRKjEQGskrzWkJBBY5NbaVHIA==", - "dev": true - }, - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", - "dev": true - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "dev": true, - "requires": { - "mime-db": "1.30.0" - } - }, - "mimic-fn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", - "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", - "dev": true - }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "dev": true, - "requires": { - "dom-walk": "0.1.1" - } - }, - "miniget": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/miniget/-/miniget-1.1.0.tgz", - "integrity": "sha512-ICBPQWEoz19eyPHbXLkvjPi62xLhz2irltks35NCYqzYHO0/35IbAaBP3Bbc4VgAwmloIPEpK7CO4/omOiUfDg==" - }, - "minimalistic-assert": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", - "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", - "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.8" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mixin-deep": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.0.tgz", - "integrity": "sha512-dgaCvoh6i1nosAUBKb0l0pfJ78K8+S9fluyIR2YvAeUD/QuMahnFnF3xYty5eYXMjhGSsB0DsW6A0uAZyetoAg==", - "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.8", - "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": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mpris-service": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/mpris-service/-/mpris-service-1.1.3.tgz", - "integrity": "sha1-GUup8v3+Vsgg3YvbFMd8RUoDqlg=", - "optional": true, - "requires": { - "dbus": "0.2.23" - }, - "dependencies": { - "dbus": { - "version": "0.2.23", - "resolved": "https://registry.npmjs.org/dbus/-/dbus-0.2.23.tgz", - "integrity": "sha1-E3ZHeco8CB51CG2CFjb8IVvZvAE=", - "optional": true, - "requires": { - "nan": "2.8.0" - } - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "multicast-dns": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.2.tgz", - "integrity": "sha512-xTO41ApiRHMVDBYhNL9bEhx7kRf1hq3OqPOnOy8bpTi0JZSxVPDre7ZRpTHLDlxmhf6d/FL+10E8VX1QRd+0DA==", - "dev": true, - "requires": { - "dns-packet": "1.3.1", - "thunky": "0.1.0" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "nan": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" - }, - "nanomatch": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.7.tgz", - "integrity": "sha512-/5ldsnyurvEw7wNpxLFgjVvBLMta43niEYOy0CJ4ntcYSbx6bugRUTQeFb4BR/WanEL1o3aQgHuVLHQaB6tOqg==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "define-property": "1.0.0", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "is-odd": "1.0.0", - "kind-of": "5.1.0", - "object.pick": "1.3.0", - "regex-not": "1.0.0", - "snapdragon": "0.8.1", - "to-regex": "3.0.1" - }, - "dependencies": { - "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 - }, - "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 - }, - "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 - } - } - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" - } - }, - "node-forge": { - "version": "0.6.33", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.33.tgz", - "integrity": "sha1-RjgRh59XPUUVWtap9D3ClujoXrw=", - "dev": true - }, - "node-gyp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz", - "integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=", - "dev": true, - "requires": { - "fstream": "1.0.11", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "npmlog": "4.1.2", - "osenv": "0.1.4", - "request": "2.83.0", - "rimraf": "2.6.2", - "semver": "5.3.0", - "tar": "2.2.1", - "which": "1.3.0" - }, - "dependencies": { - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "dev": true - } - } - }, - "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.4.1", - "browserify-zlib": "0.2.0", - "buffer": "4.9.1", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "domain-browser": "1.1.7", - "events": "1.1.1", - "https-browserify": "1.0.0", - "os-browserify": "0.3.0", - "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.3", - "stream-browserify": "2.0.1", - "stream-http": "2.7.2", - "string_decoder": "1.0.3", - "timers-browserify": "2.0.4", - "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", - "vm-browserify": "0.0.4" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "node-sass": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.7.2.tgz", - "integrity": "sha512-CaV+wLqZ7//Jdom5aUFCpGNoECd7BbNhjuwdsX/LkXBrHl8eb1Wjw4HvWqcFvhr5KuNgAk8i/myf/MQ1YYeroA==", - "dev": true, - "requires": { - "async-foreach": "0.1.3", - "chalk": "1.1.3", - "cross-spawn": "3.0.1", - "gaze": "1.1.2", - "get-stdin": "4.0.1", - "glob": "7.1.2", - "in-publish": "2.0.0", - "lodash.assign": "4.2.0", - "lodash.clonedeep": "4.5.0", - "lodash.mergewith": "4.6.0", - "meow": "3.7.0", - "mkdirp": "0.5.1", - "nan": "2.8.0", - "node-gyp": "3.6.2", - "npmlog": "4.1.2", - "request": "2.79.0", - "sass-graph": "2.2.4", - "stdout-stream": "1.4.0", - "true-case-path": "1.0.2" - }, - "dependencies": { - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", - "dev": true - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", - "dev": true - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", - "dev": true - }, - "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.1.1", - "which": "1.3.0" - } - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "dev": true, - "requires": { - "boom": "2.10.1" - } - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "dev": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" - } - }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "commander": "2.12.2", - "is-my-json-valid": "2.17.1", - "pinkie-promise": "2.0.1" - } - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "dev": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "dev": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" - } - }, - "qs": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", - "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", - "dev": true - }, - "request": { - "version": "2.79.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", - "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", - "dev": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.11.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "qs": "6.3.2", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.4.3", - "uuid": "3.1.0" - } - }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true - } - } - }, - "nodebrainz": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nodebrainz/-/nodebrainz-2.1.1.tgz", - "integrity": "sha1-3r8Mv2n/6ux0OaNkCe2cEEBLES8=", - "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.1.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.5.0", - "is-builtin-module": "1.0.0", - "semver": "5.4.1", - "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.1.0" - } - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "prepend-http": "1.0.4", - "query-string": "4.3.4", - "sort-keys": "1.1.2" - } - }, - "npm-run-all": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.2.tgz", - "integrity": "sha512-Z2aRlajMK4SQ8u19ZA75NZZu7wupfCNQWdYosIi8S6FgBdGf/8Y6Hgyjdc8zU2cYmIRVCx1nM80tJPkdEd+UYg==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "chalk": "2.3.0", - "cross-spawn": "5.1.0", - "memorystream": "0.3.1", - "minimatch": "3.0.4", - "ps-tree": "1.1.0", - "read-pkg": "3.0.0", - "shell-quote": "1.6.1", - "string.prototype.padend": "3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "4.0.0", - "pify": "3.0.0", - "strip-bom": "3.0.0" - } - }, - "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" - } - }, - "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" - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "4.0.0", - "normalize-package-data": "2.4.0", - "path-type": "3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "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" - } - } - } - }, - "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.1" - } - }, - "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.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "nth-check": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", - "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", - "requires": { - "boolbase": "1.0.0" - } - }, - "nugget": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", - "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", - "dev": true, - "requires": { - "debug": "2.6.9", - "minimist": "1.2.0", - "pretty-bytes": "1.0.4", - "progress-stream": "1.2.0", - "request": "2.83.0", - "single-line-log": "1.1.2", - "throttleit": "0.0.2" - }, - "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" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "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 - }, - "numeral": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", - "integrity": "sha1-StCAk21EPCVhrtnyGX7//iX05QY=" - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "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=" - }, - "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.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" - }, - "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.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "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.1.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 - } - } - } - } - }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "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.1" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" - }, - "dependencies": { - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "1.0.2" - } - } - } - }, - "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" - } - }, - "obuf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.1.tgz", - "integrity": "sha1-EEEktsYCxnlogaBCVB0220OlJk4=", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "opn": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz", - "integrity": "sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ==", - "dev": true, - "requires": { - "is-wsl": "1.1.0" - } - }, - "original": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.0.tgz", - "integrity": "sha1-kUf5P6FpbQS+YeAb1QuurKZWvTs=", - "dev": true, - "requires": { - "url-parse": "1.0.5" - }, - "dependencies": { - "url-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.0.5.tgz", - "integrity": "sha1-CFSGBCKv3P7+tsllxmLUgAFpkns=", - "dev": true, - "requires": { - "querystringify": "0.0.4", - "requires-port": "1.0.0" - } - } - } - }, - "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": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "osenv": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", - "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", - "dev": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "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-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", - "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.2.0" - } - }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "dev": true - }, - "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 - }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", - "dev": true, - "requires": { - "got": "6.7.1", - "registry-auth-token": "3.3.1", - "registry-url": "3.1.0", - "semver": "5.4.1" - } - }, - "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", - "dev": true - }, - "parse-asn1": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", - "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", - "dev": true, - "requires": { - "asn1.js": "4.9.2", - "browserify-aes": "1.1.1", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.14" - } - }, - "parse-color": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-color/-/parse-color-1.0.0.tgz", - "integrity": "sha1-e3SLlag/A/FqlPU15S1/PZRlhhk=", - "dev": true, - "requires": { - "color-convert": "0.5.3" - }, - "dependencies": { - "color-convert": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", - "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=", - "dev": true - } - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" - } - }, - "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.3.1" - } - }, - "parse5": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", - "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "requires": { - "@types/node": "7.0.52" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true - }, - "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": "https://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": "https://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-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "requires": { - "isarray": "0.0.1" - } - }, - "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.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "2.3.8" - } - }, - "pbkdf2": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", - "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", - "dev": true, - "requires": { - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.9" - } - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "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.4" - } - }, - "pitchfork-bnm": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pitchfork-bnm/-/pitchfork-bnm-1.0.3.tgz", - "integrity": "sha512-2N60Eot+P/9/c0/EwhSdDViivBsf0q9G1/zAo5gJhQ+6q34gfLM2GnMkWAmvmgFuSCmeXGfoigssh/D5UmbeqQ==", - "requires": { - "cheerio": "1.0.0-rc.2", - "isomorphic-fetch": "2.2.1" - } - }, - "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" - } - }, - "plist": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", - "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=", - "dev": true, - "requires": { - "base64-js": "1.2.0", - "xmlbuilder": "8.2.2", - "xmldom": "0.1.27" - } - }, - "portfinder": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz", - "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=", - "dev": true, - "requires": { - "async": "1.5.2", - "debug": "2.6.9", - "mkdirp": "0.5.1" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "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": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.0", - "source-map": "0.5.7", - "supports-color": "3.2.3" - }, - "dependencies": { - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-calc": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", - "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-message-helpers": "2.0.0", - "reduce-css-calc": "1.3.0" - } - }, - "postcss-colormin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", - "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", - "dev": true, - "requires": { - "colormin": "1.1.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-convert-values": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", - "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-discard-comments": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", - "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-duplicates": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", - "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-empty": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", - "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-overridden": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", - "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-unused": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", - "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, - "postcss-filter-plugins": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz", - "integrity": "sha1-bYWGJTTXNaxCDkqFgG4fXUKG2Ew=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "uniqid": "4.1.1" - } - }, - "postcss-merge-idents": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", - "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-merge-longhand": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", - "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-merge-rules": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", - "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-api": "1.6.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3", - "vendors": "1.0.1" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000793", - "electron-to-chromium": "1.3.30" - } - } - } - }, - "postcss-message-helpers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", - "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", - "dev": true - }, - "postcss-minify-font-values": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", - "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-minify-gradients": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", - "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-minify-params": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", - "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0", - "uniqs": "2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", - "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3" - } - }, - "postcss-modules-extract-imports": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz", - "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", - "dev": true, - "requires": { - "postcss": "6.0.16" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - }, - "dependencies": { - "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" - } - } - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "postcss": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.16.tgz", - "integrity": "sha512-m758RWPmSjFH/2MyyG3UOW1fgYbR9rtdzz5UNJnlm7OLtu4B2h9C6gi+bE4qFKghsBRFfZT8NzoQBs6JhLotoA==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "5.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 - }, - "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", - "dev": true, - "requires": { - "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.16" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - }, - "dependencies": { - "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" - } - } - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "postcss": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.16.tgz", - "integrity": "sha512-m758RWPmSjFH/2MyyG3UOW1fgYbR9rtdzz5UNJnlm7OLtu4B2h9C6gi+bE4qFKghsBRFfZT8NzoQBs6JhLotoA==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "5.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 - }, - "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", - "dev": true, - "requires": { - "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.16" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - }, - "dependencies": { - "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" - } - } - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "postcss": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.16.tgz", - "integrity": "sha512-m758RWPmSjFH/2MyyG3UOW1fgYbR9rtdzz5UNJnlm7OLtu4B2h9C6gi+bE4qFKghsBRFfZT8NzoQBs6JhLotoA==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "5.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 - }, - "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", - "dev": true, - "requires": { - "icss-replace-symbols": "1.1.0", - "postcss": "6.0.16" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - }, - "dependencies": { - "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" - } - } - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "postcss": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.16.tgz", - "integrity": "sha512-m758RWPmSjFH/2MyyG3UOW1fgYbR9rtdzz5UNJnlm7OLtu4B2h9C6gi+bE4qFKghsBRFfZT8NzoQBs6JhLotoA==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "5.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 - }, - "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "postcss-normalize-charset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", - "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-normalize-url": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", - "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", - "dev": true, - "requires": { - "is-absolute-url": "2.1.0", - "normalize-url": "1.9.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-ordered-values": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", - "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-reduce-idents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", - "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-reduce-initial": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", - "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-reduce-transforms": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", - "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", - "dev": true, - "requires": { - "flatten": "1.0.2", - "indexes-of": "1.0.1", - "uniq": "1.0.1" - } - }, - "postcss-svgo": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", - "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", - "dev": true, - "requires": { - "is-svg": "2.1.0", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0", - "svgo": "0.7.2" - } - }, - "postcss-unique-selectors": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", - "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, - "postcss-value-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", - "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", - "dev": true - }, - "postcss-zindex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", - "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-bytes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", - "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", - "dev": true, - "requires": { - "get-stdin": "4.0.1", - "meow": "3.7.0" - } - }, - "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.5.2", - "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "progress-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", - "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", - "dev": true, - "requires": { - "speedometer": "0.1.4", - "through2": "0.2.3" - } - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { - "asap": "2.0.6" - } - }, - "prop-types": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz", - "integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=", - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1" - } - }, - "proxy-addr": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", - "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", - "dev": true, - "requires": { - "forwarded": "0.1.2", - "ipaddr.js": "1.5.2" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "ps-tree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", - "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", - "dev": true, - "requires": { - "event-stream": "3.3.4" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", - "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "parse-asn1": "5.1.0", - "randombytes": "2.0.6" - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "strict-uri-encode": "1.1.0" - } - }, - "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 - }, - "querystringify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.4.tgz", - "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=", - "dev": true - }, - "raf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", - "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", - "requires": { - "performance-now": "2.1.0" - }, - "dependencies": { - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - } - } - }, - "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "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.2.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.6" - } - } - } - }, - "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.6" - } - } - } - }, - "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.1" - } - }, - "randomfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.3.tgz", - "integrity": "sha512-YL6GrhrWoic0Eq8rXVbMptH7dAxCs0J+mh5Y0euNekPPYaxEmdVGim6GdoxoRzKW2yJoU8tueifS7mYxvcFDEQ==", - "dev": true, - "requires": { - "randombytes": "2.0.6", - "safe-buffer": "5.1.1" - } - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - } - }, - "rc": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz", - "integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=", - "dev": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "react": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.2.0.tgz", - "integrity": "sha512-ZmIomM7EE1DvPEnSFAHZn9Vs9zJl5A9H7el0EGTE6ZbW9FKe/14IYAlPbC8iH25YarEQxZL+E8VW7Mi7kfQrDQ==", - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.0" - } - }, - "react-debounce-input": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.1.0.tgz", - "integrity": "sha512-7XG+VpWHfcZfKjrXQ11fPB30YdVvAjnPvi45RJQtLRb79otQEnXON2cCInydEAw+y+zxvH5g48kRMKU069vd0w==", - "dev": true, - "requires": { - "lodash.debounce": "4.0.8", - "prop-types": "15.6.0" - } - }, - "react-dom": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.2.0.tgz", - "integrity": "sha512-zpGAdwHVn9K0091d+hr+R0qrjoJ84cIBFL2uU60KvWBPfZ7LPSrfqviTxGHWN0sjPZb2hxWzMexwrvJdKePvjg==", - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.0" - } - }, - "react-fontawesome": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/react-fontawesome/-/react-fontawesome-1.6.1.tgz", - "integrity": "sha1-7dzhfn3HMaoJ/UoYZoimF5OhbFw=", - "dev": true, - "requires": { - "prop-types": "15.6.0" - } - }, - "react-hot-loader": { - "version": "4.0.0-beta.12", - "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.0.0-beta.12.tgz", - "integrity": "sha512-bSmWbjuzzYUDw/e6YDzHI1VkiKRjHP7egDh2lg7b/jJhcLbu3uhy7Al9sIZ4PDalZAGJZbnX9hGlSO/bj03H3w==", - "dev": true, - "requires": { - "fast-levenshtein": "2.0.6", - "global": "4.3.2", - "hoist-non-react-statics": "2.3.1", - "react-stand-in": "4.0.0-beta.12", - "redbox-react": "1.5.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 - } - } - }, - "react-motion": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/react-motion/-/react-motion-0.5.2.tgz", - "integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==", - "requires": { - "performance-now": "0.2.0", - "prop-types": "15.6.0", - "raf": "3.4.0" - } - }, - "react-redux": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz", - "integrity": "sha512-8taaaGu+J7PMJQDJrk/xiWEYQmdo3mkXw6wPr3K3LxvXis3Fymiq7c13S+Tpls/AyNUAsoONkU81AP0RA6y6Vw==", - "dev": true, - "requires": { - "hoist-non-react-statics": "2.3.1", - "invariant": "2.2.2", - "lodash": "4.17.4", - "lodash-es": "4.17.4", - "loose-envify": "1.3.1", - "prop-types": "15.6.0" - } - }, - "react-router": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz", - "integrity": "sha512-DY6pjwRhdARE4TDw7XjxjZsbx9lKmIcyZoZ+SDO7SBJ1KUeWNxT22Kara2AC7u6/c2SYEHlEDLnzBCcNhLE8Vg==", - "requires": { - "history": "4.7.2", - "hoist-non-react-statics": "2.3.1", - "invariant": "2.2.2", - "loose-envify": "1.3.1", - "path-to-regexp": "1.7.0", - "prop-types": "15.6.0", - "warning": "3.0.0" - } - }, - "react-router-dom": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.2.2.tgz", - "integrity": "sha512-cHMFC1ZoLDfEaMFoKTjN7fry/oczMgRt5BKfMAkTu5zEuJvUiPp1J8d0eXSVTnBh6pxlbdqDhozunOOLtmKfPA==", - "requires": { - "history": "4.7.2", - "invariant": "2.2.2", - "loose-envify": "1.3.1", - "prop-types": "15.6.0", - "react-router": "4.2.0", - "warning": "3.0.0" - } - }, - "react-router-redux": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-4.0.8.tgz", - "integrity": "sha1-InQDWWtRUeGCN32rg1tdRfD4BU4=", - "dev": true - }, - "react-router-transition": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/react-router-transition/-/react-router-transition-1.2.0.tgz", - "integrity": "sha512-eikpVutMen9ns++rZFMBuqC6Rq4DFx8IEykzGaE+jxhJNYzuvh2WzoTF3nNsDsCL5fGw3sc6374UIe/bZVAyUQ==", - "requires": { - "prop-types": "15.6.0", - "react": "15.6.2", - "react-dom": "15.6.2", - "react-motion": "0.5.2", - "react-router-dom": "4.2.2" - }, - "dependencies": { - "react": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", - "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", - "requires": { - "create-react-class": "15.6.2", - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.0" - } - }, - "react-dom": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz", - "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=", - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.0" - } - } - } - }, - "react-sound": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/react-sound/-/react-sound-0.10.0.tgz", - "integrity": "sha512-Urxh8n0I42IFK5UOJLmtXuahnM2iD1TmggD8Droy4lPjApFB/spfmv6BArKtD8RbMBH1bGauhkXcSqfmQLbIqQ==", - "requires": { - "prop-types": "15.6.0", - "react": "15.6.2", - "soundmanager2": "2.97.20170602" - }, - "dependencies": { - "react": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", - "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", - "requires": { - "create-react-class": "15.6.2", - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.0" - } - } - } - }, - "react-stand-in": { - "version": "4.0.0-beta.12", - "resolved": "https://registry.npmjs.org/react-stand-in/-/react-stand-in-4.0.0-beta.12.tgz", - "integrity": "sha512-mbd4OTHgWuQZYBDKad+IGfO0KGgqESAvK8AH5p+MJInhNOKs0Ahn74BDGtfWFoqsIR9HsHA1sadlvdZXm/iAow==", - "dev": true, - "requires": { - "shallowequal": "1.0.2" - } - }, - "read-config-file": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-2.1.1.tgz", - "integrity": "sha512-tzV5MRYA1OIbjy0ZC3cKlQZMLyRYMJ7k37Inff0CH0fQGXFP9p0s0eJ3bQxnnvQDhPSspnW9fw9v2K0b+6TODg==", - "dev": true, - "requires": { - "ajv": "5.5.2", - "ajv-keywords": "2.1.1", - "bluebird-lst": "1.0.5", - "dotenv": "4.0.0", - "dotenv-expand": "4.0.1", - "fs-extra-p": "4.5.0", - "js-yaml": "3.10.0", - "json5": "0.5.1", - "lazy-val": "1.0.3" - }, - "dependencies": { - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "4.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.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.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.1.2", - "read-pkg": "1.1.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.1.0", - "pinkie-promise": "2.0.1" - } - }, - "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.1" - } - } - } - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.3", - "set-immediate-shim": "1.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "redbox-react": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/redbox-react/-/redbox-react-1.5.0.tgz", - "integrity": "sha512-mdxArOI3sF8K5Nay5NG+lv/VW516TbXjjd4h1wcV1Iy4IMDQPnCayjoQXBAycAFSME4nyXRUXCjHxsw2rYpVRw==", - "dev": true, - "requires": { - "error-stack-parser": "1.3.6", - "object-assign": "4.1.1", - "prop-types": "15.6.0", - "sourcemapped-stacktrace": "1.1.8" - } - }, - "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" - } - }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "math-expression-evaluator": "1.2.17", - "reduce-function-call": "1.0.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "reduce-function-call": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", - "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", - "dev": true, - "requires": { - "balanced-match": "0.4.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "redux": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", - "dev": true, - "requires": { - "lodash": "4.17.4", - "lodash-es": "4.17.4", - "loose-envify": "1.3.1", - "symbol-observable": "1.1.0" - } - }, - "redux-promise": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/redux-promise/-/redux-promise-0.5.3.tgz", - "integrity": "sha1-6X5snTvzdurLebq+bZBtogES1tg=", - "dev": true, - "requires": { - "flux-standard-action": "0.6.1" - } - }, - "redux-thunk": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.2.0.tgz", - "integrity": "sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU=", - "dev": true - }, - "regenerate": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, - "regenerator-transform": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "private": "0.1.8" - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "0.1.3" - } - }, - "regex-not": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.0.tgz", - "integrity": "sha1-Qvg+OXcWIt+CawKvF2Ul1qXxV/k=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1" - } - }, - "regexpu-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", - "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", - "dev": true, - "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" - } - }, - "registry-auth-token": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", - "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", - "dev": true, - "requires": { - "rc": "1.2.2", - "safe-buffer": "5.1.1" - } - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "dev": true, - "requires": { - "rc": "1.2.2" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "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 - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "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.2" - } - }, - "request": { - "version": "2.83.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", - "dev": true, - "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.1", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.6.0", - "uuid": "3.1.0" - }, - "dependencies": { - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "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 - }, - "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-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-pathname": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" - }, - "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 - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "requires": { - "align-text": "0.1.4" - } - }, - "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.1.2" - } - }, - "ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", - "dev": true, - "requires": { - "hash-base": "2.0.2", - "inherits": "2.0.3" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "sanitize-filename": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.1.tgz", - "integrity": "sha1-YS2hyWRz+gLczaktzVtKsWSmdyo=", - "dev": true, - "requires": { - "truncate-utf8-bytes": "1.0.2" - } - }, - "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.1.2", - "lodash": "4.17.4", - "scss-tokenizer": "0.2.3", - "yargs": "7.1.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "1.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 - }, - "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.2.0", - "get-caller-file": "1.0.2", - "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": "6.0.6", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.6.tgz", - "integrity": "sha512-c3/Zc+iW+qqDip6kXPYLEgsAu2lf4xz0EZDplB7EmSUMda12U1sGJPetH55B/j9eu0bTtKzKlNPWWyYC7wFNyQ==", - "dev": true, - "requires": { - "async": "2.6.0", - "clone-deep": "0.3.0", - "loader-utils": "1.1.0", - "lodash.tail": "4.1.1", - "pify": "3.0.0" - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", - "dev": true, - "requires": { - "ajv": "5.5.2" - } - }, - "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.4.0", - "source-map": "0.4.4" - }, - "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": "1.0.1" - } - } - } - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true - }, - "selfsigned": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.1.tgz", - "integrity": "sha1-v4y3uDJWxFUeMTR8YxF3jbme7FI=", - "dev": true, - "requires": { - "node-forge": "0.6.33" - } - }, - "semantic-ui-react": { - "version": "0.77.2", - "resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-0.77.2.tgz", - "integrity": "sha512-VwxY6oGLrBO2xoJiAW/vn40GL4WGZYWxtbrHIKYE1xChTOFlDxyIULMdnuAHOTbrbOYbLQ0X7LpdvEEYmieiJw==", - "requires": { - "babel-runtime": "6.26.0", - "classnames": "2.2.5", - "fbjs": "0.8.16", - "lodash": "4.17.4", - "prop-types": "15.6.0" - } - }, - "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", - "dev": true - }, - "semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "dev": true, - "requires": { - "semver": "5.4.1" - } - }, - "send": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.1", - "fresh": "0.5.2", - "http-errors": "1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.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" - } - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true - } - } - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "requires": { - "accepts": "1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "1.0.3", - "http-errors": "1.6.2", - "mime-types": "2.1.17", - "parseurl": "1.3.2" - }, - "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" - } - } - } - }, - "serve-static": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", - "dev": true, - "requires": { - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.1" - } - }, - "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-getter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", - "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", - "dev": true, - "requires": { - "to-object-path": "0.3.0" - } - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "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.4", - "split-string": "3.1.0" - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "sha.js": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz", - "integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "shallow-clone": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", - "dev": true, - "requires": { - "is-extendable": "0.1.1", - "kind-of": "2.0.1", - "lazy-cache": "0.2.7", - "mixin-object": "2.0.1" - }, - "dependencies": { - "kind-of": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "shallowequal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.0.2.tgz", - "integrity": "sha512-zlVXeVUKvo+HEv1e2KQF/csyeMKx2oHvatQ9l6XjCUj3agvC8XGf6R9HvIPDSmp8FNPvx7b5kaEJTRi7CqxtEw==", - "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 - }, - "shell-quote": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", - "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", - "dev": true, - "requires": { - "array-filter": "0.0.1", - "array-map": "0.0.0", - "array-reduce": "0.0.0", - "jsonify": "0.0.0" - } - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "single-line-log": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", - "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", - "dev": true, - "requires": { - "string-width": "1.0.2" - } - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "snapdragon": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.1.tgz", - "integrity": "sha1-4StUh/re0+PeoKyR6UAL91tAE3A=", - "dev": true, - "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.7", - "source-map-resolve": "0.5.1", - "use": "2.0.2" - }, - "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.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "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.1.0" - } - }, - "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 - } - } - }, - "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.1", - "snapdragon-util": "3.0.1" - } - }, - "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.2" - } - }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "dev": true, - "requires": { - "hoek": "4.2.0" - } - }, - "sockjs": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", - "dev": true, - "requires": { - "faye-websocket": "0.10.0", - "uuid": "3.1.0" - } - }, - "sockjs-client": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.4.tgz", - "integrity": "sha1-W6vjhrd15M8U51IJEUUmVAFsixI=", - "dev": true, - "requires": { - "debug": "2.6.9", - "eventsource": "0.1.6", - "faye-websocket": "0.11.1", - "inherits": "2.0.3", - "json3": "3.3.2", - "url-parse": "1.2.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" - } - }, - "faye-websocket": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", - "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", - "dev": true, - "requires": { - "websocket-driver": "0.7.0" - } - } - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "1.1.0" - } - }, - "soundmanager2": { - "version": "2.97.20170602", - "resolved": "https://registry.npmjs.org/soundmanager2/-/soundmanager2-2.97.20170602.tgz", - "integrity": "sha512-2ee7ES9SJ++WkD7PGHMeT4QUuJr7uC3wacD6RoCDlKjdSp9lpEOaKm3lKWKld119DLILjS2l9U6xpXJN6U0KPQ==" - }, - "source-list-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", - "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", - "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.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", - "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", - "dev": true, - "requires": { - "atob": "2.0.3", - "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.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - }, - "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 - }, - "sourcemapped-stacktrace": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/sourcemapped-stacktrace/-/sourcemapped-stacktrace-1.1.8.tgz", - "integrity": "sha512-OkVoI7GQOLl/laR1qsSo1c87tS8kF2VXhQq2SrQCDdXufBAcm8FgXogWso96ciMYoDtTw1Dn70CVdwYzoYs6Pg==", - "dev": true, - "requires": { - "source-map": "0.5.6" - }, - "dependencies": { - "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", - "dev": true - } - } - }, - "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", - "dev": true, - "requires": { - "spdx-license-ids": "1.2.2" - } - }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", - "dev": true - }, - "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", - "dev": true - }, - "spdy": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz", - "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", - "dev": true, - "requires": { - "debug": "2.6.9", - "handle-thing": "1.2.5", - "http-deceiver": "1.2.7", - "safe-buffer": "5.1.1", - "select-hose": "2.0.0", - "spdy-transport": "2.0.20" - }, - "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" - } - } - } - }, - "spdy-transport": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.0.20.tgz", - "integrity": "sha1-c15yBUxIayNU/onnAiVgBKOazk0=", - "dev": true, - "requires": { - "debug": "2.6.9", - "detect-node": "2.0.3", - "hpack.js": "2.1.6", - "obuf": "1.1.1", - "readable-stream": "2.3.3", - "safe-buffer": "5.1.1", - "wbuf": "1.7.2" - }, - "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" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "speedometer": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", - "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", - "dev": true - }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "dev": true, - "requires": { - "through": "2.3.8" - } - }, - "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.2" - }, - "dependencies": { - "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" - } - }, - "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" - } - } - } - }, - "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.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "dev": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - } - }, - "stackframe": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-0.3.1.tgz", - "integrity": "sha1-M6qE8Rd6VUjIk1Uzy/6zQgl19aQ=", - "dev": true - }, - "stat-mode": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", - "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", - "dev": true - }, - "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.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "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.1.0" - } - }, - "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 - } - } - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", - "dev": true - }, - "stdout-stream": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz", - "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=", - "dev": true, - "requires": { - "readable-stream": "2.3.3" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "steno": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/steno/-/steno-0.4.4.tgz", - "integrity": "sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs=", - "requires": { - "graceful-fs": "4.1.11" - } - }, - "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "0.1.1" - } - }, - "stream-http": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", - "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", - "dev": true, - "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - } - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string.prototype.padend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz", - "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=", - "dev": true, - "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.10.0", - "function-bind": "1.1.1" - } - }, - "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 - }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "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.1.1" - } - }, - "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.1" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://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.19.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.19.1.tgz", - "integrity": "sha512-IRE+ijgojrygQi3rsqT0U4dd+UcPCqcVvauZpCnQrGAlEe+FUIyrK93bUDScamesjP08JlQNsFJU+KmPedP5Og==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.3.0" - } - }, - "sumchecker": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-1.3.1.tgz", - "integrity": "sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0=", - "dev": true, - "requires": { - "debug": "2.6.9", - "es6-promise": "4.2.2" - }, - "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" - } - } - } - }, - "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 - }, - "svgo": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", - "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", - "dev": true, - "requires": { - "coa": "1.0.4", - "colors": "1.1.2", - "csso": "2.3.2", - "js-yaml": "3.7.0", - "mkdirp": "0.5.1", - "sax": "1.2.4", - "whet.extend": "0.9.9" - } - }, - "symbol-observable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.1.0.tgz", - "integrity": "sha512-dQoid9tqQ+uotGhuTKEY11X4xhyYePVnqGSoSm3OGKh2E8LZ6RPULp1uXTctk33IeERlrRJYoVSBglsL05F5Uw==", - "dev": true - }, - "tapable": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", - "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", - "dev": true - }, - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "temp-file": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.1.1.tgz", - "integrity": "sha512-W/6SJgtg2SE/5rxgwUwoDhdSXrvUWQBpgKJglaAe6S7mk1kLkI+LUbY/jPZBu3UhydDJZstNNd7AJhnZ0UZHtw==", - "dev": true, - "requires": { - "async-exit-hook": "2.0.1", - "bluebird-lst": "1.0.5", - "fs-extra-p": "4.5.0", - "lazy-val": "1.0.3" - } - }, - "term-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", - "dev": true, - "requires": { - "execa": "0.7.0" - } - }, - "throttleit": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", - "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", - "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": "0.2.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", - "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", - "dev": true, - "requires": { - "readable-stream": "1.1.14", - "xtend": "2.1.2" - } - }, - "thunky": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-0.1.0.tgz", - "integrity": "sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=", - "dev": true - }, - "time-stamp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-2.0.0.tgz", - "integrity": "sha1-lcakRTDhW6jW9KPsuMOj+sRto1c=", - "dev": true - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true - }, - "timers-browserify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz", - "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==", - "dev": true, - "requires": { - "setimmediate": "1.0.5" - } - }, - "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": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "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.2.2" - } - }, - "to-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.1.tgz", - "integrity": "sha1-FTWL7kosg712N3uh3ASdDxiDeq4=", - "dev": true, - "requires": { - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "regex-not": "1.0.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.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "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.1.0" - } - }, - "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 - } - } - }, - "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" - }, - "dependencies": { - "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.2.2" - } - } - } - }, - "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", - "dev": true, - "requires": { - "punycode": "1.4.1" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz", - "integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=", - "dev": true, - "requires": { - "glob": "6.0.4" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } - } - }, - "truncate-utf8-bytes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", - "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", - "dev": true, - "requires": { - "utf8-byte-length": "1.0.4" - } - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://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.1.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, - "optional": true - }, - "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "2.1.17" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "ua-parser-js": { - "version": "0.7.17", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", - "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==" - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - }, - "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - } - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "uglifyjs-webpack-plugin": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", - "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", - "dev": true, - "requires": { - "source-map": "0.5.7", - "uglify-js": "2.8.29", - "webpack-sources": "1.1.0" - } - }, - "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": { - "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.4", - "to-object-path": "0.3.0" - } - } - } - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "uniqid": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.1.tgz", - "integrity": "sha1-iSIN32t1GuUrX3JISGNShZa7hME=", - "dev": true, - "requires": { - "macaddress": "0.2.8" - } - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "dev": true, - "requires": { - "crypto-random-string": "1.0.0" - } - }, - "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "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.1" - }, - "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.6", - "has-values": "0.1.4", - "isobject": "2.1.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 - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", - "dev": true - }, - "update-notifier": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.3.0.tgz", - "integrity": "sha1-TognpruRUUCrCTVZ1wFOPruDdFE=", - "dev": true, - "requires": { - "boxen": "1.3.0", - "chalk": "2.3.0", - "configstore": "3.1.1", - "import-lazy": "2.1.0", - "is-installed-globally": "0.1.0", - "is-npm": "1.0.0", - "latest-version": "3.1.0", - "semver-diff": "2.1.0", - "xdg-basedir": "3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "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" - } - } - } - }, - "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-parse": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz", - "integrity": "sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw==", - "dev": true, - "requires": { - "querystringify": "1.0.0", - "requires-port": "1.0.0" - }, - "dependencies": { - "querystringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", - "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=", - "dev": true - } - } - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "1.0.4" - } - }, - "use": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/use/-/use-2.0.2.tgz", - "integrity": "sha1-riig1y+TvyJCKhii43mZMRLeyOg=", - "dev": true, - "requires": { - "define-property": "0.2.5", - "isobject": "3.0.1", - "lazy-cache": "2.0.2" - }, - "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.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "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.1.0" - } - }, - "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 - }, - "lazy-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", - "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", - "dev": true, - "requires": { - "set-getter": "0.1.0" - } - } - } - }, - "utf8-byte-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", - "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "dev": true, - "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" - } - }, - "value-equal": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "vendors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz", - "integrity": "sha1-N61zyO5Bf7PVgOeFMSMH0nSEfyI=", - "dev": true - }, - "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.3.0" - } - }, - "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } - }, - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "1.3.1" - } - }, - "watchpack": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", - "integrity": "sha1-ShRyvLuVK9Cpu0A2gB+VTfs5+qw=", - "dev": true, - "requires": { - "async": "2.6.0", - "chokidar": "1.7.0", - "graceful-fs": "4.1.11" - } - }, - "wbuf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.2.tgz", - "integrity": "sha1-1pe5nx9ZUS3ydRvkJ2nBWAtYAf4=", - "dev": true, - "requires": { - "minimalistic-assert": "1.0.0" - } - }, - "webpack": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.10.0.tgz", - "integrity": "sha512-fxxKXoicjdXNUMY7LIdY89tkJJJ0m1Oo8PQutZ5rLgWbV5QVKI15Cn7+/IHnRTd3vfKfiwBx6SBqlorAuNA8LA==", - "dev": true, - "requires": { - "acorn": "5.3.0", - "acorn-dynamic-import": "2.0.2", - "ajv": "5.5.2", - "ajv-keywords": "2.1.1", - "async": "2.6.0", - "enhanced-resolve": "3.4.1", - "escope": "3.6.0", - "interpret": "1.1.0", - "json-loader": "0.5.7", - "json5": "0.5.1", - "loader-runner": "2.3.0", - "loader-utils": "1.1.0", - "memory-fs": "0.4.1", - "mkdirp": "0.5.1", - "node-libs-browser": "2.1.0", - "source-map": "0.5.7", - "supports-color": "4.5.0", - "tapable": "0.2.8", - "uglifyjs-webpack-plugin": "0.4.6", - "watchpack": "1.4.0", - "webpack-sources": "1.1.0", - "yargs": "8.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "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 - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "2.3.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "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" - } - }, - "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": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "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" - } - }, - "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", - "dev": true, - "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "7.0.0" - } - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "dev": true, - "requires": { - "camelcase": "4.1.0" - } - } - } - }, - "webpack-dev-middleware": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz", - "integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==", - "dev": true, - "requires": { - "memory-fs": "0.4.1", - "mime": "1.6.0", - "path-is-absolute": "1.0.1", - "range-parser": "1.2.0", - "time-stamp": "2.0.0" - }, - "dependencies": { - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - } - } - }, - "webpack-dev-server": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.0.tgz", - "integrity": "sha512-lXzc36DGjKUVinETNmDWhfZFRbHMhatuF+lKex+czqY+JVe0Qf2V+Ig6/svDdbt/DmXFXuLQmSqhncYCqYf3qA==", - "dev": true, - "requires": { - "ansi-html": "0.0.7", - "array-includes": "3.0.3", - "bonjour": "3.5.0", - "chokidar": "2.0.0", - "compression": "1.7.1", - "connect-history-api-fallback": "1.5.0", - "debug": "3.1.0", - "del": "3.0.0", - "express": "4.16.2", - "html-entities": "1.2.1", - "http-proxy-middleware": "0.17.4", - "import-local": "1.0.0", - "internal-ip": "1.2.0", - "ip": "1.1.5", - "killable": "1.0.0", - "loglevel": "1.6.1", - "opn": "5.2.0", - "portfinder": "1.0.13", - "selfsigned": "1.10.1", - "serve-index": "1.9.1", - "sockjs": "0.3.19", - "sockjs-client": "1.1.4", - "spdy": "3.4.7", - "strip-ansi": "4.0.0", - "supports-color": "5.1.0", - "webpack-dev-middleware": "1.12.2", - "yargs": "6.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "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.5", - "normalize-path": "2.1.1" - } - }, - "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 - }, - "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 - }, - "braces": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.0.tgz", - "integrity": "sha512-P4O8UQRdGiMLWSizsApmXVQDBS6KCt7dSexgLKBmH5Hr1CZq7vsnscFh8oR1sP1ab1Zj0uCHCEzZeV6SfUf3rA==", - "dev": true, - "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "define-property": "1.0.0", - "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.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.1" - } - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "chokidar": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.0.tgz", - "integrity": "sha512-OgXCNv2U6TnG04D3tth0gsvdbV4zdbxFG3sYUqcoQMoEFVd1j1pZR6TZ8iknC45o9IJ6PeQI/J6wT/+cHcniAw==", - "dev": true, - "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.1", - "braces": "2.3.0", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.0", - "normalize-path": "2.1.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.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.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "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.6" - } - } - } - }, - "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" - } - }, - "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.1" - } - }, - "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.2" - }, - "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.1" - } - } - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.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.6" - } - } - } - }, - "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.1.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-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-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.2.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.6" - } - } - } - }, - "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 - }, - "micromatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.5.tgz", - "integrity": "sha512-ykttrLPQrz1PUJcXjwsTUjGoPJ64StIGNE2lGVD1c9CuguJ+L7/navsE8IcDNndOoCMvYV0qc/exfVbMHkUhvA==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.0", - "define-property": "1.0.0", - "extend-shallow": "2.0.1", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.7", - "object.pick": "1.3.0", - "regex-not": "1.0.0", - "snapdragon": "0.8.1", - "to-regex": "3.0.1" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "1.0.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" - } - }, - "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", - "dev": true, - "requires": { - "has-flag": "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 - }, - "yargs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", - "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", - "dev": true, - "requires": { - "camelcase": "3.0.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "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": "4.2.1" - } - }, - "yargs-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", - "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", - "dev": true, - "requires": { - "camelcase": "3.0.0" - } - } - } - }, - "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", - "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.9", - "websocket-extensions": "0.1.3" - } - }, - "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 - }, - "whatwg-fetch": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", - "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" - }, - "whet.extend": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", - "dev": true - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "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.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", - "dev": true, - "requires": { - "string-width": "1.0.2" - } - }, - "widest-line": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", - "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", - "dev": true, - "requires": { - "string-width": "2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "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 - }, - "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" - } - }, - "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" - } - } - } - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "imurmurhash": "0.1.4", - "signal-exit": "3.0.2" - } - }, - "xdg-basedir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", - "dev": true - }, - "xmlbuilder": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", - "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", - "dev": true - }, - "xmldom": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", - "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", - "dev": true - }, - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "dev": true, - "requires": { - "object-keys": "0.4.0" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "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": "10.1.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-10.1.1.tgz", - "integrity": "sha512-7uRL1HZdCbc1QTP+X8mehOPuCYKC/XTaqAPj7gABLfTt6pgLyVRn3QVte4qhtilZouWCvqd1kipgMKl5tKsFiw==", - "dev": true, - "requires": { - "cliui": "4.0.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "8.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "cliui": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz", - "integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==", - "dev": true, - "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.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 - }, - "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" - } - }, - "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" - } - } - } - }, - "yargs-parser": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", - "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", - "dev": true, - "requires": { - "camelcase": "4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } - }, - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "dev": true, - "requires": { - "fd-slicer": "1.0.1" - } - }, - "ytdl-core": { - "version": "0.18.7", - "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-0.18.7.tgz", - "integrity": "sha512-ZAopedHbn8YQzXAanz1EvD20JLs5+E/BTADOmPPwtLR8QP9ipMi3Iamcs5lhf5FztCNcjtasPIMyxslZZc3szQ==", - "requires": { - "html-entities": "1.2.1", - "m3u8stream": "0.2.1", - "miniget": "1.1.0", - "sax": "1.2.4" - } - } - } -} diff --git a/package.json b/package.json index 994494c636..062435c144 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,28 @@ { "name": "nuclear", - "version": "0.4.0", + "version": "0.4.5", "description": "Streaming music player that finds music sources automatically.", "main": "main.js", "scripts": { - "start": "npm-run-all --parallel watch electron", - "electron": "electron .", + "start": "npm-run-all --parallel watch electron:dev:linux", + "electron:dev:linux": "webpack --progress --colors --env.LINUX=true --config=webpack.config.electron.js && electron ./bundle.electron.js", + "electron:dev": "webpack --progress --colors --config=webpack.config.electron.js && electron ./bundle.electron.js", + "electron:prod:linux": "npm run build:electron:linux && electron ./dist/bundle.electron.js", + "electron:prod": "npm run build:electron && electron ./dist/bundle.electron.js", + "electron:docker": "docker run --rm --net=host --env=\"DISPLAY\" --volume=\"$HOME/.Xauthority:/root/.Xauthority:rw\" --device /dev/snd nuclear", "watch": "webpack-dev-server --inline --progress --env=dev", - "build:dist": "webpack --progress --colors --env=prod", - "test": "echo \"Error: no test specified\" && exit 1", - "pack": "electron-builder --dir --em.main=main.prod.js", - "dist": "electron-builder --em.main=main.prod.js", - "build:linux": "electron-builder --em.main=main.prod.js --linux", - "build:windows": "electron-builder --em.main=main.prod.js --windows", - "build:macos": "electron-builder --em.main=main.prod.js --macos", - "build:all": "electron-builder --em.main=main.prod.js -mwl" + "build:dist": "webpack --progress --colors --env=prod && cp loader.css dist", + "build:electron:linux": "webpack --progress --colors --env.LINUX=true --config=webpack.config.electron.prod.js", + "build:electron": "webpack --progress --colors --config=webpack.config.electron.prod.js", + "build:docker": "docker build -t nuclear .", + "test": "mocha --require babel-register --require babel-polyfill --require ignore-styles --timeout 10000 --prof", + "pack": "electron-builder --dir -c.extraMetadata.main=dist/bundle.electron.js", + "dist": "babel-node electron-builder -c.extraMetadata.main=dist/bundle.electron.js", + "build:linux": "electron-builder -c.extraMetadata.main=dist/bundle.electron.js --linux", + "build:windows": "electron-builder -c.extraMetadata.main=dist/bundle.electron.js --windows", + "build:macos": "electron-builder -c.extraMetadata.main=dist/bundle.electron.js --macos", + "build:all": "electron-builder -c.extraMetadata.main=dist/bundle.electron.js -mwl", + "lint": "eslint app" }, "repository": { "type": "git", @@ -25,81 +33,131 @@ "music", "youtube" ], - "author": "nukeop", - "license": "GPL-3.0", + "author": { + "name": "nukeop", + "email": "nuclear@gumblert.tech" + }, + "license": "AGPL-3.0", "bugs": { "url": "https://github.com/nukeop/nuclear/issues" }, "homepage": "https://github.com/nukeop/nuclear#readme", "dependencies": { + "billboard-top-100": "^2.0.8", + "bluebird": "^3.5.3", + "body-parser": "^1.18.3", + "cheerio": "^1.0.0-rc.2", + "cors": "^2.8.5", "electron-platform": "^1.2.0", + "electron-store": "^2.0.0", + "electron-timber": "^0.5.1", + "express": "^4.16.4", + "express-json-validator-middleware": "^1.2.3", + "fast-levenshtein": "^2.0.6", "font-awesome": "^4.7.0", - "lowdb": "^1.0.0", + "get-artist-title": "^1.1.1", "md5": "^2.2.1", + "moment": "^2.20.1", + "mousetrap": "^1.6.2", + "nuclear-core": "0.0.5", + "nuclear-ui": "0.0.6", "numeral": "^2.0.6", "pitchfork-bnm": "^1.0.3", - "react": "^16.2.0", - "react-dom": "^16.2.0", - "react-router-transition": "^1.2.0", - "react-sound": "^0.10.0", - "semantic-ui-react": "^0.77.2", - "ytdl-core": "^0.18.7" + "react": "^16.3.2", + "react-beautiful-dnd": "^9.0.0", + "react-dom": "^16.3.2", + "react-image-smooth-loading": "^2.0.0", + "react-range-progress": "^4.0.3", + "react-sound": "^1.1.0", + "react-toastify": "^4.5.2", + "semantic-ui-react": "^0.82.1", + "simple-get-lyrics": "0.0.4", + "styled-components": "^3.2.6", + "swagger-spec-express": "^2.0.7", + "uuid": "^3.2.1", + "webpack-cli": "^3.0.8", + "youtube-playlist": "^1.0.2", + "ytdl-core": "^0.24.0", + "mpris-service": "^2.0.0" }, "devDependencies": { "babel-core": "^6.26.0", + "babel-eslint": "^10.0.1", "babel-loader": "^7.1.2", - "babel-preset-env": "^1.6.1", + "babel-plugin-add-module-exports": "^0.2.1", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-object-rest-spread": "^6.26.0", + "babel-polyfill": "^6.26.0", + "babel-preset-env": "^1.7.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", + "babel-preset-stage-0": "^6.24.1", + "babel-preset-stage-2": "^6.24.1", + "babel-register": "^6.26.0", + "chai": "^4.1.2", "classnames": "^2.2.5", - "css-loader": "^0.28.9", - "electron": "^1.7.10", - "electron-builder": "^19.54.0", - "electron-devtools-installer": "^2.2.3", + "css-loader": "^1.0.0", + "electron": "^4.0.2", + "electron-builder": "^20.38.5", + "electron-devtools-installer": "^2.2.4", + "enzyme": "^3.3.0", + "enzyme-adapter-react-16": "^1.3.1", + "eslint": "^5.12.1", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-react": "^7.12.4", "extract-text-webpack-plugin": "^3.0.2", "file-loader": "^1.1.6", - "google-fonts-webpack-plugin": "^0.4.4", - "lodash": "^4.17.4", - "node-sass": "^4.7.2", + "happypack": "^5.0.0", + "html-webpack-plugin": "^3.2.0", + "ignore-styles": "^5.0.1", + "isomorphic-fetch": "^2.2.1", + "jsdom": "^12.0.0", + "lodash": "^4.17.5", + "mocha": "^5.0.0", + "node-loader": "^0.6.0", + "node-sass": "^4.8.3", "nodebrainz": "^2.1.1", "npm-run-all": "^4.1.2", - "react-debounce-input": "^3.1.0", + "react-debounce-input": "^3.2.0", "react-fontawesome": "^1.6.1", "react-hot-loader": "next", "react-redux": "^5.0.6", "react-router": "^4.2.0", "react-router-dom": "^4.2.2", "react-router-redux": "^4.0.8", - "redux": "^3.7.2", - "redux-promise": "^0.5.3", + "redux": "^4.0.0", + "redux-promise": "^0.6.0", "redux-thunk": "^2.2.0", - "sass-loader": "^6.0.6", - "style-loader": "^0.19.1", - "webpack": "^3.10.0", - "webpack-dev-server": "^2.11.0" + "remote-redux-devtools": "^0.5.16", + "sass-loader": "^7.0.1", + "style-loader": "^0.23.0", + "url-loader": "^1.0.1", + "webpack": "^4.12.1", + "webpack-dev-server": "^3.1.4" }, "optionalDependencies": { - "dbus": "^1.0.2", - "mpris-service": "^1.1.3" + "7zip-bin-mac": "^1.0.1" }, "build": { "appId": "nuclear", "productName": "nuclear", - "icon": "resources/media/icon.icns", "directories": { "output": "release" }, "files": [ "dist/", - "index.prod.html", - "main.prod.js", - "mpris.js", + "dist/electron.bundle.js", "package.json" ], "extraFiles": [ "resources" ], "linux": { + "desktop": { + "Name": "Nuclear", + "Name[es]": "Reproductor de música Nuclear", + "Comment[es]": "Reproductor que retransmite música desde fuentes encontradas automáticamente." + }, "target": [ "AppImage", "deb", @@ -110,8 +168,9 @@ "mac": { "category": "public.app-category.music", "target": [ - "dmg", - "zip" + "zip", + "pkg", + "dmg" ] }, "win": { diff --git a/resources/media/1024x1024.png b/resources/media/1024x1024.png new file mode 100644 index 0000000000..d3193755a1 Binary files /dev/null and b/resources/media/1024x1024.png differ diff --git a/resources/media/512x512.png b/resources/media/512x512.png new file mode 100644 index 0000000000..645b73de29 Binary files /dev/null and b/resources/media/512x512.png differ diff --git a/resources/media/presskit/Press Statement.txt b/resources/media/presskit/Press Statement.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/resources/media/presskit/icons/color/1024.png b/resources/media/presskit/icons/color/1024.png new file mode 100644 index 0000000000..88142d288f Binary files /dev/null and b/resources/media/presskit/icons/color/1024.png differ diff --git a/resources/media/presskit/icons/color/128.png b/resources/media/presskit/icons/color/128.png new file mode 100644 index 0000000000..276e7a37ef Binary files /dev/null and b/resources/media/presskit/icons/color/128.png differ diff --git a/resources/media/presskit/icons/color/16.png b/resources/media/presskit/icons/color/16.png new file mode 100644 index 0000000000..f80ba9f37d Binary files /dev/null and b/resources/media/presskit/icons/color/16.png differ diff --git a/resources/media/presskit/icons/color/24.png b/resources/media/presskit/icons/color/24.png new file mode 100644 index 0000000000..6e6a0dbe67 Binary files /dev/null and b/resources/media/presskit/icons/color/24.png differ diff --git a/resources/media/presskit/icons/color/256.png b/resources/media/presskit/icons/color/256.png new file mode 100644 index 0000000000..a92ead0507 Binary files /dev/null and b/resources/media/presskit/icons/color/256.png differ diff --git a/resources/media/presskit/icons/color/32.png b/resources/media/presskit/icons/color/32.png new file mode 100644 index 0000000000..cd1f5b0047 Binary files /dev/null and b/resources/media/presskit/icons/color/32.png differ diff --git a/resources/media/presskit/icons/color/48.png b/resources/media/presskit/icons/color/48.png new file mode 100644 index 0000000000..d2f85f9808 Binary files /dev/null and b/resources/media/presskit/icons/color/48.png differ diff --git a/resources/media/presskit/icons/color/512.png b/resources/media/presskit/icons/color/512.png new file mode 100644 index 0000000000..c47a81788c Binary files /dev/null and b/resources/media/presskit/icons/color/512.png differ diff --git a/resources/media/presskit/icons/color/64.png b/resources/media/presskit/icons/color/64.png new file mode 100644 index 0000000000..6e6156e955 Binary files /dev/null and b/resources/media/presskit/icons/color/64.png differ diff --git a/resources/media/presskit/icons/color/96.png b/resources/media/presskit/icons/color/96.png new file mode 100644 index 0000000000..21f2c5f241 Binary files /dev/null and b/resources/media/presskit/icons/color/96.png differ diff --git a/resources/media/presskit/icons/greyscale/dark/1024.png b/resources/media/presskit/icons/greyscale/dark/1024.png new file mode 100644 index 0000000000..b1d23bd3e6 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/dark/1024.png differ diff --git a/resources/media/presskit/icons/greyscale/dark/128.png b/resources/media/presskit/icons/greyscale/dark/128.png new file mode 100644 index 0000000000..025d251af4 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/dark/128.png differ diff --git a/resources/media/presskit/icons/greyscale/dark/24.png b/resources/media/presskit/icons/greyscale/dark/24.png new file mode 100644 index 0000000000..8c9cdd6023 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/dark/24.png differ diff --git a/resources/media/presskit/icons/greyscale/dark/256.png b/resources/media/presskit/icons/greyscale/dark/256.png new file mode 100644 index 0000000000..8033fa7f38 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/dark/256.png differ diff --git a/resources/media/presskit/icons/greyscale/dark/32.png b/resources/media/presskit/icons/greyscale/dark/32.png new file mode 100644 index 0000000000..a98834e98c Binary files /dev/null and b/resources/media/presskit/icons/greyscale/dark/32.png differ diff --git a/resources/media/presskit/icons/greyscale/dark/48.png b/resources/media/presskit/icons/greyscale/dark/48.png new file mode 100644 index 0000000000..e6122fb995 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/dark/48.png differ diff --git a/resources/media/presskit/icons/greyscale/dark/512.png b/resources/media/presskit/icons/greyscale/dark/512.png new file mode 100644 index 0000000000..0cd7be45f1 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/dark/512.png differ diff --git a/resources/media/presskit/icons/greyscale/dark/64.png b/resources/media/presskit/icons/greyscale/dark/64.png new file mode 100644 index 0000000000..97f495a874 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/dark/64.png differ diff --git a/resources/media/presskit/icons/greyscale/dark/96.png b/resources/media/presskit/icons/greyscale/dark/96.png new file mode 100644 index 0000000000..209dc1668d Binary files /dev/null and b/resources/media/presskit/icons/greyscale/dark/96.png differ diff --git a/resources/media/presskit/icons/greyscale/light/1024.png b/resources/media/presskit/icons/greyscale/light/1024.png new file mode 100644 index 0000000000..4990b144f1 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/light/1024.png differ diff --git a/resources/media/presskit/icons/greyscale/light/128.png b/resources/media/presskit/icons/greyscale/light/128.png new file mode 100644 index 0000000000..41f6444d00 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/light/128.png differ diff --git a/resources/media/presskit/icons/greyscale/light/16.png b/resources/media/presskit/icons/greyscale/light/16.png new file mode 100644 index 0000000000..f665c9f936 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/light/16.png differ diff --git a/resources/media/presskit/icons/greyscale/light/24.png b/resources/media/presskit/icons/greyscale/light/24.png new file mode 100644 index 0000000000..074c3ee0eb Binary files /dev/null and b/resources/media/presskit/icons/greyscale/light/24.png differ diff --git a/resources/media/presskit/icons/greyscale/light/256.png b/resources/media/presskit/icons/greyscale/light/256.png new file mode 100644 index 0000000000..d701ed0c87 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/light/256.png differ diff --git a/resources/media/presskit/icons/greyscale/light/32.png b/resources/media/presskit/icons/greyscale/light/32.png new file mode 100644 index 0000000000..2b07ca2ad5 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/light/32.png differ diff --git a/resources/media/presskit/icons/greyscale/light/48.png b/resources/media/presskit/icons/greyscale/light/48.png new file mode 100644 index 0000000000..d2d1cea86f Binary files /dev/null and b/resources/media/presskit/icons/greyscale/light/48.png differ diff --git a/resources/media/presskit/icons/greyscale/light/512.png b/resources/media/presskit/icons/greyscale/light/512.png new file mode 100644 index 0000000000..a68c290466 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/light/512.png differ diff --git a/resources/media/presskit/icons/greyscale/light/64.png b/resources/media/presskit/icons/greyscale/light/64.png new file mode 100644 index 0000000000..1ef9e7bdd5 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/light/64.png differ diff --git a/resources/media/presskit/icons/greyscale/light/96.png b/resources/media/presskit/icons/greyscale/light/96.png new file mode 100644 index 0000000000..da73f6cb87 Binary files /dev/null and b/resources/media/presskit/icons/greyscale/light/96.png differ diff --git a/resources/media/presskit/icons/scalable/nuclear-icon-mono-dark.svg b/resources/media/presskit/icons/scalable/nuclear-icon-mono-dark.svg new file mode 100644 index 0000000000..3db9bd20cc --- /dev/null +++ b/resources/media/presskit/icons/scalable/nuclear-icon-mono-dark.svg @@ -0,0 +1,1171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/resources/media/presskit/icons/scalable/nuclear-icon-mono-light.svg b/resources/media/presskit/icons/scalable/nuclear-icon-mono-light.svg new file mode 100644 index 0000000000..90ef3bd7cc --- /dev/null +++ b/resources/media/presskit/icons/scalable/nuclear-icon-mono-light.svg @@ -0,0 +1,1171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/resources/media/presskit/icons/scalable/nuclear-icon.svg b/resources/media/presskit/icons/scalable/nuclear-icon.svg new file mode 100644 index 0000000000..7264fc137f --- /dev/null +++ b/resources/media/presskit/icons/scalable/nuclear-icon.svg @@ -0,0 +1,1194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/resources/media/presskit/logo/logo-large.png b/resources/media/presskit/logo/logo-large.png new file mode 100644 index 0000000000..7e157ca0db Binary files /dev/null and b/resources/media/presskit/logo/logo-large.png differ diff --git a/resources/media/presskit/logo/logo-larger.png b/resources/media/presskit/logo/logo-larger.png new file mode 100644 index 0000000000..3520fb5bd2 Binary files /dev/null and b/resources/media/presskit/logo/logo-larger.png differ diff --git a/resources/media/presskit/logo/logo-medium.png b/resources/media/presskit/logo/logo-medium.png new file mode 100644 index 0000000000..7308f184ee Binary files /dev/null and b/resources/media/presskit/logo/logo-medium.png differ diff --git a/resources/media/presskit/logo/logo-small.png b/resources/media/presskit/logo/logo-small.png new file mode 100644 index 0000000000..bd3ff448b6 Binary files /dev/null and b/resources/media/presskit/logo/logo-small.png differ diff --git a/resources/media/presskit/logo/scalable/logo.svg b/resources/media/presskit/logo/scalable/logo.svg new file mode 100644 index 0000000000..76b5cfb40d --- /dev/null +++ b/resources/media/presskit/logo/scalable/logo.svg @@ -0,0 +1,1208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + nuclear + + diff --git a/resources/media/presskit/screenshots/screenshot-artist-page.png b/resources/media/presskit/screenshots/screenshot-artist-page.png new file mode 100644 index 0000000000..e389591a42 Binary files /dev/null and b/resources/media/presskit/screenshots/screenshot-artist-page.png differ diff --git a/resources/media/presskit/screenshots/screenshot-dashboard.png b/resources/media/presskit/screenshots/screenshot-dashboard.png new file mode 100644 index 0000000000..b46bca0551 Binary files /dev/null and b/resources/media/presskit/screenshots/screenshot-dashboard.png differ diff --git a/resources/media/presskit/screenshots/screenshot-genres.png b/resources/media/presskit/screenshots/screenshot-genres.png new file mode 100644 index 0000000000..2223aa98fd Binary files /dev/null and b/resources/media/presskit/screenshots/screenshot-genres.png differ diff --git a/resources/media/presskit/screenshots/screenshot-settings.png b/resources/media/presskit/screenshots/screenshot-settings.png new file mode 100644 index 0000000000..2a213e63be Binary files /dev/null and b/resources/media/presskit/screenshots/screenshot-settings.png differ diff --git a/server/.eslintrc b/server/.eslintrc new file mode 100644 index 0000000000..abc95623e3 --- /dev/null +++ b/server/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": "../.eslintrc.js", + "env": { + "node": true + } +} \ No newline at end of file diff --git a/server/http/api/index.js b/server/http/api/index.js new file mode 100644 index 0000000000..10f310b143 --- /dev/null +++ b/server/http/api/index.js @@ -0,0 +1,6 @@ +export * from './window'; +export * from './settings'; +export * from './player'; +export * from './swagger'; +export * from './playlist'; +export * from './queue'; diff --git a/server/http/api/player.js b/server/http/api/player.js new file mode 100644 index 0000000000..fc1dd6cbc4 --- /dev/null +++ b/server/http/api/player.js @@ -0,0 +1,116 @@ +import express from 'express'; +import { Validator } from 'express-json-validator-middleware'; +import swagger from 'swagger-spec-express'; + +import { + onNext, + onPrevious, + onPause, + onPlayPause, + onStop, + onPlay, + onVolume, + onSeek, + onMute, + getPlayingStatus +} from '../../mpris'; +import { volumeSchema, seekSchema } from '../schema'; +import { getStandardDescription } from '../lib/swagger'; + + +const { validate } = new Validator({ allErrors: true }); + +export function playerRouter() { + + const router = express.Router(); + + swagger.swaggerize(router); + + router + .get('/now-playing', (req, res, next) => { + getPlayingStatus() + .then(res.json.bind(res)) + .catch(next); + }) + .describe( + getStandardDescription({ + successDescription: 'the status of nuclear player', + tags: ['Player'] + }) + ); + + router + .post('/next', (req, res) => { + onNext(); + res.send(); + }) + .describe(getStandardDescription({ tags: ['Player'] })); + + router + .post('/previous', (req, res) => { + onPrevious(); + res.send(); + }) + .describe(getStandardDescription({ tags: ['Player'] })); + + router + .post('/pause', (req, res) => { + onPause(); + res.send(); + }) + .describe(getStandardDescription({ tags: ['Player'] })); + + router + .post('/play-pause', (req, res) => { + onPlayPause(); + res.send(); + }) + .describe(getStandardDescription({ tags: ['Player'] })); + + router + .post('/stop', (req, res) => { + onStop(); + res.send(); + }) + .describe(getStandardDescription({ tags: ['Player'] })); + + router + .post('/play', (req, res) => { + onPlay(); + res.send(); + }) + .describe(getStandardDescription({ tags: ['Player'] })); + + router + .post('/mute', (req, res) => { + onMute(); + res.send(); + }) + .describe(getStandardDescription({ tags: ['Player'] })); + + router + .post('/volume', validate(volumeSchema), (req, res) => { + onVolume(req.body.value); + res.send(); + }) + .describe( + getStandardDescription({ + tags: ['Player'], + body: ['volumeValue'] + }) + ); + + router + .post('/seek', validate(seekSchema), (req, res) => { + onSeek(req.body.value); + res.send(); + }) + .describe( + getStandardDescription({ + tags: ['Player'], + body: ['seekValue'] + }) + ); + + return router; +} diff --git a/server/http/api/playlist.js b/server/http/api/playlist.js new file mode 100644 index 0000000000..bd844f62c3 --- /dev/null +++ b/server/http/api/playlist.js @@ -0,0 +1,75 @@ +import express from 'express'; +import { Validator } from 'express-json-validator-middleware'; +import swagger from 'swagger-spec-express'; + +import { + onCreatePlaylist, + onRemovePlaylist +} from '../../mpris'; +import { addPlaylistSchema } from '../schema'; +import { getStandardDescription } from '../lib/swagger'; +import { store } from '../../store'; + + +const { validate } = new Validator({ allErrors: true }); + +export function playlistRouter() { + + const router = express.Router(); + + swagger.swaggerize(router); + + router + .post('/', validate(addPlaylistSchema), (req, res) => { + onCreatePlaylist(req.body.name); + res.send(); + }) + .describe( + getStandardDescription({ + successDescription: 'The playlist has been created', + errorDescription: 'The playlist has not been created', + tags: ['Playlist'], + body: ['playlistName'] + }) + ); + + router + .get('/', (req, res, next) => { + try { + const playlists = store.get('playlists'); + + res.json(playlists); + } catch (err) { + next(err); + } + }) + .describe( + getStandardDescription({ + successDescription: 'The list of all playlists', + errorDescription: 'The playlist has not been created', + tags: ['Playlist'] + }) + ); + + router + .delete('/:name', (req, res, next) => { + try { + const playlists = store.get('playlists'); + + store.set('playlists', playlists.filter(({ name }) => name !== req.params.name)); + onRemovePlaylist(); + res.send(); + } catch (err) { + next(err); + } + }) + .describe( + getStandardDescription({ + errorDescription: 'The playlist has not been removed', + tags: ['Playlist'], + path: ['name'] + }) + ); + + return router; +} diff --git a/server/http/api/queue.js b/server/http/api/queue.js new file mode 100644 index 0000000000..0c77e21802 --- /dev/null +++ b/server/http/api/queue.js @@ -0,0 +1,37 @@ +import express from 'express'; +import swagger from 'swagger-spec-express'; + +import { + onEmptyQueue, + getQueue +} from '../../mpris'; +import { getStandardDescription } from '../lib/swagger'; + +export function queueRouter() { + + const router = express.Router(); + + swagger.swaggerize(router); + + router.get('/', (req, res, next) => { + getQueue() + .then(res.json.bind(res)) + .catch(next); + }) + .describe(getStandardDescription({ + successDescription: 'The current queue', + tags: ['Queue'] + })); + + router + .post('/empty', (req, res) => { + onEmptyQueue(); + res.send(); + }) + .describe(getStandardDescription({ + successDescription: 'The queue is now empty', + tags: ['Queue'] + })); + + return router; +} diff --git a/server/http/api/settings.js b/server/http/api/settings.js new file mode 100644 index 0000000000..3d44ba7839 --- /dev/null +++ b/server/http/api/settings.js @@ -0,0 +1,72 @@ +import express from 'express'; +import { Validator } from 'express-json-validator-middleware'; +import swagger from 'swagger-spec-express'; + +import { onSettings } from '../../mpris'; +import { getOption, store } from '../../store'; +import { getSettingsSchema, updateSettingsSchema, RESTRICTED_SETTINGS } from '../schema'; +import settingsParams from '../../../app/constants/settings'; +import { getStandardDescription } from '../lib/swagger'; + +const { validate } = new Validator({ allErrors: true }); + +export function settingsRouter() { + + const router = express.Router(); + + swagger.swaggerize(router); + + router + .get('/', (req, res) => { + const settings = store.get('settings'); + const filteredSettings = settingsParams + .filter(({ name }) => !RESTRICTED_SETTINGS.includes(name)) + .reduce((acc, item) => ({ + ...acc, + [item.name]: settings[item.name] || item.default + }), {}); + + + res.json(filteredSettings); + }) + .describe( + getStandardDescription({ + tags: ['Settings'], + successDescription: 'nuclear\'s settings' + }) + ); + + router + .get( + '/:option', + validate(getSettingsSchema), + (req, res) => { + res.send(getOption(req.params.option)); + } + ) + .describe( + getStandardDescription({ + tags: ['Settings'], + path: ['option'] + }) + ); + + router + .post( + '/:option', + validate(updateSettingsSchema), + (req, res) => { + onSettings({ [req.params.option]: req.body.value }); + res.send(); + } + ) + .describe( + getStandardDescription({ + tags: ['Settings'], + path: ['option'], + body: ['settingsValue'] + }) + ); + + return router; +} diff --git a/server/http/api/swagger.js b/server/http/api/swagger.js new file mode 100644 index 0000000000..fae617b82c --- /dev/null +++ b/server/http/api/swagger.js @@ -0,0 +1,76 @@ +import express from 'express'; +import swagger from 'swagger-spec-express'; +import { getOption } from '../../store'; + +export function swaggerRouter() { + const router = express.Router(); + + router.get('/swagger.json', (req, res) => { + res.json(swagger.json()); + }); + + router.get('/', (req, res) => { + const PORT = getOption('api.port'); + + res.setHeader('Content-Type', 'text/html'); + res.send(` + + + + + Nuclear API docs + + + + + +
+ + + + + + + `); + }); + + return router; +} + diff --git a/server/http/api/window.js b/server/http/api/window.js new file mode 100644 index 0000000000..2e93d3d6a1 --- /dev/null +++ b/server/http/api/window.js @@ -0,0 +1,35 @@ +import express from 'express'; +const { ipcMain } = require('electron'); +import swagger from 'swagger-spec-express'; +import { getStandardDescription } from '../lib/swagger'; + +export function windowRouter() { + + const router = express.Router(); + + swagger.swaggerize(router); + + router + .post('/quit', (req, res) => { + ipcMain.emit('close'); + res.send(); + }) + .describe(getStandardDescription({ tags: ['Window'] })); + + router + .post('/maximize', (req, res) => { + ipcMain.emit('maximize'); + res.send(); + }) + .describe(getStandardDescription({ tags: ['Window'] })); + + router + .post('/minimize', (req, res) => { + ipcMain.emit('minimize'); + res.send(); + }) + .describe(getStandardDescription({ tags: ['Window'] })); + + return router; +} + diff --git a/server/http/lib/swagger.js b/server/http/lib/swagger.js new file mode 100644 index 0000000000..625fef9921 --- /dev/null +++ b/server/http/lib/swagger.js @@ -0,0 +1,102 @@ +import swagger from 'swagger-spec-express'; + +import { volumeSchema, seekSchema, updateSettingsSchema, getSettingsSchema, addPlaylistSchema, deletePlaylistSchema } from '../schema'; + +export function getStandardDescription({ + successDescription = 'Action successfull', + errorDescription = 'Internal server error', + tags, + body, + path +}) { + return { + tags, + common: { + parameters: { + body, + path + } + }, + responses: { + 200: { + description: successDescription + }, + 500: { + description: errorDescription + } + } + }; +} + +export function initSwagger(app) { + swagger.reset(); + swagger.initialise(app, { + title: 'Nuclear REST API', + description: 'This Api allow you to remotly control nuclear desktop app', + tags: [ + { + name: 'Player', + description: 'Player related endpoints (play, pause, volume ...)' + }, + { + name: 'Window', + description: 'Window related endpoints (maximize, close ...)' + }, + { + name: 'Settings', + description: 'Settings related endpoints (update settings ...)' + }, + { + name: 'Playlist', + description: 'Playlist related endpoints (create, clean queue ...)' + }, + { + name: 'Queue', + description: 'Queue related endpoints' + } + ] + }); + + swagger.common.parameters.addBody({ + name: 'settingsValue', + description: 'The value of the property you want to change', + required: true, + schema: updateSettingsSchema.body.properties.value + }); + + swagger.common.parameters.addBody({ + name: 'volumeValue', + description: 'The new volume', + required: true, + schema: volumeSchema.body + }); + + swagger.common.parameters.addBody({ + name: 'seekValue', + description: 'The new position of the seek', + required: true, + schema: seekSchema.body + }); + + swagger.common.parameters.addPath({ + name: 'option', + description: 'The name of the settings you want to get / update', + required: true, + ...getSettingsSchema.params.properties.option + }); + + swagger.common.parameters.addBody({ + name: 'playlistName', + description: 'the name of the new playlist', + required: true, + schema: addPlaylistSchema.body + }); + + swagger.common.parameters.addPath({ + name: 'name', + description: 'The name of the playlist to remove', + required: true, + ...deletePlaylistSchema.params.properties.name + }); +} + diff --git a/server/http/middlewares.js b/server/http/middlewares.js new file mode 100644 index 0000000000..6a2940b1f8 --- /dev/null +++ b/server/http/middlewares.js @@ -0,0 +1,31 @@ +import { ValidationError } from 'express-json-validator-middleware'; + +const getValidationMessage = ({ validationErrors }) => { + if (validationErrors.params) { + const err = validationErrors.params.shift(); + + return `${err.dataPath} ${err.message} ${err.params.allowedValues.toString()}`; + } else { + return `request body ${validationErrors.body.shift().message}`; + } +}; + +export function errorMiddleware(logger) { + return (err, req, res, next) => { + if (err instanceof ValidationError) { + const message = getValidationMessage(err); + + res.status(400).send(message); + next(); + } else { + logger.error(err); + res.status(500).send('Internal Server Error'); + } + }; +} + +export function notFoundMiddleware() { + return (req, res) => { + res.status(404).send('Not Found'); + }; +} diff --git a/server/http/schema.js b/server/http/schema.js new file mode 100644 index 0000000000..9ba07a610d --- /dev/null +++ b/server/http/schema.js @@ -0,0 +1,96 @@ +import settings from '../../app/constants/settings'; + +export const RESTRICTED_SETTINGS = []; +export const READONLY_SETTINGS = []; + +export const getSettingsSchema = { + params: { + type: 'object', + required: ['option'], + properties: { + option: { + type: 'string', + enum: settings + .filter(({ name }) => !RESTRICTED_SETTINGS.includes(name)) + .map(({ name }) => name) + } + } + } +}; + +export const updateSettingsSchema = { + params: { + type: 'object', + required: ['option'], + properties: { + option: { + type: 'string', + enum: settings + .filter(({ name }) => !READONLY_SETTINGS.includes(name)) + .filter(({ name }) => !RESTRICTED_SETTINGS.includes(name)) + .map(({ name }) => name) + } + } + }, + body: { + type: 'object', + required: ['value'], + properties: { + value: { + type: ['string', 'boolean', 'number'] + } + } + } +}; + +export const volumeSchema = { + body: { + type: 'object', + required: ['value'], + properties: { + value: { + type: 'number', + minimum: 0, + maximum: 100 + } + } + } +}; + +export const seekSchema = { + body: { + type: 'object', + required: ['value'], + properties: { + value: { + type: 'number', + minimum: 0 + } + } + } +}; + +export const addPlaylistSchema = { + body: { + type: 'object', + required: ['name'], + properties: { + name: { + type: 'string', + maxLength: 100 + } + } + } +}; + +export const deletePlaylistSchema = { + params: { + type: 'object', + required: ['option'], + properties: { + name: { + type: 'string' + } + } + } +}; diff --git a/server/http/server.js b/server/http/server.js new file mode 100644 index 0000000000..3e03be3cb5 --- /dev/null +++ b/server/http/server.js @@ -0,0 +1,63 @@ +import Logger from 'electron-timber'; +import express from 'express'; +import bodyParser from 'body-parser'; +import cors from 'cors'; +import swagger from 'swagger-spec-express'; + +import { + windowRouter, + playerRouter, + settingsRouter, + swaggerRouter, + playlistRouter, + queueRouter +} from './api'; +import { errorMiddleware, notFoundMiddleware } from './middlewares'; +import { initSwagger } from './lib/swagger'; + +function runHttpServer({ + log, + port, + host = '0.0.0.0', + prefix = '/nuclear' +}) { + const app = express(); + const logger = log + ? Logger.create({ name: 'http api' }) + : { log: () => {}, error: () => {} }; + + initSwagger(app); + + return app + .use(cors()) + .use(bodyParser.urlencoded({ extended: false })) + .use(bodyParser.json()) + .use(`${prefix}/window`, windowRouter()) + .use(`${prefix}/player`, playerRouter()) + .use(`${prefix}/settings`, settingsRouter()) + .use(`${prefix}/docs`, swaggerRouter()) + .use(`${prefix}/playlist`, playlistRouter()) + .use(`${prefix}/queue`, queueRouter()) + .use(notFoundMiddleware()) + .use(errorMiddleware(logger)) + .listen(port, host, err => { + if (err) { + logger.error(err); + } else { + swagger.compile(); + logger.log(`nuclear api available on port ${port}`); + } + }); +} + +function closeHttpServer(app) { + return new Promise(resolve => { + if (app && app.listening) { + app.close(resolve); + } else { + resolve(); + } + }); +} + +module.exports = { runHttpServer, closeHttpServer }; diff --git a/server/main.dev.js b/server/main.dev.js new file mode 100644 index 0000000000..83c5fd9a1a --- /dev/null +++ b/server/main.dev.js @@ -0,0 +1,145 @@ +// const { +// default: installExtension, +// REACT_DEVELOPER_TOOLS, +// REDUX_DEVTOOLS +// } = require('electron-devtools-installer'); +const { + app, + ipcMain, + nativeImage, + BrowserWindow, + Menu, + Tray +} = require('electron'); +const platform = require('electron-platform'); +const path = require('path'); +const url = require('url'); +const { getOption, setOption } = require('./store'); +const { runHttpServer, closeHttpServer } = require('./http/server'); + +let httpServer; +let win; +let tray; +let icon = nativeImage.createFromPath( + path.resolve(__dirname, 'resources', 'media', 'icon.png') +); + +function changeWindowTitle (artist, title) { + win.setTitle(`${artist} - ${title} - Nuclear Music Player`); +} + +function createWindow () { + win = new BrowserWindow({ + width: 1366, + height: 768, + frame: !getOption('framelessWindow'), + icon: icon, + show: false, + webPreferences: { + experimentalFeatures: true, + webSecurity: false + }, + additionalArguments: [ + getOption('disableGPU') && '--disable-gpu' + ] + }); + + win.setTitle('Nuclear Music Player'); + + // Needs to be commented for now + // https://github.com/electron/electron/issues/13008 + // installExtension(REACT_DEVELOPER_TOOLS) + // .then((name) => console.log(`Added Extension: ${name}`)) + // .catch((err) => console.log('An error occurred: ', err)); + + // installExtension(REDUX_DEVTOOLS) + // .then((name) => console.log(`Added Extension: ${name}`)) + // .catch((err) => console.log('An error occurred: ', err)); + + win.loadURL( + url.format({ + pathname: 'localhost:8080', + protocol: 'http:', + slashes: true + }) + ); + + win.once('ready-to-show', () => { + win.show(); + }); + + win.webContents.openDevTools(); + + win.on('closed', () => { + win = null; + }); + + // MacOS specific + if (platform.isDarwin) { + app.dock.setIcon(icon); + icon = nativeImage.createFromPath( + path.resolve(__dirname, 'resources', 'media', 'icon_apple.png') + ); + } + + const trayMenu = Menu.buildFromTemplate([ + { + label: 'Quit', + type: 'normal', + click: () => { + app.quit(); + } + } + ]); + + tray = new Tray(icon); + tray.setTitle('Nuclear Music Player'); + tray.setToolTip('Nuclear Music Player'); + tray.setContextMenu(trayMenu); + + ipcMain.on('close', () => { + closeHttpServer(httpServer).then(() => app.quit()); + }); + + ipcMain.on('minimize', () => { + win.minimize(); + }); + + ipcMain.on('maximize', () => { + if (platform.isDarwin) { + win.isFullScreen() ? win.setFullScreen(false) : win.setFullScreen(true); + } else { + win.isMaximized() ? win.unmaximize() : win.maximize(); + } + }); + + ipcMain.on('songChange', (event, arg) => { + if (arg === null) { + return; + } + changeWindowTitle(arg.artist, arg.name); + }); + + ipcMain.on('restart-api', () => { + closeHttpServer(httpServer).then(() => { + httpServer = runHttpServer({ log: true, port: getOption('api.port') }); + }); + }); + + ipcMain.on('stop-api', () => { + closeHttpServer(httpServer); + }); +} + +app.on('ready', () => { + createWindow(); + + if (getOption('api.enabled')) { + setOption('api.port', 3000); + httpServer = runHttpServer({ log: true, port: 3000 }); + } +}); + +app.on('window-all-closed', () => { + closeHttpServer(httpServer).then(() => app.quit()); +}); diff --git a/server/main.dev.linux.js b/server/main.dev.linux.js new file mode 100644 index 0000000000..7148b4ba9c --- /dev/null +++ b/server/main.dev.linux.js @@ -0,0 +1,285 @@ +import logger from 'electron-timber'; +import { setOption } from './store'; +// const { default: installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } = require('electron-devtools-installer'); +const { app, ipcMain, nativeImage, BrowserWindow, Menu, Tray } = require('electron'); +const platform = require('electron-platform'); +const path = require('path'); +const url = require('url'); +const getOption = require('./store').getOption; +const { runHttpServer, closeHttpServer } = require('./http/server'); +const mpris = require('./mpris'); +var Player; + +// GNU/Linux-specific +if (!platform.isDarwin && !platform.isWin32) { + Player = require('mpris-service'); +} + +let win; +let httpServer; +let tray; +let icon = nativeImage.createFromPath(path.resolve(__dirname, 'resources', 'media', 'icon.png')); + +function changeWindowTitle(artist, title) { + win.setTitle(`${artist} - ${title} - nuclear music player`); +} + +function createWindow() { + logger.log('Electron is ready, creating a window'); + win = new BrowserWindow({ + width: 1366, + height: 768, + frame: !getOption('framelessWindow'), + icon: icon, + show: false, + webPreferences: { + experimentalFeatures: true, + webSecurity: false + }, + additionalArguments: [ + getOption('disableGPU') && '--disable-gpu' + ] + }); + + win.setTitle('nuclear music player'); + + // Needs to be commented for now + // https://github.com/electron/electron/issues/13008 + // installExtension(REACT_DEVELOPER_TOOLS) + // .then((name) => console.log(`Added Extension: ${name}`)) + // .catch((err) => console.log('An error occurred: ', err)); + + // installExtension(REDUX_DEVTOOLS) + // .then((name) => console.log(`Added Extension: ${name}`)) + // .catch((err) => console.log('An error occurred: ', err)); + + win.loadURL(url.format({ + pathname: 'localhost:8080', + protocol: 'http:', + slashes: true + })); + + win.once('ready-to-show', () => { + win.show(); + }); + + win.webContents.openDevTools(); + + win.on('closed', () => { + win = null; + }); + + + // MacOS specific + if (platform.isDarwin) { + app.dock.setIcon(icon); + icon = nativeImage.createFromPath(path.resolve(__dirname, 'resources', 'media', 'icon_apple.png')); + } + + const trayMenu = Menu.buildFromTemplate([ + {label: 'Quit', type: 'normal', click: + () => { + closeHttpServer(httpServer).then(() => app.quit()); + } + } + ]); + + tray = new Tray(icon); + tray.setTitle('nuclear music player'); + tray.setToolTip('nuclear music player'); + tray.setContextMenu(trayMenu); + + ipcMain.on('close', () => { + logger.log('Received a close message from ipc, quitting'); + app.quit(); + }); + + ipcMain.on('minimize', () => { + win.minimize(); + }); + + ipcMain.on('maximize', () => { + win.isMaximized() ? win.unmaximize() : win.maximize(); + }); + + ipcMain.on('restart-api', () => { + closeHttpServer(httpServer).then(() => { + httpServer = runHttpServer({ log: true, port: getOption('api.port') }); + }); + }); + + ipcMain.on('stop-api', () => { + closeHttpServer(httpServer); + }); + + // GNU/Linux-specific + if (!platform.isDarwin && !platform.isWin32) { + let hashCode = function(str) { + str = str.toString(); + let hash = 0; + if (str.length == 0) { + return hash; + } + for (var i = 0; i < str.length; i++) { + var char = str.charCodeAt(i); + hash = ((hash<<5)-hash)+char; + hash = hash & hash; // Convert to 32bit integer + } + return hash; + } + + let secToUs = function(sec) { + return Math.floor(Number(sec) * 1e6); + } + + let positionSec = 0.0; + + let player = Player({ + name: 'nuclear', + identity: 'nuclear music player', + supportedUriSchemes: ['file'], + supportedMimeTypes: ['audio/mpeg', 'application/ogg'], + supportedInterfaces: ['player'], + desktopEntry: 'nuclear' + }); + + if (getOption('loopAfterQueueEnd')) { + player.loopStatus = 'Track'; + } else { + player.loopStatus = 'None'; + } + + player.shuffle = getOption('shuffleQueue'); + + player.volume = 1.0; + + player.getPosition = function() { + return secToUs(positionSec); + }; + + player.on('quit', function () { + win = null; + }); + + player.on('next', mpris.onNext); + player.on('previous', mpris.onPrevious); + player.on('pause', mpris.onPause); + player.on('playpause', mpris.onPlayPause); + player.on('stop', mpris.onStop); + player.on('play', mpris.onPlay); + player.on('volume', function(volume) { + mpris.onVolume(volume * 100); + }); + player.on('position', function(e) { + let {trackId, position} = e; + if (player.metadata && player.metadata['mpris:trackid'] === trackId) { + mpris.onSeek(position / 1e3); + } + }); + player.on('seek', function(seek) { + let seekTo = (positionSec * 1e3) + (seek / 1e3); + mpris.onSeek(seekTo); + }); + player.on('shuffle', function(shuffle) { + mpris.onSettings({shuffleQueue: shuffle}); + }); + player.on('loopStatus', function(status) { + if (status === 'None') { + mpris.onSettings({ loopAfterQueueEnd: false}); + } else if (status === 'Track') { + mpris.onSettings({loopAfterQueueEnd: true}); + } else { + // XXX 'Playlist' loop status is not supported, just do the closest + // thing. + mpris.onSettings({loopAfterQueueEnd: true}); + } + }); + + let lastId = null; + ipcMain.on('songChange', (event, arg) => { + if (arg === null) { + return; + } + + changeWindowTitle(arg.artist, arg.name); + + if (arg.streams && arg.streams.length > 0) { + let id = arg.streams[0].id; + if (id !== lastId) { + lastId = id; + let metadata = { + 'mpris:trackid': player.objectPath(`track/${Math.abs(hashCode(id))}`), + 'mpris:artUrl': arg.thumbnail || '', + 'xesam:title': arg.name || '', + 'xesam:artist': arg.artist || '' + }; + if (arg.streams[0].source === 'Youtube') { + metadata['mpris:length'] = secToUs(Number(arg.streams[0].duration)); + } else { + // XXX: Soundcloud is in ms, and I think this is reasonble, but I + // don't know what other duration formats to expect here. + metadata['mpris:length'] = Math.floor(Number(arg.streams[0].duration) * 1e3); + } + player.positionSec = 0; + player.metadata = metadata; + } + } + }); + + ipcMain.on('play', (event, arg) => { + player.playbackStatus = 'Playing'; + }); + + ipcMain.on('paused', (event, arg) => { + player.playbackStatus = 'Paused'; + }); + + ipcMain.on('volume', (event, volume) => { + player.volume = volume / 100; + }); + + ipcMain.on('playbackProgress', (event, progress) => { + positionSec = progress; + }); + + ipcMain.on('seek', (event, seek) => { + // this is in miliseconds + player.positionSec = seek / 1e3; + player.seeked(Math.floor(seek * 1e3)); + }); + + ipcMain.on('set-option', (event, kv) => { + let {key, value} = kv; + if (key === 'loopAfterQueueEnd') { + if (value) { + player.loopStatus = 'Track'; + } else { + player.loopStatus = 'None'; + } + } else if (key === 'shuffleQueue') { + player.shuffle = value; + } + }); + } else { + ipcMain.on('songChange', (event, arg) => { + if (arg === null) { + return; + } + changeWindowTitle(arg.artist, arg.name); + }); + } +} + +app.on('ready', () => { + createWindow(); + + if (getOption('api.enabled')) { + setOption('api.port', 3000); + httpServer = runHttpServer({ log: true, port: 3000 }); + } +}); + +app.on('window-all-closed', () => { + logger.log('All windows closed, quitting'); + closeHttpServer(httpServer).then(() => app.quit()); +}); diff --git a/server/main.prod.js b/server/main.prod.js new file mode 100644 index 0000000000..854a921a57 --- /dev/null +++ b/server/main.prod.js @@ -0,0 +1,108 @@ +require('babel-polyfill'); +const { app, ipcMain, nativeImage, BrowserWindow, Menu, Tray } = require('electron'); +const platform = require('electron-platform'); +const path = require('path'); +const url = require('url'); +const getOption = require('./store').getOption; +const { runHttpServer, closeHttpServer } = require('./http/server'); + +let httpServer; +let win; +let tray; +let icon = nativeImage.createFromPath(path.resolve(__dirname, 'resources', 'media', 'icon.png')); + +function changeWindowTitle(artist, title) { + win.setTitle(`${artist} - ${title} - Nuclear Music Player`); +} + +function createWindow() { + win = new BrowserWindow({ + width: 1366, + height: 768, + frame: !getOption('framelessWindow'), + icon: icon, + show: false, + webPreferences: { + experimentalFeatures: true, + webSecurity: false + }, + additionalArguments: [ + getOption('disableGPU') && '--disable-gpu' + ] + }); + + win.setTitle('Nuclear Music Player'); + + win.loadURL(url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true + })); + + win.once('ready-to-show', () => { + win.show(); + }); + + win.on('closed', () => { + win = null; + }); + + // MacOS specific + if (platform.isDarwin) { + app.dock.setIcon(icon); + icon = nativeImage.createFromPath(path.resolve(__dirname, 'resources', 'media', 'icon_apple.png')); + } + + const trayMenu = Menu.buildFromTemplate([ + {label: 'Quit', type: 'normal', click: + () => { + closeHttpServer(httpServer).then(() => app.quit()); + } + } + ]); + + tray = new Tray(icon); + tray.setTitle('Nuclear Music Player'); + tray.setToolTip('Nuclear Music Player'); + tray.setContextMenu(trayMenu); + + ipcMain.on('close', () => { + closeHttpServer(httpServer).then(() => app.quit()); + }); + + ipcMain.on('minimize', () => { + win.minimize(); + }); + + ipcMain.on('maximize', () => { + win.isMaximized() ? win.unmaximize() : win.maximize(); + }); + + ipcMain.on('songChange', (event, arg) => { + if (arg === null) { + return; + } + changeWindowTitle(arg.artist, arg.name); + }); + + ipcMain.on('restart-api', () => { + closeHttpServer(httpServer).then(() => { + httpServer = runHttpServer({ port: getOption('api.port') }); + }); + }); + + ipcMain.on('stop-api', () => { + closeHttpServer(httpServer); + }); +} + +app.on('ready', () => { + createWindow(); + if (getOption('api.enabled')) { + httpServer = runHttpServer({ port: getOption('api.port') }); + } +}); + +app.on('window-all-closed', () => { + closeHttpServer(httpServer).then(() => app.quit()); +}); diff --git a/server/main.prod.linux.js b/server/main.prod.linux.js new file mode 100644 index 0000000000..6c96c4d8e3 --- /dev/null +++ b/server/main.prod.linux.js @@ -0,0 +1,102 @@ +require('babel-polyfill'); +const { app, ipcMain, nativeImage, BrowserWindow, Menu, Tray } = require('electron'); +const platform = require('electron-platform'); +const path = require('path'); +const url = require('url'); +// const mpris = require('./mpris'); +const getOption = require('./store').getOption; +const { runHttpServer, closeHttpServer } = require('./http/server'); + +let httpServer; +let win; +let tray; +let icon = nativeImage.createFromPath(path.resolve(__dirname, 'resources', 'media', 'icon.png')); + +// function changeWindowTitle(artist, title) { +// win.setTitle(`${artist} - ${title} - nuclear music player`); +// } + +function createWindow() { + win = new BrowserWindow({ + width: 1366, + height: 768, + frame: !getOption('framelessWindow'), + icon: icon, + show: false, + webPreferences: { + experimentalFeatures: true, + webSecurity: false + }, + additionalArguments: [ + getOption('disableGPU') && '--disable-gpu' + ] + }); + + win.setTitle('nuclear music player'); + + win.loadURL(url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true + })); + + win.once('ready-to-show', () => { + win.show(); + }); + + win.on('closed', () => { + win = null; + }); + + // MacOS specific + if (platform.isDarwin) { + app.dock.setIcon(icon); + icon = nativeImage.createFromPath(path.resolve(__dirname, 'resources', 'media', 'icon_apple.png')); + } + + const trayMenu = Menu.buildFromTemplate([ + {label: 'Quit', type: 'normal', click: + () => { + closeHttpServer(httpServer).then(() => app.quit()); + } + } + ]); + + tray = new Tray(icon); + tray.setTitle('nuclear music player'); + tray.setToolTip('nuclear music player'); + tray.setContextMenu(trayMenu); + + ipcMain.on('close', () => { + closeHttpServer(httpServer).then(() => app.quit()); + }); + + ipcMain.on('minimize', () => { + win.minimize(); + }); + + ipcMain.on('maximize', () => { + win.isMaximized() ? win.unmaximize() : win.maximize(); + }); + + ipcMain.on('restart-api', () => { + closeHttpServer(httpServer).then(() => { + httpServer = runHttpServer({ port: getOption('api.port') }); + }); + }); + + ipcMain.on('stop-api', () => { + closeHttpServer(httpServer); + }); +} + +app.on('ready', () => { + createWindow(); + if (getOption('api.enabled')) { + httpServer = runHttpServer({ port: getOption('api.port') }); + } +}); + +app.on('window-all-closed', () => { + closeHttpServer(httpServer).then(() => app.quit()); +}); diff --git a/server/mpris.js b/server/mpris.js new file mode 100644 index 0000000000..d1df69119d --- /dev/null +++ b/server/mpris.js @@ -0,0 +1,100 @@ +import logger from 'electron-timber'; +import { ipcMain } from 'electron'; + +let rendererWindow = null; + +// const events = ['raise', 'quit', 'next', 'previous', 'pause', 'playpause', 'stop', 'play', 'seek', 'position', 'open', 'volume', 'settings']; + +ipcMain.on('started', event => { + logger.log('Renderer process started and registered.'); + rendererWindow = event.sender; +}); + + +function onNext() { + rendererWindow.send('next'); +} + +function onPrevious() { + rendererWindow.send('previous'); +} + +function onPause() { + rendererWindow.send('pause'); +} + +function onPlayPause() { + rendererWindow.send('playpause'); +} + +function onStop() { + rendererWindow.send('stop'); +} + +function onPlay() { + rendererWindow.send('play'); +} + +function onVolume(volume) { + rendererWindow.send('volume', volume); +} + +function onSeek(position) { + rendererWindow.send('seek', position); +} + +function onSettings(settings) { + rendererWindow.send('settings', settings); +} + +function onMute() { + rendererWindow.send('mute'); +} + +function onEmptyQueue() { + rendererWindow.send('empty-queue'); +} + +function onCreatePlaylist(name) { + rendererWindow.send('create-playlist', name); +} + +function onRemovePlaylist() { + rendererWindow.send('refresh-playlists'); +} + +function getQueue() { + return new Promise(resolve => { + rendererWindow.send('queue'); + ipcMain.on('queue', (evt, data) => { + resolve(data); + }); + }); +} + +function getPlayingStatus() { + return new Promise(resolve => { + rendererWindow.send('playing-status'); + ipcMain.on('playing-status', (evt, data) => { + resolve(data); + }); + }); +} + +module.exports = { + onNext, + onPrevious, + onPause, + onPlayPause, + onStop, + onPlay, + onSettings, + onVolume, + onSeek, + onMute, + onEmptyQueue, + getQueue, + onCreatePlaylist, + onRemovePlaylist, + getPlayingStatus +}; diff --git a/server/store.js b/server/store.js new file mode 100644 index 0000000000..804b86dc50 --- /dev/null +++ b/server/store.js @@ -0,0 +1,26 @@ +import logger from 'electron-timber'; +import electronStore from 'electron-store'; +import _ from 'lodash'; + +import options from '../app/constants/settings'; + +const store = new electronStore(); +logger.log(`Initialized settings store at ${store.path}`); + +function getOption (key) { + let settings = store.get('settings') || {}; + let value = settings[key]; + if (typeof value === 'undefined') { + value = _.find(options, { name: key }).default; + } + + return value; +} + +function setOption (key, value) { + const settings = store.get('settings') || {}; + + store.set('settings', Object.assign({}, settings, { [`${key}`]: value })); +} + +export { getOption, setOption, store }; diff --git a/test/enzyme.albumcover.test.js b/test/enzyme.albumcover.test.js new file mode 100644 index 0000000000..ec0d096e9a --- /dev/null +++ b/test/enzyme.albumcover.test.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { expect } from 'chai'; +import { shallow } from 'enzyme'; +import AlbumCover from '../app/components/AlbumCover'; + +describe('', () => { + it('renders an album cover', () => { + const wrapper = shallow( + + ); + + expect(wrapper.containsMatchingElement( +
+ )); + }); +}); diff --git a/test/enzyme.header.test.js b/test/enzyme.header.test.js new file mode 100644 index 0000000000..7eea819cd7 --- /dev/null +++ b/test/enzyme.header.test.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { expect } from 'chai'; +import { shallow } from 'enzyme'; +import Header from '../app/components/Header'; + +describe('
', () => { + it('renders a header with no children', () => { + const wrapper = shallow(
); + expect(wrapper.containsMatchingElement( +
+ )); + }); + + it('renders a header with children', () => { + const wrapper = shallow(
test
); + expect(wrapper.containsMatchingElement( +
+ )); + }); +}); diff --git a/test/pitchfork.test.js b/test/pitchfork.test.js new file mode 100644 index 0000000000..839d2a8d06 --- /dev/null +++ b/test/pitchfork.test.js @@ -0,0 +1,50 @@ +import { + getBestNewAlbums, + getBestNewTracks +} from 'pitchfork-bnm'; +import { expect } from 'chai'; + +describe('Pitchfork API tests', () => { + it('gets best new albums', async () => { + const result = await getBestNewAlbums(). + then(albums => { + return albums; + }) + .catch(error => { + console.error(error); + expect(false).to.equal(true); + }); + + expect(result).to.be.an('array'); + result.forEach(entry => { + expect(entry).to.be.an('object').that.has.all.keys('thumbnail', + 'artist', + 'title', + 'reviewUrl', + 'genres', + 'score', + 'abstract', + 'review'); + }); + }); + + it('gets best new tracks', async () => { + const result = await getBestNewTracks(). + then(tracks => { + return tracks; + }) + .catch(error => { + console.error(error); + expect(false).to.equal(true); + }); + + expect(result).to.be.an('array'); + result.forEach(entry => { + expect(entry).to.be.an('object').that.has.all.keys('thumbnail', + 'artist', + 'title', + 'reviewUrl', + 'review'); + }); + }); +}); diff --git a/test/setup.js b/test/setup.js new file mode 100644 index 0000000000..b8ce69bf18 --- /dev/null +++ b/test/setup.js @@ -0,0 +1,26 @@ +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import { JSDOM } from 'jsdom'; + +Enzyme.configure({ adapter: new Adapter() }); + +const jsdom = new JSDOM(''); + +const { window } = jsdom; + +function copyProps(src, target) { + const props = Object.getOwnPropertyNames(src) + .filter(prop => typeof target[prop] === 'undefined') + .reduce((result, prop) => ({ + ...result, + [prop]: Object.getOwnPropertyDescriptor(src, prop), + }), {}); + Object.defineProperties(target, props); +} + +global.window = window; +global.document = window.document; +global.navigator = { + userAgent: 'node.js', +}; +copyProps(window, global); diff --git a/test/soundcloud.test.js b/test/soundcloud.test.js new file mode 100644 index 0000000000..e51e3442b2 --- /dev/null +++ b/test/soundcloud.test.js @@ -0,0 +1,15 @@ +import { soundcloudSearch } from '../app/rest/Soundcloud'; +import { expect } from 'chai'; + +describe('Soundcloud REST API tests', () => { + it('performs a basic search', () => { + soundcloudSearch('death grips - get got') + .then(data => data.json()) + .then(results => { + expect(results[0]).to.be.an('object').that.includes.all.keys('id', 'title', 'duration', 'stream_url'); + }) + .catch(err => { + console.error(err); + }); + }); +}); diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000000..2ab1dfcb32 --- /dev/null +++ b/test/test.js @@ -0,0 +1,133 @@ +require('isomorphic-fetch'); +import assert from 'assert'; +import { expect } from 'chai'; +import core from 'nuclear-core'; +import globals from '../app/globals'; + +var billboard = require('../app/rest/Billboard'); +var lastfm = new core.LastFmApi(globals.lastfmApiKey, globals.lastfmApiSecret); + +describe('Billboard api tests', () => { + it('tests exports', () => { + expect(billboard).to.be.an('object'); + expect(billboard).to.have.property('getTop'); + expect(billboard).to.have.property('lists'); + }); + + it('gets a pop songs list', () => { + billboard.getTop(billboard.lists.genres[0].link) + .then(songs => { + expect(songs).to.be.an('array').that.has.lengthOf(40); + }) + .catch(err => { + console.error(err); + }); + }); +}); + +describe('Last.fm api tests', () => { + it('tests exports', () => { + expect(lastfm).to.be.an('object'); + }); + + it('tests getting top tags', () => { + lastfm.getTopTags() + .then(response => response.json()) + .then(results => { + expect(results).to.be.an('object').that.has.nested.property('toptags.tag'); + var sample = results.toptags.tag[0]; + expect(sample).to.be.an('object').that.has.all.keys('name', 'count', 'reach'); + }); + }); + + it('tests getting tag info', () => { + lastfm.getTagInfo('indie') + .then(response => response.json()) + .then(results => { + expect(results).to.be.an('object').that.has.property('tag'); + expect(results.tag).to.be.an('object').that.has.all.keys( + 'name', + 'total', + 'reach', + 'wiki' + ); + }) + .catch(err => { + console.error(err); + }); + }); + + it('tests getting top tag tracks', () => { + lastfm.getTagTracks('indie') + .then(response => response.json()) + .then(results => { + expect(results).to.be.an('object').that.has.nested.property('tracks.track'); + var sample = results.tracks.track[0]; + expect(sample).to.be.an('object').that.has.all.keys( + 'name', + 'artist', + 'duration', + 'streamable', + 'mbid', + 'url', + 'image', + '@attr' + ); + }) + .catch(err => { + console.error(err); + }); + }); + + it('tests getting top tag albums', () => { + lastfm.getTagAlbums('indie') + .then(response => response.json()) + .then(results => { + expect(results).to.be.an('object').that.has.nested.property('albums.album'); + var sample = results.albums.album[0]; + expect(sample).to.be.an('object').that.has.all.keys( + 'name', + 'mbid', + 'url', + 'artist', + 'image', + '@attr' + ); + }) + .catch(err => { + console.error(err); + }); + }); + + it('tests getting top tag artists', () => { + lastfm.getTagArtists('indie') + .then(response => response.json()) + .then(results => { + expect(results).to.be.an('object').that.has.nested.property('topartists.artist'); + var sample = results.topartists.artist[0]; + expect(sample).to.be.an('object').that.has.all.keys( + 'name', + 'mbid', + 'url', + 'streamable', + 'image', + '@attr' + ); + }) + .catch(err => { + console.error(err); + }); + }); + + it('tests getting similar tags', () => { + lastfm.getSimilarTags('electronic') + .then(response => response.json()) + .then(results => { + expect(results).to.be.an('object').that.has.property('similartags'); + }) + .catch(err => { + console.error(err); + }); + }); + +}); diff --git a/webpack.config.dev.js b/webpack.config.dev.js index 48aa4c5790..d6d82c1ea5 100644 --- a/webpack.config.dev.js +++ b/webpack.config.dev.js @@ -1,14 +1,19 @@ const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const HappyPack = require('happypack'); const path = require('path'); -const GoogleFontsPlugin = require("google-fonts-webpack-plugin"); const BUILD_DIR = path.resolve(__dirname, 'dist'); const APP_DIR = path.resolve(__dirname, 'app'); const RESOURCES_DIR = path.resolve(__dirname, 'resources'); const config = { + externals: { + bindings: 'require("bindings")', + 'abstract-socket': 'require("abstract-socket")' + }, entry: [ - "react-hot-loader/patch", + 'react-hot-loader/patch', 'webpack-dev-server/client?http://localhost:8080', 'webpack/hot/only-dev-server', path.resolve(APP_DIR, 'index.js') @@ -16,45 +21,65 @@ const config = { output: { path: BUILD_DIR, filename: 'bundle.js', - publicPath: 'http://localhost:8080/' + publicPath: '/' }, devServer: { hot: true, - contentBase: BUILD_DIR, - publicPath: 'http://localhost:8080/' + contentBase: '/', + publicPath: '/' + }, + mode: 'development', + devtool: 'source-map', + optimization: { + namedModules: true }, node: { - fs: "empty" + fs: 'empty' }, module: { - loaders: [ + rules: [ { - test: /\.jsx?/, - loader: 'babel-loader', + test: /.jsx?$/, + use: 'happypack/loader?id=jsx', include: APP_DIR - }, { + }, + { + test: /.scss$/, + use: 'happypack/loader?id=scss' + }, + { test: /\.css/, loader: 'style-loader!css-loader?modules=true&localIdentName=[name]__[local]___[hash:base64:5]' - }, { - test: /\.scss$/, - loader: 'style-loader!css-loader?importLoaders=1&modules&localIdentName=[local]!sass-loader' }, { test: /\.(png|jpg|gif)$/, - loader: 'file-loader', + loader: 'url-loader', include: RESOURCES_DIR } ] }, plugins: [ new webpack.HotModuleReplacementPlugin(), - new webpack.NamedModulesPlugin(), - new GoogleFontsPlugin({ - fonts: [ - { - family: 'lato', - variants: ['regular', '300', '700'] - } - ] + new HtmlWebpackPlugin({ + template: 'index.html', + minify: { + html5: true, + removeComments: true, + collapseWhitespace: true, + removeRedundantAttributes: true, + useShortDoctype: true, + removeEmptyAttributes: true, + removeStyleLinkTypeAttributes: true, + keepClosingSlash: true + }, + inject: true + }), + new HappyPack({ + id: 'jsx', + loaders: ['babel-loader'] + }), + new HappyPack({ + id: 'scss', + loaders: ['style-loader!css-loader?importLoaders=1&modules&localIdentName=[local]!sass-loader'] }) ], target: 'electron-renderer' diff --git a/webpack.config.electron.js b/webpack.config.electron.js new file mode 100644 index 0000000000..239ebf1ac8 --- /dev/null +++ b/webpack.config.electron.js @@ -0,0 +1,51 @@ +/* eslint-env node */ +const webpack = require('webpack'); +const HappyPack = require('happypack'); +const path = require('path'); + +module.exports = env => { + const entry = env && env.LINUX ? './server/main.dev.linux.js' : './server/main.dev.js'; + + return { + externals: { + bindings: 'require("bindings")', + 'abstract-socket': 'require("abstract-socket")' + }, + entry: entry, + resolve: { + alias: { + jsbi: path.resolve(__dirname, 'node_modules', 'jsbi', 'dist', 'jsbi-cjs.js') + } + }, + output: { + path: __dirname, + filename: 'bundle.electron.js' + }, + mode: 'development', + stats: { + warningsFilter: 'express' + }, + module: { + rules: [ + { + test: /.jsx?$/, + use: 'happypack/loader?id=jsx', + exclude: /node_modules/ + } + ] + }, + plugins: [ + new webpack.NamedModulesPlugin(), + new HappyPack({ + id: 'jsx', + loaders: ['babel-loader'] + }) + ], + node: { + fs: 'empty', + __dirname: false, + __filename: false + }, + target: 'electron-main' + }; +}; diff --git a/webpack.config.electron.prod.js b/webpack.config.electron.prod.js new file mode 100644 index 0000000000..d51ed22164 --- /dev/null +++ b/webpack.config.electron.prod.js @@ -0,0 +1,48 @@ +/* eslint-env node */ +const HappyPack = require('happypack'); + +module.exports = env => { + let entry = env && env.LINUX ? './server/main.prod.linux.js' : './server/main.prod.js'; + + return { + externals: { + bindings: 'require("bindings")', + 'abstract-socket': 'require("abstract-socket")' + }, + entry: entry, + output: { + path: __dirname, + filename: './dist/bundle.electron.js' + }, + mode: 'production', + optimization: { + namedModules: true + }, + stats: { + warningsFilter: 'express' + }, + module: { + rules: [ + { + test: /.jsx?$/, + use: 'happypack/loader?id=jsx' + } + ] + }, + plugins: [ + new HappyPack({ + id: 'jsx', + loaders: ['babel-loader'] + }) + ], + externals: { + dbus: 'dbus' + }, + node: { + fs: 'empty', + __dirname: false, + __filename: false + }, + target: 'electron-main' + }; +}; diff --git a/webpack.config.prod.js b/webpack.config.prod.js index b67afc00cc..e1bf6afce3 100644 --- a/webpack.config.prod.js +++ b/webpack.config.prod.js @@ -1,52 +1,96 @@ const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path'); -const GoogleFontsPlugin = require("google-fonts-webpack-plugin"); +const HappyPack = require('happypack'); const BUILD_DIR = path.resolve(__dirname, 'dist'); const APP_DIR = path.resolve(__dirname, 'app'); const RESOURCES_DIR = path.resolve(__dirname, 'resources'); const config = { + externals: { + bindings: 'require("bindings")', + 'abstract-socket': 'require("abstract-socket")' + }, entry: path.resolve(APP_DIR, 'index.js'), output: { path: BUILD_DIR, - filename: 'bundle.js', - publicPath: BUILD_DIR + filename: 'bundle.js' }, node: { fs: "empty" }, + mode: 'production', + devtool: 'source-map', + optimization: { + namedModules: true, + splitChunks: { + chunks: 'all', + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + priority: -10 + }, + default: { + minChunks: 2, + priority: -20, + reuseExistingChunk: true + } + } + } + }, module: { - loaders: [ + rules: [ + { + test: /.jsx?$/, + use: 'happypack/loader?id=jsx', + exclude: /node_modules\/electron\-timber\/preload\.js/ + }, { - test: /\.jsx?/, - loader: 'babel-loader', - exclude: /node_modules/ + test: /.node$/, + use: 'node-loader' + }, + { + test: /.scss$/, + use: 'happypack/loader?id=scss' }, { test: /\.css/, loader: 'style-loader!css-loader?modules=true&localIdentName=[name]__[local]___[hash:base64:5]' - }, { - test: /\.scss$/, - loader: 'style-loader!css-loader?importLoaders=1&modules&localIdentName=[local]!sass-loader' - }, { + }, { test: /\.(png|jpg|gif)$/, - loader: 'file-loader', + loader: 'url-loader', include: RESOURCES_DIR } ] }, plugins: [ - new webpack.NamedModulesPlugin(), - new GoogleFontsPlugin({ - fonts: [ - { - family: 'lato', - variants: ['regular', '300', '700'] - } - ] + new HtmlWebpackPlugin({ + template: 'index.prod.html', + minify: { + html5: true, + removeComments: true, + collapseWhitespace: true, + removeRedundantAttributes: true, + useShortDoctype: true, + removeEmptyAttributes: true, + removeStyleLinkTypeAttributes: true, + keepClosingSlash: true + }, + inject: true + }), + new HappyPack({ + id: 'jsx', + loaders: [ 'babel-loader' ] + }), + new HappyPack({ + id: 'scss', + loaders: [ 'style-loader!css-loader?importLoaders=1&modules&localIdentName=[local]!sass-loader' ] + }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production') }) ], - target: 'electron-main' + target: 'electron-renderer' }; module.exports = config;