summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2017-10-13 12:29:40 +0200
committerLibravatar Stefan Malzner <stefan@adlk.io>2017-10-13 12:29:40 +0200
commit58cda9cc7fb79ca9df6746de7f9662bc08dc156a (patch)
tree1211600c2a5d3b5f81c435c6896618111a611720
downloadferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.gz
ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.zst
ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.zip
initial commit
-rw-r--r--.babelrc19
-rw-r--r--.editorconfig12
-rw-r--r--.eslintignore1
-rw-r--r--.eslintrc36
-rw-r--r--.github/FEATURE_PROPOSAL_TEMPLATE.md14
-rw-r--r--.github/ISSUE_TEMPLATE.md34
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md28
-rw-r--r--.gitignore9
-rw-r--r--CHANGELOG.md0
-rw-r--r--CODE_OF_CONDUCT.md11
-rw-r--r--CONTRIBUTING15
-rw-r--r--LICENSE201
-rw-r--r--README.md54
-rw-r--r--appveyor.yml32
-rw-r--r--build-helpers/default.entitlements.mas.inherit.plist10
-rw-r--r--build-helpers/default.entitlements.mas.plist8
-rw-r--r--build-helpers/images/dmgInstaller.tiffbin0 -> 34272 bytes
-rw-r--r--build-helpers/images/icon.icnsbin0 -> 1057357 bytes
-rw-r--r--build-helpers/images/icon.icobin0 -> 370070 bytes
-rw-r--r--build-helpers/images/icon.pngbin0 -> 547480 bytes
-rw-r--r--build-helpers/images/win-app-ico.icobin0 -> 32038 bytes
-rw-r--r--build-helpers/images/win-installer-loading-splash.gifbin0 -> 36630 bytes
-rw-r--r--electron-builder.yml28
-rw-r--r--gulpfile.babel.js131
-rw-r--r--package.json108
-rw-r--r--src/I18n.js28
-rw-r--r--src/actions/app.js23
-rw-r--r--src/actions/index.js28
-rw-r--r--src/actions/lib/actions.js18
-rw-r--r--src/actions/news.js7
-rw-r--r--src/actions/payment.js8
-rw-r--r--src/actions/recipe.js9
-rw-r--r--src/actions/recipePreview.js7
-rw-r--r--src/actions/requests.js3
-rw-r--r--src/actions/service.js75
-rw-r--r--src/actions/settings.js10
-rw-r--r--src/actions/ui.js11
-rw-r--r--src/actions/user.js30
-rw-r--r--src/api/AppApi.js9
-rw-r--r--src/api/LocalApi.js18
-rw-r--r--src/api/NewsApi.js14
-rw-r--r--src/api/PaymentApi.js22
-rw-r--r--src/api/RecipePreviewsApi.js17
-rw-r--r--src/api/RecipesApi.js17
-rw-r--r--src/api/ServicesApi.js33
-rw-r--r--src/api/UserApi.js49
-rw-r--r--src/api/index.js19
-rw-r--r--src/api/server/LocalApi.js33
-rw-r--r--src/api/server/ServerApi.js574
-rw-r--r--src/app.js103
-rwxr-xr-xsrc/assets/fonts/OpenSans-Bold.ttfbin0 -> 224592 bytes
-rwxr-xr-xsrc/assets/fonts/OpenSans-BoldItalic.ttfbin0 -> 213292 bytes
-rwxr-xr-xsrc/assets/fonts/OpenSans-ExtraBold.ttfbin0 -> 222584 bytes
-rwxr-xr-xsrc/assets/fonts/OpenSans-ExtraBoldItalic.ttfbin0 -> 213420 bytes
-rwxr-xr-xsrc/assets/fonts/OpenSans-Light.ttfbin0 -> 222412 bytes
-rwxr-xr-xsrc/assets/fonts/OpenSans-Regular.ttfbin0 -> 217360 bytes
-rw-r--r--src/assets/images/adlk.svg53
-rw-r--r--src/assets/images/emoji/dontknow.pngbin0 -> 29336 bytes
-rw-r--r--src/assets/images/emoji/sad.pngbin0 -> 25270 bytes
-rwxr-xr-xsrc/assets/images/emoji/star.pngbin0 -> 16093 bytes
-rw-r--r--src/assets/images/logo.svg35
-rw-r--r--src/assets/images/sm.pngbin0 -> 751417 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-1.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-10.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-2.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-3.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-4.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-5.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-6.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-7.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-8.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-9.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/taskbar/win32/taskbar-alert.icobin0 -> 32038 bytes
-rw-r--r--src/assets/images/tray/darwin/tray-active.pngbin0 -> 396 bytes
-rw-r--r--src/assets/images/tray/darwin/tray-active@2x.pngbin0 -> 1291 bytes
-rw-r--r--src/assets/images/tray/darwin/tray-unread-active.pngbin0 -> 424 bytes
-rw-r--r--src/assets/images/tray/darwin/tray-unread-active@2x.pngbin0 -> 1359 bytes
-rw-r--r--src/assets/images/tray/darwin/tray-unread.pngbin0 -> 1264 bytes
-rw-r--r--src/assets/images/tray/darwin/tray-unread@2x.pngbin0 -> 2026 bytes
-rw-r--r--src/assets/images/tray/darwin/tray.pngbin0 -> 1230 bytes
-rw-r--r--src/assets/images/tray/darwin/tray@2x.pngbin0 -> 1545 bytes
-rw-r--r--src/assets/images/tray/linux/tray-unread.pngbin0 -> 1264 bytes
-rw-r--r--src/assets/images/tray/linux/tray-unread@2x.pngbin0 -> 2026 bytes
-rw-r--r--src/assets/images/tray/linux/tray.pngbin0 -> 1230 bytes
-rw-r--r--src/assets/images/tray/linux/tray@2x.pngbin0 -> 1545 bytes
-rw-r--r--src/assets/images/tray/win32/tray-unread.icobin0 -> 1150 bytes
-rw-r--r--src/assets/images/tray/win32/tray-unread@2x.icobin0 -> 5430 bytes
-rw-r--r--src/assets/images/tray/win32/tray.icobin0 -> 1150 bytes
-rw-r--r--src/assets/images/tray/win32/tray@2x.icobin0 -> 5430 bytes
-rw-r--r--src/components/auth/AuthLayout.js88
-rw-r--r--src/components/auth/Import.js168
-rw-r--r--src/components/auth/Invite.js111
-rw-r--r--src/components/auth/Login.js161
-rw-r--r--src/components/auth/Password.js135
-rw-r--r--src/components/auth/Pricing.js130
-rw-r--r--src/components/auth/Signup.js206
-rw-r--r--src/components/auth/Welcome.js69
-rw-r--r--src/components/layout/AppLayout.js148
-rw-r--r--src/components/layout/Sidebar.js75
-rw-r--r--src/components/services/content/ServiceWebview.js73
-rw-r--r--src/components/services/content/Services.js81
-rw-r--r--src/components/services/tabs/TabBarSortableList.js44
-rw-r--r--src/components/services/tabs/TabItem.js136
-rw-r--r--src/components/services/tabs/Tabbar.js77
-rw-r--r--src/components/settings/SettingsLayout.js56
-rw-r--r--src/components/settings/account/AccountDashboard.js286
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js84
-rw-r--r--src/components/settings/recipes/RecipeItem.js34
-rw-r--r--src/components/settings/recipes/RecipesDashboard.js151
-rw-r--r--src/components/settings/services/EditServiceForm.js277
-rw-r--r--src/components/settings/services/ServiceError.js68
-rw-r--r--src/components/settings/services/ServiceItem.js98
-rw-r--r--src/components/settings/services/ServicesDashboard.js155
-rw-r--r--src/components/settings/settings/EditSettingsForm.js148
-rw-r--r--src/components/settings/user/EditUserForm.js145
-rw-r--r--src/components/ui/AppLoader.js15
-rw-r--r--src/components/ui/Button.js78
-rw-r--r--src/components/ui/InfoBar.js88
-rw-r--r--src/components/ui/Infobox.js87
-rw-r--r--src/components/ui/Input.js148
-rw-r--r--src/components/ui/Link.js78
-rw-r--r--src/components/ui/Loader.js41
-rw-r--r--src/components/ui/Radio.js89
-rw-r--r--src/components/ui/SearchInput.js124
-rw-r--r--src/components/ui/Select.js70
-rw-r--r--src/components/ui/Subscription.js265
-rw-r--r--src/components/ui/SubscriptionPopup.js84
-rw-r--r--src/components/ui/Tabs/TabItem.js17
-rw-r--r--src/components/ui/Tabs/Tabs.js69
-rw-r--r--src/components/ui/Tabs/index.js6
-rw-r--r--src/components/ui/Toggle.js67
-rw-r--r--src/components/ui/effects/Appear.js51
-rw-r--r--src/config.js5
-rw-r--r--src/containers/auth/AuthLayoutContainer.js47
-rw-r--r--src/containers/auth/ImportScreen.js41
-rw-r--r--src/containers/auth/InviteScreen.js29
-rw-r--r--src/containers/auth/LoginScreen.js45
-rw-r--r--src/containers/auth/PasswordScreen.js38
-rw-r--r--src/containers/auth/PricingScreen.js53
-rw-r--r--src/containers/auth/SignupScreen.js43
-rw-r--r--src/containers/auth/WelcomeScreen.js34
-rw-r--r--src/containers/layout/AppLayoutContainer.js166
-rw-r--r--src/containers/settings/AccountScreen.js114
-rw-r--r--src/containers/settings/EditServiceScreen.js208
-rw-r--r--src/containers/settings/EditSettingsScreen.js167
-rw-r--r--src/containers/settings/EditUserScreen.js165
-rw-r--r--src/containers/settings/RecipesScreen.js126
-rw-r--r--src/containers/settings/ServicesScreen.js75
-rw-r--r--src/containers/settings/SettingsWindow.js43
-rw-r--r--src/containers/ui/SubscriptionFormScreen.js126
-rw-r--r--src/containers/ui/SubscriptionPopupScreen.js43
-rw-r--r--src/electron/Settings.js15
-rw-r--r--src/electron/exception.js4
-rw-r--r--src/electron/ipc-api/appIndicator.js80
-rw-r--r--src/electron/ipc-api/autoUpdate.js54
-rw-r--r--src/electron/ipc-api/index.js9
-rw-r--r--src/electron/ipc-api/settings.js10
-rw-r--r--src/electron/ipc-api/tray.js48
-rw-r--r--src/electron/webview-ime-focus.js40
-rw-r--r--src/environment.js22
-rw-r--r--src/helpers/password-helpers.js36
-rw-r--r--src/helpers/recipe-helpers.js39
-rw-r--r--src/helpers/routing-helpers.js4
-rw-r--r--src/helpers/validation-helpers.js48
-rw-r--r--src/helpers/webview-ime-focus-helpers.js38
-rw-r--r--src/i18n/globalMessages.js16
-rw-r--r--src/i18n/languages.js4
-rw-r--r--src/i18n/locales/en-US.json167
-rw-r--r--src/i18n/translations.js13
-rw-r--r--src/index.html30
-rw-r--r--src/index.js147
-rw-r--r--src/lib/Form.js31
-rw-r--r--src/lib/Menu.js259
-rw-r--r--src/lib/Miner.js72
-rw-r--r--src/lib/TouchBar.js45
-rw-r--r--src/lib/analytics.js42
-rw-r--r--src/models/News.js19
-rw-r--r--src/models/Order.js17
-rw-r--r--src/models/Plan.js16
-rw-r--r--src/models/Recipe.js52
-rw-r--r--src/models/RecipePreview.js16
-rw-r--r--src/models/Service.js132
-rw-r--r--src/models/User.js41
-rw-r--r--src/prop-types.js14
-rw-r--r--src/stores/AppStore.js309
-rw-r--r--src/stores/GlobalErrorStore.js28
-rw-r--r--src/stores/NewsStore.js42
-rw-r--r--src/stores/PaymentStore.js47
-rw-r--r--src/stores/RecipePreviewsStore.js50
-rw-r--r--src/stores/RecipesStore.js96
-rw-r--r--src/stores/RequestStore.js59
-rw-r--r--src/stores/ServicesStore.js503
-rw-r--r--src/stores/SettingsStore.js55
-rw-r--r--src/stores/UIStore.js34
-rw-r--r--src/stores/UserStore.js272
-rw-r--r--src/stores/index.js34
-rw-r--r--src/stores/lib/CachedRequest.js106
-rw-r--r--src/stores/lib/Reaction.js22
-rw-r--r--src/stores/lib/Request.js112
-rw-r--r--src/stores/lib/Store.js44
-rw-r--r--src/styles/animations.scss90
-rw-r--r--src/styles/auth.scss144
-rw-r--r--src/styles/badge.scss15
-rw-r--r--src/styles/button.scss74
-rw-r--r--src/styles/colors.scss22
-rw-r--r--src/styles/config.scss1
-rw-r--r--src/styles/content-tabs.scss52
-rw-r--r--src/styles/fonts.scss44
-rw-r--r--src/styles/info-bar.scss79
-rw-r--r--src/styles/infobox.scss61
-rw-r--r--src/styles/input.scss99
-rw-r--r--src/styles/layout.scss141
-rw-r--r--src/styles/main.scss36
-rw-r--r--src/styles/mixins.scss9
-rw-r--r--src/styles/radio.scss34
-rw-r--r--src/styles/recipes.scss72
-rw-r--r--src/styles/reset.scss95
-rw-r--r--src/styles/searchInput.scss4
-rw-r--r--src/styles/select.scss19
-rw-r--r--src/styles/service-table.scss62
-rw-r--r--src/styles/services.scss60
-rw-r--r--src/styles/settings.scss392
-rw-r--r--src/styles/subscription-popup.scss20
-rw-r--r--src/styles/subscription.scss72
-rw-r--r--src/styles/tabs.scss72
-rw-r--r--src/styles/toggle.scss47
-rw-r--r--src/styles/tooltip.scss4
-rw-r--r--src/styles/type.scss73
-rw-r--r--src/styles/util.scss20
-rw-r--r--src/styles/welcome.scss75
-rw-r--r--src/webview/ime.js10
-rw-r--r--src/webview/lib/RecipeWebview.js74
-rw-r--r--src/webview/notifications.js45
-rw-r--r--src/webview/plugin.js24
-rw-r--r--src/webview/spellchecker.js14
-rw-r--r--src/webview/zoom.js37
-rw-r--r--yarn.lock6731
237 files changed, 20469 insertions, 0 deletions
diff --git a/.babelrc b/.babelrc
new file mode 100644
index 000000000..dc041f43b
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,19 @@
1{
2 "presets": [
3 [
4 "babel-preset-env",
5 {
6 "targets": {
7 "electron": "1.7.2"
8 }
9 }
10 ],
11 "babel-preset-react"
12 ],
13 "plugins": [
14 "transform-decorators-legacy",
15 "transform-flow-strip-types",
16 "transform-class-properties",
17 ],
18 "sourceMaps": "inline"
19}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..4a7ea3036
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
1root = true
2
3[*]
4indent_style = space
5indent_size = 2
6end_of_line = lf
7charset = utf-8
8trim_trailing_whitespace = true
9insert_final_newline = true
10
11[*.md]
12trim_trailing_whitespace = false
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 000000000..567609b12
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
build/
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 000000000..994c00653
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,36 @@
1{
2 "parser": "babel-eslint",
3 "extends": "eslint-config-airbnb",
4 "rules": {
5 "import/extensions": 0,
6 "import/no-extraneous-dependencies": 0,
7 "import/no-unresolved": [2, {
8 "ignore": ["electron"]
9 }],
10 "linebreak-style": 0,
11 "react/prefer-stateless-function": 0,
12 "react/jsx-filename-extension": [1, {
13 "extensions": [".js", ".jsx"]
14 }],
15 "no-underscore-dangle": 0,
16 "max-len": 0,
17 "class-methods-use-this": 0,
18 "no-console": 0,
19 "react/jsx-no-bind": 0,
20 "jsx-a11y/no-static-element-interactions": 0,
21 "react/jsx-no-target-blank": 0,
22 "no-restricted-syntax": [0, "ForInStatement"]
23 },
24 "globals": {
25 "window": true,
26 "document": true,
27 "ENV": true,
28 "FormData": true,
29 "localStorage": true,
30 "navigator": true,
31 "Worker": true,
32 "atob": true,
33 "btoa": true,
34 "ga": true
35 }
36}
diff --git a/.github/FEATURE_PROPOSAL_TEMPLATE.md b/.github/FEATURE_PROPOSAL_TEMPLATE.md
new file mode 100644
index 000000000..164844c54
--- /dev/null
+++ b/.github/FEATURE_PROPOSAL_TEMPLATE.md
@@ -0,0 +1,14 @@
1<!--- Provide a general summary of your changes in the Title above -->
2
3### Feature Description
4<!--- Describe your feature in detail -->
5
6### Motivation and Context
7<!---
8* Why is this change required?
9* How is this improving the Franz experience?
10* What problem does it solve?
11-->
12
13### Mockups, Screenshots (if available):
14<!--- A picture says more than a thousand words. -->
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000..d766f756b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,34 @@
1<!--- Provide a general summary of the issue in the Title above -->
2
3<!--- If you want to propose a feature, use this template: https://github.com/meetfranz/franz/.github/FEATURE_PROPOSAL_TEMPLATE.md -->
4
5### Expected Behavior
6<!--- If you're describing a bug, tell us what should happen -->
7<!--- If you're suggesting a change/improvement, tell us how it should work -->
8
9### Current Behavior
10<!--- If describing a bug, tell us what happens instead of the expected behavior -->
11<!--- If suggesting a change/improvement, explain the difference from current behavior -->
12
13### Screenshots (if appropriate):
14
15### Possible Solution
16<!--- Not obligatory, but suggest a fix/reason for the bug, -->
17<!--- or ideas how to implement the addition or change -->
18
19### Steps to Reproduce (for bugs)
20<!--- Provide a link to a live example, or an unambiguous set of steps to -->
21<!--- reproduce this bug. Include code to reproduce, if relevant -->
221.
232.
243.
254.
26
27### Context
28<!--- How has this issue affected you? What are you trying to accomplish? -->
29<!--- Providing context helps us come up with a solution that is most useful in the real world -->
30
31### Your Environment
32<!--- Include as many relevant details about the environment you experienced the bug in -->
33* Franz Version used:
34* Operating System and version:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 000000000..6c4384695
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,28 @@
1<!--- Provide a general summary of your changes in the Title above -->
2
3### Description
4<!--- Describe your changes in detail -->
5
6### Motivation and Context
7<!--- Why is this change required? What problem does it solve? -->
8<!--- If it fixes an open issue, please link to the issue here. -->
9
10### How Has This Been Tested?
11<!--- Please describe in detail how you tested your changes. -->
12<!--- Include details of your testing environment, tests ran to see how -->
13<!--- your change affects other areas of the code, etc. -->
14
15### Screenshots (if appropriate):
16
17### Types of changes
18<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
19- [ ] Bug fix (non-breaking change which fixes an issue)
20- [ ] New feature (non-breaking change which adds functionality)
21- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
22
23### Checklist:
24<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
25<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
26- [ ] My code follows the code style of this project (run `$ yarn lint`).
27<!---- [ ] My change requires a change to the documentation.
28- [ ] I have updated the documentation accordingly. -->
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..f9ca0edc5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
1node_modules
2flow-typed
3out
4.DS_Store
5build
6.tmp
7.stage
8.env
9yarn-error.log
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/CHANGELOG.md
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..38bafdced
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,11 @@
1Contributor Code of Conduct
2
3As contributors and maintainers of the Franz project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities.
4
5Communication through GitHub, Slack, email or any other channel must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
6
7We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the project to do the same.
8
9If any member of the community violates this code of conduct, the maintainers of the Franz project may take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate.
10
11If you are subject to or witness unacceptable behavior, or have any other concerns, please open an issue or send an email to [Stefan](stefan@adlk.io).
diff --git a/CONTRIBUTING b/CONTRIBUTING
new file mode 100644
index 000000000..65476fa66
--- /dev/null
+++ b/CONTRIBUTING
@@ -0,0 +1,15 @@
1# Contributing to Franz 5
2
3:tada: First off, thanks for taking the time and your effort to make Franz better! :tada:
4
5## Code of Conduct
6
7This project and everyone participating in it is governed by the [Franz Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [stefan@adlk.io](mailto:stefan@adlk.io).
8
9## What should I know before I get started?
10With Franz 5, we have completely separated the client and the services. If you have any issues with a service recipe, please do not open an issue at this repository. Instead head over to the [Franz Recipe Repository](https://github.com/meetfranz/recipes) and open a new issue there.
11
12If you need help with development, want to discuss a new feature or improvement please talk to us either on [Slack](https://slack.franz.im) or open a new issue with the [feature proposal template](.github/FEATURE_PROPOSAL_TEMPLATE.md).
13
14## How Can I Contribute?
15As a basic rule, before filing issues, feature requests or anything else. Take a look at the issues and check if this has not already been reported by another user. If so, engage in the already existing discussion.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..0f43161d7
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
1 Apache License
2 Version 2.0, January 2004
3 http://www.apache.org/licenses/
4
5 TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
7 1. Definitions.
8
9 "License" shall mean the terms and conditions for use, reproduction,
10 and distribution as defined by Sections 1 through 9 of this document.
11
12 "Licensor" shall mean the copyright owner or entity authorized by
13 the copyright owner that is granting the License.
14
15 "Legal Entity" shall mean the union of the acting entity and all
16 other entities that control, are controlled by, or are under common
17 control with that entity. For the purposes of this definition,
18 "control" means (i) the power, direct or indirect, to cause the
19 direction or management of such entity, whether by contract or
20 otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 outstanding shares, or (iii) beneficial ownership of such entity.
22
23 "You" (or "Your") shall mean an individual or Legal Entity
24 exercising permissions granted by this License.
25
26 "Source" form shall mean the preferred form for making modifications,
27 including but not limited to software source code, documentation
28 source, and configuration files.
29
30 "Object" form shall mean any form resulting from mechanical
31 transformation or translation of a Source form, including but
32 not limited to compiled object code, generated documentation,
33 and conversions to other media types.
34
35 "Work" shall mean the work of authorship, whether in Source or
36 Object form, made available under the License, as indicated by a
37 copyright notice that is included in or attached to the work
38 (an example is provided in the Appendix below).
39
40 "Derivative Works" shall mean any work, whether in Source or Object
41 form, that is based on (or derived from) the Work and for which the
42 editorial revisions, annotations, elaborations, or other modifications
43 represent, as a whole, an original work of authorship. For the purposes
44 of this License, Derivative Works shall not include works that remain
45 separable from, or merely link (or bind by name) to the interfaces of,
46 the Work and Derivative Works thereof.
47
48 "Contribution" shall mean any work of authorship, including
49 the original version of the Work and any modifications or additions
50 to that Work or Derivative Works thereof, that is intentionally
51 submitted to Licensor for inclusion in the Work by the copyright owner
52 or by an individual or Legal Entity authorized to submit on behalf of
53 the copyright owner. For the purposes of this definition, "submitted"
54 means any form of electronic, verbal, or written communication sent
55 to the Licensor or its representatives, including but not limited to
56 communication on electronic mailing lists, source code control systems,
57 and issue tracking systems that are managed by, or on behalf of, the
58 Licensor for the purpose of discussing and improving the Work, but
59 excluding communication that is conspicuously marked or otherwise
60 designated in writing by the copyright owner as "Not a Contribution."
61
62 "Contributor" shall mean Licensor and any individual or Legal Entity
63 on behalf of whom a Contribution has been received by Licensor and
64 subsequently incorporated within the Work.
65
66 2. Grant of Copyright License. Subject to the terms and conditions of
67 this License, each Contributor hereby grants to You a perpetual,
68 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 copyright license to reproduce, prepare Derivative Works of,
70 publicly display, publicly perform, sublicense, and distribute the
71 Work and such Derivative Works in Source or Object form.
72
73 3. Grant of Patent License. Subject to the terms and conditions of
74 this License, each Contributor hereby grants to You a perpetual,
75 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 (except as stated in this section) patent license to make, have made,
77 use, offer to sell, sell, import, and otherwise transfer the Work,
78 where such license applies only to those patent claims licensable
79 by such Contributor that are necessarily infringed by their
80 Contribution(s) alone or by combination of their Contribution(s)
81 with the Work to which such Contribution(s) was submitted. If You
82 institute patent litigation against any entity (including a
83 cross-claim or counterclaim in a lawsuit) alleging that the Work
84 or a Contribution incorporated within the Work constitutes direct
85 or contributory patent infringement, then any patent licenses
86 granted to You under this License for that Work shall terminate
87 as of the date such litigation is filed.
88
89 4. Redistribution. You may reproduce and distribute copies of the
90 Work or Derivative Works thereof in any medium, with or without
91 modifications, and in Source or Object form, provided that You
92 meet the following conditions:
93
94 (a) You must give any other recipients of the Work or
95 Derivative Works a copy of this License; and
96
97 (b) You must cause any modified files to carry prominent notices
98 stating that You changed the files; and
99
100 (c) You must retain, in the Source form of any Derivative Works
101 that You distribute, all copyright, patent, trademark, and
102 attribution notices from the Source form of the Work,
103 excluding those notices that do not pertain to any part of
104 the Derivative Works; and
105
106 (d) If the Work includes a "NOTICE" text file as part of its
107 distribution, then any Derivative Works that You distribute must
108 include a readable copy of the attribution notices contained
109 within such NOTICE file, excluding those notices that do not
110 pertain to any part of the Derivative Works, in at least one
111 of the following places: within a NOTICE text file distributed
112 as part of the Derivative Works; within the Source form or
113 documentation, if provided along with the Derivative Works; or,
114 within a display generated by the Derivative Works, if and
115 wherever such third-party notices normally appear. The contents
116 of the NOTICE file are for informational purposes only and
117 do not modify the License. You may add Your own attribution
118 notices within Derivative Works that You distribute, alongside
119 or as an addendum to the NOTICE text from the Work, provided
120 that such additional attribution notices cannot be construed
121 as modifying the License.
122
123 You may add Your own copyright statement to Your modifications and
124 may provide additional or different license terms and conditions
125 for use, reproduction, or distribution of Your modifications, or
126 for any such Derivative Works as a whole, provided Your use,
127 reproduction, and distribution of the Work otherwise complies with
128 the conditions stated in this License.
129
130 5. Submission of Contributions. Unless You explicitly state otherwise,
131 any Contribution intentionally submitted for inclusion in the Work
132 by You to the Licensor shall be under the terms and conditions of
133 this License, without any additional terms or conditions.
134 Notwithstanding the above, nothing herein shall supersede or modify
135 the terms of any separate license agreement you may have executed
136 with Licensor regarding such Contributions.
137
138 6. Trademarks. This License does not grant permission to use the trade
139 names, trademarks, service marks, or product names of the Licensor,
140 except as required for reasonable and customary use in describing the
141 origin of the Work and reproducing the content of the NOTICE file.
142
143 7. Disclaimer of Warranty. Unless required by applicable law or
144 agreed to in writing, Licensor provides the Work (and each
145 Contributor provides its Contributions) on an "AS IS" BASIS,
146 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 implied, including, without limitation, any warranties or conditions
148 of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 PARTICULAR PURPOSE. You are solely responsible for determining the
150 appropriateness of using or redistributing the Work and assume any
151 risks associated with Your exercise of permissions under this License.
152
153 8. Limitation of Liability. In no event and under no legal theory,
154 whether in tort (including negligence), contract, or otherwise,
155 unless required by applicable law (such as deliberate and grossly
156 negligent acts) or agreed to in writing, shall any Contributor be
157 liable to You for damages, including any direct, indirect, special,
158 incidental, or consequential damages of any character arising as a
159 result of this License or out of the use or inability to use the
160 Work (including but not limited to damages for loss of goodwill,
161 work stoppage, computer failure or malfunction, or any and all
162 other commercial damages or losses), even if such Contributor
163 has been advised of the possibility of such damages.
164
165 9. Accepting Warranty or Additional Liability. While redistributing
166 the Work or Derivative Works thereof, You may choose to offer,
167 and charge a fee for, acceptance of support, warranty, indemnity,
168 or other liability obligations and/or rights consistent with this
169 License. However, in accepting such obligations, You may act only
170 on Your own behalf and on Your sole responsibility, not on behalf
171 of any other Contributor, and only if You agree to indemnify,
172 defend, and hold each Contributor harmless for any liability
173 incurred by, or claims asserted against, such Contributor by reason
174 of your accepting any such warranty or additional liability.
175
176 END OF TERMS AND CONDITIONS
177
178 APPENDIX: How to apply the Apache License to your work.
179
180 To apply the Apache License to your work, attach the following
181 boilerplate notice, with the fields enclosed by brackets "[]"
182 replaced with your own identifying information. (Don't include
183 the brackets!) The text should be enclosed in the appropriate
184 comment syntax for the file format. We also recommend that a
185 file or class name and description of purpose be included on the
186 same "printed page" as the copyright notice for easier
187 identification within third-party archives.
188
189 Copyright 2017 Stefan Malzner
190
191 Licensed under the Apache License, Version 2.0 (the "License");
192 you may not use this file except in compliance with the License.
193 You may obtain a copy of the License at
194
195 http://www.apache.org/licenses/LICENSE-2.0
196
197 Unless required by applicable law or agreed to in writing, software
198 distributed under the License is distributed on an "AS IS" BASIS,
199 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 See the License for the specific language governing permissions and
201 limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..fbb8b8760
--- /dev/null
+++ b/README.md
@@ -0,0 +1,54 @@
1**This repository is only for Franz 5 and later, previous versions are no longer maintained.**
2---
3
4<img src="./build-helpers/images/icon.png" alt="" width="150"/>
5
6# Franz 5 (beta) [![Build status Windows](https://ci.appveyor.com/api/projects/status/22e01sssgdt90d34/branch/master?svg=true)](https://ci.appveyor.com/project/adlk/franz/branch/master) [![Build Status Mac](https://travis-ci.com/meetfranz/franz.svg?token=9zxYPVb6RdKVnmTFMJLX&branch=master)](https://travis-ci.com/meetfranz/franz) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://meetfranz.com/payment.html)
7
8Messaging app for WhatsApp, Slack, Telegram, HipChat, Hangouts and many many more.
9
10
11## Development
12
13### Preparations
14#### Install yarn
15##### MacOS
16```bash
17$ brew install yarn
18```
19##### Windows
20[Download installer](https://yarnpkg.com/latest.msi)
21
22#### Install Gulp 4
23```bash
24$ yarn add global gulp-cli@1.2.2
25$ yarn add global gulpjs/gulp#4.0
26```
27
28
29### Run Franz Development App
30Run these two commands __simultaneously__ in different console tabs.
31
32```bash
33$ yarn run dev
34$ yarn start
35```
36
37## Packaging
38```bash
39$ yarn build
40```
41
42## How can I support the project?
43If you have found a bug that hasn't been reported yet, please open a new issue.
44
45## I need help?
46Join the Franz community on [Slack](https://slack.franz.im) and get in touch with us.
47
48## Next steps
49- [ ] Create acceptance tests
50- [ ] 5.0 stable release
51- [ ] Developer Documentation
52
53## License
54Franz 5 is open-source licensed under the GPLv2 License.
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 000000000..c00198312
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,32 @@
1environment:
2 GH_TOKEN:
3 secure: LiO1Z/i16UV5YyElROSUykQqpJowSMK86I7Nw4G/NMa0q5yNA7EsUqEiJUL+OG01
4 CSC_LINK:
5 secure: NEYFWgUIAB//y2GR4AJOaegwuNjkxyNsdDf5A40dYovebTUsnIB5k4GCbU3I6JKW9iwH2ldU7Z+QawfyFerNgw==
6 CSC_KEY_PASSWORD:
7 secure: t8ypNTPKTmvRfd3hHA4aMOtC5KOFqOw3AsKhpU7140Q=
8
9version: 5.0.0.{build}
10
11install:
12 - ps: Install-Product node 7
13 - yarn add global gulp-cli@1.2.2
14 - yarn add global gulpjs/gulp#4.0
15 - yarn install
16
17cache:
18 - "%LOCALAPPDATA%\\Yarn"
19
20before_build:
21 - yarn lint
22
23build_script:
24 - yarn build
25
26notifications:
27 - provider: Slack
28 incoming_webhook:
29 secure: 2NnhP/L7Jk9PDE0JvdNnYVBnmDHFRWHmTQBQ1492ZQ5O0Ok/97FAbk/FbGZVEalli8xcaPkVu3jiOD7IPVvHLKUrmyvficRgmL+qf4lhD1s=
30
31artifacts:
32 - path: out\*.exe
diff --git a/build-helpers/default.entitlements.mas.inherit.plist b/build-helpers/default.entitlements.mas.inherit.plist
new file mode 100644
index 000000000..d8dc69e80
--- /dev/null
+++ b/build-helpers/default.entitlements.mas.inherit.plist
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4 <dict>
5 <key>com.apple.security.app-sandbox</key>
6 <true/>
7 <key>com.apple.security.inherit</key>
8 <true/>
9 </dict>
10</plist>
diff --git a/build-helpers/default.entitlements.mas.plist b/build-helpers/default.entitlements.mas.plist
new file mode 100644
index 000000000..8e31f755a
--- /dev/null
+++ b/build-helpers/default.entitlements.mas.plist
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4 <dict>
5 <key>com.apple.security.app-sandbox</key>
6 <true/>
7 </dict>
8</plist>
diff --git a/build-helpers/images/dmgInstaller.tiff b/build-helpers/images/dmgInstaller.tiff
new file mode 100644
index 000000000..faff9f245
--- /dev/null
+++ b/build-helpers/images/dmgInstaller.tiff
Binary files differ
diff --git a/build-helpers/images/icon.icns b/build-helpers/images/icon.icns
new file mode 100644
index 000000000..15120c210
--- /dev/null
+++ b/build-helpers/images/icon.icns
Binary files differ
diff --git a/build-helpers/images/icon.ico b/build-helpers/images/icon.ico
new file mode 100644
index 000000000..947f703a3
--- /dev/null
+++ b/build-helpers/images/icon.ico
Binary files differ
diff --git a/build-helpers/images/icon.png b/build-helpers/images/icon.png
new file mode 100644
index 000000000..e0b4935b5
--- /dev/null
+++ b/build-helpers/images/icon.png
Binary files differ
diff --git a/build-helpers/images/win-app-ico.ico b/build-helpers/images/win-app-ico.ico
new file mode 100644
index 000000000..339ba79ca
--- /dev/null
+++ b/build-helpers/images/win-app-ico.ico
Binary files differ
diff --git a/build-helpers/images/win-installer-loading-splash.gif b/build-helpers/images/win-installer-loading-splash.gif
new file mode 100644
index 000000000..02598281f
--- /dev/null
+++ b/build-helpers/images/win-installer-loading-splash.gif
Binary files differ
diff --git a/electron-builder.yml b/electron-builder.yml
new file mode 100644
index 000000000..0a0c19676
--- /dev/null
+++ b/electron-builder.yml
@@ -0,0 +1,28 @@
1directories:
2 app: ./build
3 output: ./out
4
5forceCodeSigning: true
6
7compression: maximum
8
9mac:
10 category: public.app-category.productivity
11 icon: ./build-helpers/images/icon.icns
12
13dmg:
14 background: ./build-helpers/images/dmgInstaller.tiff
15 icon: ./build-helpers/images/icon.icns
16 iconSize: 128
17 contents: [{x: 380, y: 240, type: link, path: /Applications}, {x: 122, y: 240, type: file}]
18
19win:
20 icon: ./build-helpers/images/icon.ico
21 target: nsis
22 arch:
23 - x64
24 - ia32
25
26nsis:
27 perMachine: true
28 oneClick: true
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
new file mode 100644
index 000000000..d947974b3
--- /dev/null
+++ b/gulpfile.babel.js
@@ -0,0 +1,131 @@
1/* eslint max-len: 0 */
2import gulp from 'gulp';
3import babel from 'gulp-babel';
4import sass from 'gulp-sass';
5import server from 'gulp-server-livereload';
6import del from 'del';
7import { exec } from 'child_process';
8import dotenv from 'dotenv';
9import sassVariables from 'gulp-sass-variables';
10
11import config from './package.json';
12
13dotenv.config();
14
15const paths = {
16 src: 'src',
17 dest: 'build',
18 tmp: '.tmp',
19 package: `out/${config.version}`,
20 html: {
21 src: 'src/**/*.html',
22 dest: 'build/',
23 watch: 'src/**/*.html',
24 },
25 styles: {
26 src: 'src/styles/main.scss',
27 dest: 'build/styles',
28 watch: 'src/styles/**/*.scss',
29 },
30 scripts: {
31 src: 'src/**/*.js',
32 dest: 'build/',
33 watch: 'src/**/*.js',
34 },
35};
36
37function _shell(cmd, cb) {
38 exec(cmd, {
39 cwd: paths.dest,
40 }, (error, stdout, stderr) => {
41 if (error) {
42 console.error(`exec error: ${error}`);
43 return;
44 }
45 console.log(`stdout: ${stdout}`);
46 console.log(`stderr: ${stderr}`);
47
48 cb();
49 });
50}
51
52const clean = () => del([paths.tmp, paths.dest]);
53export { clean };
54
55export function mvSrc() {
56 return gulp.src(
57 [
58 `${paths.src}/*`,
59 `${paths.src}/*/**`,
60 `!${paths.scripts.watch}`,
61 `!${paths.src}/styles/**`,
62 ], { since: gulp.lastRun(mvSrc) })
63 .pipe(gulp.dest(paths.dest));
64}
65
66export function mvPackageJson() {
67 return gulp.src(
68 [
69 './package.json',
70 ])
71 .pipe(gulp.dest(paths.dest));
72}
73
74export function html() {
75 return gulp.src(paths.html.src, { since: gulp.lastRun(html) })
76 .pipe(gulp.dest(paths.html.dest));
77}
78
79export function styles() {
80 return gulp.src(paths.styles.src)
81 .pipe(sassVariables({
82 $env: process.env.NODE_ENV === 'development' ? 'development' : 'production',
83 }))
84 .pipe(sass({
85 includePaths: [
86 './node_modules',
87 '../node_modules',
88 ],
89 }).on('error', sass.logError))
90 .pipe(gulp.dest(paths.styles.dest));
91}
92
93export function scripts() {
94 return gulp.src(paths.scripts.src, { since: gulp.lastRun(scripts) })
95 .pipe(babel({
96 comments: false,
97 }))
98 .pipe(gulp.dest(paths.scripts.dest));
99}
100
101export function watch() {
102 gulp.watch(paths.scripts.watch, scripts);
103 gulp.watch(paths.styles.watch, styles);
104
105 gulp.watch([
106 paths.src,
107 `${paths.scripts.src}`,
108 `${paths.styles.src}`,
109 ], mvSrc);
110}
111
112export function webserver() {
113 gulp.src(paths.dest)
114 .pipe(server({
115 livereload: true,
116 }));
117}
118
119export function sign(done) {
120 _shell(`codesign --verbose=4 --deep --strict --force --sign "${process.env.SIGNING_IDENTITY}" "${__dirname}/node_modules/electron/dist/Electron.app"`, done);
121}
122
123const build = gulp.series(
124 clean,
125 gulp.parallel(mvSrc, mvPackageJson),
126 gulp.parallel(html, scripts, styles),
127);
128export { build };
129
130const dev = gulp.series(build, gulp.parallel(webserver, watch));
131export { dev };
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..6521432de
--- /dev/null
+++ b/package.json
@@ -0,0 +1,108 @@
1{
2 "name": "franz",
3 "productName": "Franz",
4 "appId": "com.meetfranz.franz",
5 "version": "5.0.0-beta.10",
6 "description": "Messaging app for WhatsApp, Slack, Telegram, HipChat, Hangouts and many many more.",
7 "copyright": "adlk x franz - Stefan Malzner",
8 "main": "index.js",
9 "repository": "https://github.com/meetfranz/franz.git",
10 "private": true,
11 "scripts": {
12 "start": "electron ./build",
13 "start:local": "cross-env LOCAL_API=1 yarn start",
14 "start:live": "cross-env LIVE_API=1 yarn start",
15 "dev": "cross-env NODE_ENV=development gulp dev",
16 "lint": "eslint src",
17 "sign": "gulp sign",
18 "prebuild": "gulp build",
19 "build": "node_modules/.bin/electron-builder --publish onTag",
20 "rebuild": "node_modules/.bin/electron-rebuild",
21 "commit": "git-cz",
22 "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
23 },
24 "keywords": [],
25 "author": "Stefan Malzner <stefan@adlk.io>",
26 "license": "Apache-2.0",
27 "dependencies": {
28 "@paulcbetts/system-idle-time": "^1.0.4",
29 "babel-polyfill": "^6.23.0",
30 "babel-runtime": "^6.23.0",
31 "classnames": "^2.2.5",
32 "electron-fetch": "^1.1.0",
33 "electron-spellchecker": "^1.2.0",
34 "electron-squirrel-startup": "^1.0.0",
35 "electron-updater": "^2.4.3",
36 "electron-window-state": "^4.1.0",
37 "fs-extra": "^3.0.1",
38 "ini": "^1.3.4",
39 "jshashes": "^1.0.6",
40 "jsonwebtoken": "^7.4.1",
41 "keymaster": "^1.6.2",
42 "lodash": "^4.17.4",
43 "mdi": "^1.9.33",
44 "minimist": "^1.2.0",
45 "mkdirp": "^0.5.1",
46 "mobx": "^3.1.0",
47 "mobx-react": "^4.1.0",
48 "mobx-react-form": "1.24.0",
49 "mobx-react-router": "^3.1.2",
50 "moment": "^2.17.1",
51 "normalize-url": "^1.9.1",
52 "prop-types": "^15.5.10",
53 "prop-types-extended": "^0.2.1",
54 "react": "^15.4.1",
55 "react-addons-css-transition-group": "^15.4.2",
56 "react-dom": "^15.4.1",
57 "react-electron-web-view": "^2.0.1",
58 "react-intl": "^2.3.0",
59 "react-loader": "^2.4.0",
60 "react-router": "^3.0.2",
61 "react-router-transition": "^0.1.1",
62 "react-sortable-hoc": "^0.6.7",
63 "react-tooltip": "^3.2.7",
64 "route-parser": "^0.0.5",
65 "smoothscroll-polyfill": "^0.3.4",
66 "tar.gz": "^1.0.5",
67 "uuid": "^3.0.1"
68 },
69 "devDependencies": {
70 "babel-eslint": "^7.1.1",
71 "babel-plugin-transform-class-properties": "^6.19.0",
72 "babel-plugin-transform-decorators-legacy": "^1.3.4",
73 "babel-plugin-transform-flow-strip-types": "^6.22.0",
74 "babel-preset-env": "^1.5.2",
75 "babel-preset-es2015": "^6.22.0",
76 "babel-preset-es2016": "^6.16.0",
77 "babel-preset-es2017": "^6.16.0",
78 "babel-preset-react": "^6.23.0",
79 "babel-preset-stage-0": "^6.22.0",
80 "babel-preset-stage-1": "^6.22.0",
81 "cross-env": "^5.0.5",
82 "cz-conventional-changelog": "^2.0.0",
83 "del": "^2.2.2",
84 "dotenv": "^4.0.0",
85 "electron": "^1.7.6",
86 "electron-builder": "19.15.1",
87 "electron-packager": "^8.7.0",
88 "electron-rebuild": "^1.6.0",
89 "eslint": "^4.7.1",
90 "eslint-config-airbnb": "^14.1.0",
91 "eslint-loader": "^1.9.0",
92 "eslint-plugin-import": "^2.2.0",
93 "eslint-plugin-jsx-a11y": "^3.0.0",
94 "eslint-plugin-react": "^6.10.0",
95 "gulp": "gulpjs/gulp#4.0",
96 "gulp-babel": "^6.1.2",
97 "gulp-github-release": "^1.2.1",
98 "gulp-sass": "^3.1.0",
99 "gulp-sass-variables": "^1.1.1",
100 "gulp-server-livereload": "^1.9.2",
101 "node-sass": "^4.5.3"
102 },
103 "config": {
104 "commitizen": {
105 "path": "./node_modules/cz-conventional-changelog"
106 }
107 }
108}
diff --git a/src/I18n.js b/src/I18n.js
new file mode 100644
index 000000000..ae3ba2fa9
--- /dev/null
+++ b/src/I18n.js
@@ -0,0 +1,28 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { IntlProvider } from 'react-intl';
5
6import { oneOrManyChildElements } from './prop-types';
7import translations from './i18n/translations';
8import UserStore from './stores/UserStore';
9
10@inject('stores') @observer
11export default class I18N extends Component {
12 render() {
13 const { stores, children } = this.props;
14 const { locale } = stores.app;
15 return (
16 <IntlProvider {...{ locale, key: locale, messages: translations[locale] }}>
17 {children}
18 </IntlProvider>
19 );
20 }
21}
22
23I18N.wrappedComponent.propTypes = {
24 stores: PropTypes.shape({
25 user: PropTypes.instanceOf(UserStore).isRequired,
26 }).isRequired,
27 children: oneOrManyChildElements.isRequired,
28};
diff --git a/src/actions/app.js b/src/actions/app.js
new file mode 100644
index 000000000..5db4b739e
--- /dev/null
+++ b/src/actions/app.js
@@ -0,0 +1,23 @@
1import PropTypes from 'prop-types';
2
3export default {
4 setBadge: {
5 unreadDirectMessageCount: PropTypes.number.isRequired,
6 unreadIndirectMessageCount: PropTypes.number,
7 },
8 notify: {
9 title: PropTypes.string.isRequired,
10 options: PropTypes.object.isRequired,
11 serviceId: PropTypes.string,
12 },
13 launchOnStartup: {
14 enable: PropTypes.bool.isRequired,
15 },
16 openExternalUrl: {
17 url: PropTypes.string.isRequired,
18 },
19 checkForUpdates: {},
20 resetUpdateStatus: {},
21 installUpdate: {},
22 healthCheck: {},
23};
diff --git a/src/actions/index.js b/src/actions/index.js
new file mode 100644
index 000000000..59acabb0b
--- /dev/null
+++ b/src/actions/index.js
@@ -0,0 +1,28 @@
1import PropTypes from 'prop-types';
2
3import defineActions from './lib/actions';
4import service from './service';
5import recipe from './recipe';
6import recipePreview from './recipePreview';
7import ui from './ui';
8import app from './app';
9import user from './user';
10import payment from './payment';
11import news from './news';
12import settings from './settings';
13import requests from './requests';
14
15const actions = Object.assign({}, {
16 service,
17 recipe,
18 recipePreview,
19 ui,
20 app,
21 user,
22 payment,
23 news,
24 settings,
25 requests,
26});
27
28export default defineActions(actions, PropTypes.checkPropTypes);
diff --git a/src/actions/lib/actions.js b/src/actions/lib/actions.js
new file mode 100644
index 000000000..499018d70
--- /dev/null
+++ b/src/actions/lib/actions.js
@@ -0,0 +1,18 @@
1export default (definitions, validate) => {
2 const newActions = {};
3 Object.keys(definitions).forEach((scopeName) => {
4 newActions[scopeName] = {};
5 Object.keys(definitions[scopeName]).forEach((actionName) => {
6 const action = (params) => {
7 const schema = definitions[scopeName][actionName];
8 validate(schema, params, actionName);
9 action.notify(params);
10 };
11 newActions[scopeName][actionName] = action;
12 action.listeners = [];
13 action.listen = listener => action.listeners.push(listener);
14 action.notify = params => action.listeners.forEach(listener => listener(params));
15 });
16 });
17 return newActions;
18};
diff --git a/src/actions/news.js b/src/actions/news.js
new file mode 100644
index 000000000..db106e84f
--- /dev/null
+++ b/src/actions/news.js
@@ -0,0 +1,7 @@
1import PropTypes from 'prop-types';
2
3export default {
4 hide: {
5 newsId: PropTypes.string.isRequired,
6 },
7};
diff --git a/src/actions/payment.js b/src/actions/payment.js
new file mode 100644
index 000000000..2aaefc025
--- /dev/null
+++ b/src/actions/payment.js
@@ -0,0 +1,8 @@
1import PropTypes from 'prop-types';
2
3export default {
4 createHostedPage: {
5 planId: PropTypes.string.isRequired,
6 },
7 createDashboardUrl: {},
8};
diff --git a/src/actions/recipe.js b/src/actions/recipe.js
new file mode 100644
index 000000000..29b0a151f
--- /dev/null
+++ b/src/actions/recipe.js
@@ -0,0 +1,9 @@
1import PropTypes from 'prop-types';
2
3export default {
4 install: {
5 recipeId: PropTypes.string.isRequired,
6 update: PropTypes.bool,
7 },
8 update: {},
9};
diff --git a/src/actions/recipePreview.js b/src/actions/recipePreview.js
new file mode 100644
index 000000000..36de3d844
--- /dev/null
+++ b/src/actions/recipePreview.js
@@ -0,0 +1,7 @@
1import PropTypes from 'prop-types';
2
3export default {
4 search: {
5 needle: PropTypes.string.isRequired,
6 },
7};
diff --git a/src/actions/requests.js b/src/actions/requests.js
new file mode 100644
index 000000000..89296e7ec
--- /dev/null
+++ b/src/actions/requests.js
@@ -0,0 +1,3 @@
1export default {
2 retryRequiredRequests: {},
3};
diff --git a/src/actions/service.js b/src/actions/service.js
new file mode 100644
index 000000000..7f429ca32
--- /dev/null
+++ b/src/actions/service.js
@@ -0,0 +1,75 @@
1import PropTypes from 'prop-types';
2
3export default {
4 setActive: {
5 serviceId: PropTypes.string.isRequired,
6 },
7 showAddServiceInterface: {
8 recipeId: PropTypes.string.isRequired,
9 },
10 createService: {
11 recipeId: PropTypes.string.isRequired,
12 serviceData: PropTypes.object.isRequired,
13 },
14 createFromLegacyService: {
15 data: PropTypes.object.isRequired,
16 },
17 updateService: {
18 serviceId: PropTypes.string.isRequired,
19 serviceData: PropTypes.object.isRequired,
20 redirect: PropTypes.bool,
21 },
22 deleteService: {
23 serviceId: PropTypes.string.isRequired,
24 redirect: PropTypes.string,
25 },
26 setUnreadMessageCount: {
27 serviceId: PropTypes.string.isRequired,
28 count: PropTypes.object.isRequired,
29 },
30 setWebviewReference: {
31 serviceId: PropTypes.string.isRequired,
32 webview: PropTypes.object.isRequired,
33 },
34 focusService: {
35 serviceId: PropTypes.string.isRequired,
36 },
37 focusActiveService: {},
38 toggleService: {
39 serviceId: PropTypes.string.isRequired,
40 },
41 handleIPCMessage: {
42 serviceId: PropTypes.string.isRequired,
43 channel: PropTypes.string.isRequired,
44 args: PropTypes.array.isRequired,
45 },
46 sendIPCMessage: {
47 serviceId: PropTypes.string.isRequired,
48 channel: PropTypes.string.isRequired,
49 args: PropTypes.object.isRequired,
50 },
51 openWindow: {
52 event: PropTypes.object.isRequired,
53 },
54 reload: {
55 serviceId: PropTypes.string.isRequired,
56 },
57 reloadActive: {},
58 reloadAll: {},
59 reloadUpdatedServices: {},
60 filter: {
61 needle: PropTypes.string.isRequired,
62 },
63 resetFilter: {},
64 reorder: {
65 oldIndex: PropTypes.number.isRequired,
66 newIndex: PropTypes.number.isRequired,
67 },
68 toggleNotifications: {
69 serviceId: PropTypes.string.isRequired,
70 },
71 openDevTools: {
72 serviceId: PropTypes.string.isRequired,
73 },
74 openDevToolsForActiveService: {},
75};
diff --git a/src/actions/settings.js b/src/actions/settings.js
new file mode 100644
index 000000000..3d53cd674
--- /dev/null
+++ b/src/actions/settings.js
@@ -0,0 +1,10 @@
1import PropTypes from 'prop-types';
2
3export default {
4 update: {
5 settings: PropTypes.object.isRequired,
6 },
7 remove: {
8 key: PropTypes.string.isRequired,
9 },
10};
diff --git a/src/actions/ui.js b/src/actions/ui.js
new file mode 100644
index 000000000..b913b430b
--- /dev/null
+++ b/src/actions/ui.js
@@ -0,0 +1,11 @@
1import PropTypes from 'prop-types';
2
3export default {
4 openSettings: {
5 path: PropTypes.string,
6 },
7 closeSettings: {},
8 toggleServiceUpdatedInfoBar: {
9 visible: PropTypes.bool,
10 },
11};
diff --git a/src/actions/user.js b/src/actions/user.js
new file mode 100644
index 000000000..fe32b8a05
--- /dev/null
+++ b/src/actions/user.js
@@ -0,0 +1,30 @@
1import PropTypes from 'prop-types';
2
3export default {
4 login: {
5 email: PropTypes.string.isRequired,
6 password: PropTypes.string.isRequired,
7 },
8 logout: {},
9 signup: {
10 firstname: PropTypes.string.isRequired,
11 lastname: PropTypes.string.isRequired,
12 email: PropTypes.string.isRequired,
13 password: PropTypes.string.isRequired,
14 accountType: PropTypes.string.isRequired,
15 company: PropTypes.string,
16 },
17 retrievePassword: {
18 email: PropTypes.string.isRequired,
19 },
20 invite: {
21 invites: PropTypes.array.isRequired,
22 },
23 update: {
24 userData: PropTypes.object.isRequired,
25 },
26 resetStatus: {},
27 importLegacyServices: PropTypes.arrayOf(PropTypes.shape({
28 recipe: PropTypes.string.isRequired,
29 })).isRequired,
30};
diff --git a/src/api/AppApi.js b/src/api/AppApi.js
new file mode 100644
index 000000000..411c187f4
--- /dev/null
+++ b/src/api/AppApi.js
@@ -0,0 +1,9 @@
1export default class AppApi {
2 constructor(server) {
3 this.server = server;
4 }
5
6 health() {
7 return this.server.healthCheck();
8 }
9}
diff --git a/src/api/LocalApi.js b/src/api/LocalApi.js
new file mode 100644
index 000000000..6f2b049d6
--- /dev/null
+++ b/src/api/LocalApi.js
@@ -0,0 +1,18 @@
1export default class LocalApi {
2 constructor(server, local) {
3 this.server = server;
4 this.local = local;
5 }
6
7 getSettings() {
8 return this.local.getAppSettings();
9 }
10
11 updateSettings(data) {
12 return this.local.updateAppSettings(data);
13 }
14
15 removeKey(key) {
16 return this.local.removeKey(key);
17 }
18}
diff --git a/src/api/NewsApi.js b/src/api/NewsApi.js
new file mode 100644
index 000000000..294957511
--- /dev/null
+++ b/src/api/NewsApi.js
@@ -0,0 +1,14 @@
1export default class NewsApi {
2 constructor(server, local) {
3 this.server = server;
4 this.local = local;
5 }
6
7 latest() {
8 return this.server.getLatestNews();
9 }
10
11 hide(id) {
12 return this.server.hideNews(id);
13 }
14}
diff --git a/src/api/PaymentApi.js b/src/api/PaymentApi.js
new file mode 100644
index 000000000..3f6bb442e
--- /dev/null
+++ b/src/api/PaymentApi.js
@@ -0,0 +1,22 @@
1export default class PaymentApi {
2 constructor(server, local) {
3 this.server = server;
4 this.local = local;
5 }
6
7 plans() {
8 return this.server.getPlans();
9 }
10
11 getHostedPage(planId) {
12 return this.server.getHostedPage(planId);
13 }
14
15 getDashboardUrl() {
16 return this.server.getPaymentDashboardUrl();
17 }
18
19 getOrders() {
20 return this.server.getSubscriptionOrders();
21 }
22}
diff --git a/src/api/RecipePreviewsApi.js b/src/api/RecipePreviewsApi.js
new file mode 100644
index 000000000..d9c675d76
--- /dev/null
+++ b/src/api/RecipePreviewsApi.js
@@ -0,0 +1,17 @@
1export default class ServicesApi {
2 constructor(server) {
3 this.server = server;
4 }
5
6 all() {
7 return this.server.getRecipePreviews();
8 }
9
10 featured() {
11 return this.server.getFeaturedRecipePreviews();
12 }
13
14 search(needle) {
15 return this.server.searchRecipePreviews(needle);
16 }
17}
diff --git a/src/api/RecipesApi.js b/src/api/RecipesApi.js
new file mode 100644
index 000000000..0573dacaf
--- /dev/null
+++ b/src/api/RecipesApi.js
@@ -0,0 +1,17 @@
1export default class ServicesApi {
2 constructor(server) {
3 this.server = server;
4 }
5
6 all() {
7 return this.server.getInstalledRecipes();
8 }
9
10 install(recipeId) {
11 return this.server.getRecipePackage(recipeId);
12 }
13
14 update(recipes) {
15 return this.server.getRecipeUpdates(recipes);
16 }
17}
diff --git a/src/api/ServicesApi.js b/src/api/ServicesApi.js
new file mode 100644
index 000000000..3cb40ba0d
--- /dev/null
+++ b/src/api/ServicesApi.js
@@ -0,0 +1,33 @@
1export default class ServicesApi {
2 constructor(server) {
3 this.server = server;
4 }
5
6 all() {
7 return this.server.getServices();
8 }
9
10 // one(customerId) {
11 // return this.server.getCustomer(customerId);
12 // }
13 //
14 // search(needle) {
15 // return this.server.searchCustomers(needle);
16 // }
17 //
18 create(recipeId, data) {
19 return this.server.createService(recipeId, data);
20 }
21
22 delete(serviceId) {
23 return this.server.deleteService(serviceId);
24 }
25
26 update(serviceId, data) {
27 return this.server.updateService(serviceId, data);
28 }
29
30 reorder(data) {
31 return this.server.reorderService(data);
32 }
33}
diff --git a/src/api/UserApi.js b/src/api/UserApi.js
new file mode 100644
index 000000000..e8fd75bed
--- /dev/null
+++ b/src/api/UserApi.js
@@ -0,0 +1,49 @@
1import { hash } from '../helpers/password-helpers';
2
3export default class UserApi {
4 constructor(server, local) {
5 this.server = server;
6 this.local = local;
7 }
8
9 login(email, password) {
10 return this.server.login(email, hash(password));
11 }
12
13 logout() {
14 return this;
15 }
16
17 signup(data) {
18 Object.assign(data, {
19 password: hash(data.password),
20 });
21 return this.server.signup(data);
22 }
23
24 password(email) {
25 return this.server.retrievePassword(email);
26 }
27
28 invite(data) {
29 return this.server.inviteUser(data);
30 }
31
32 getInfo() {
33 return this.server.userInfo();
34 }
35
36 updateInfo(data) {
37 const userData = data;
38 if (userData.oldPassword && userData.newPassword) {
39 userData.oldPassword = hash(userData.oldPassword);
40 userData.newPassword = hash(userData.newPassword);
41 }
42
43 return this.server.updateUserInfo(userData);
44 }
45
46 getLegacyServices() {
47 return this.server.getLegacyServices();
48 }
49}
diff --git a/src/api/index.js b/src/api/index.js
new file mode 100644
index 000000000..3fc18c4b5
--- /dev/null
+++ b/src/api/index.js
@@ -0,0 +1,19 @@
1import AppApi from './AppApi';
2import ServicesApi from './ServicesApi';
3import RecipePreviewsApi from './RecipePreviewsApi';
4import RecipesApi from './RecipesApi';
5import UserApi from './UserApi';
6import LocalApi from './LocalApi';
7import PaymentApi from './PaymentApi';
8import NewsApi from './NewsApi';
9
10export default (server, local) => ({
11 app: new AppApi(server, local),
12 services: new ServicesApi(server, local),
13 recipePreviews: new RecipePreviewsApi(server, local),
14 recipes: new RecipesApi(server, local),
15 user: new UserApi(server, local),
16 local: new LocalApi(server, local),
17 payment: new PaymentApi(server, local),
18 news: new NewsApi(server, local),
19});
diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js
new file mode 100644
index 000000000..79ac6e12f
--- /dev/null
+++ b/src/api/server/LocalApi.js
@@ -0,0 +1,33 @@
1export default class LocalApi {
2 // App
3 async updateAppSettings(data) {
4 const currentSettings = await this.getAppSettings();
5 const settings = Object.assign(currentSettings, data);
6
7 localStorage.setItem('app', JSON.stringify(settings));
8 console.debug('LocalApi::updateAppSettings resolves', settings);
9
10 return settings;
11 }
12
13 async getAppSettings() {
14 const settingsString = localStorage.getItem('app');
15 try {
16 const settings = JSON.parse(settingsString) || {};
17 console.debug('LocalApi::getAppSettings resolves', settings);
18
19 return settings;
20 } catch (err) {
21 return {};
22 }
23 }
24
25 async removeKey(key) {
26 const settings = await this.getAppSettings();
27
28 if (Object.hasOwnProperty.call(settings, key)) {
29 delete settings[key];
30 localStorage.setItem('app', JSON.stringify(settings));
31 }
32 }
33}
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js
new file mode 100644
index 000000000..b369796e8
--- /dev/null
+++ b/src/api/server/ServerApi.js
@@ -0,0 +1,574 @@
1import os from 'os';
2import path from 'path';
3import targz from 'tar.gz';
4import fs from 'fs-extra';
5import { remote } from 'electron';
6
7import ServiceModel from '../../models/Service';
8import RecipePreviewModel from '../../models/RecipePreview';
9import RecipeModel from '../../models/Recipe';
10import PlanModel from '../../models/Plan';
11import NewsModel from '../../models/News';
12import UserModel from '../../models/User';
13import OrderModel from '../../models/Order';
14
15import { API } from '../../environment';
16
17import {
18 getRecipeDirectory,
19 getDevRecipeDirectory,
20 loadRecipeConfig,
21} from '../../helpers/recipe-helpers';
22
23module.paths.unshift(
24 getDevRecipeDirectory(),
25 getRecipeDirectory(),
26);
27
28const { app } = remote;
29const fetch = remote.require('electron-fetch');
30
31const SERVER_URL = API;
32const API_VERSION = 'v1';
33
34export default class ServerApi {
35 recipePreviews = [];
36 recipes = [];
37
38 // User
39 async login(email, passwordHash) {
40 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/auth/login`, this._prepareAuthRequest({
41 method: 'POST',
42 headers: {
43 Authorization: `Basic ${window.btoa(`${email}:${passwordHash}`)}`,
44 },
45 }, false));
46 if (!request.ok) {
47 throw request;
48 }
49 const u = await request.json();
50
51 console.debug('ServerApi::login resolves', u);
52 return u.token;
53 }
54
55 async signup(data) {
56 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/auth/signup`, this._prepareAuthRequest({
57 method: 'POST',
58 body: JSON.stringify(data),
59 }, false));
60 if (!request.ok) {
61 throw request;
62 }
63 const u = await request.json();
64
65 console.debug('ServerApi::signup resolves', u);
66 return u.token;
67 }
68
69 async inviteUser(data) {
70 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/invite`, this._prepareAuthRequest({
71 method: 'POST',
72 body: JSON.stringify(data),
73 }));
74 if (!request.ok) {
75 throw request;
76 }
77
78 console.debug('ServerApi::inviteUser');
79 return true;
80 }
81
82 async retrievePassword(email) {
83 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/auth/password`, this._prepareAuthRequest({
84 method: 'POST',
85 body: JSON.stringify({
86 email,
87 }),
88 }, false));
89 if (!request.ok) {
90 throw request;
91 }
92 const r = await request.json();
93
94 console.debug('ServerApi::retrievePassword');
95 return r;
96 }
97
98 async userInfo() {
99 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me`, this._prepareAuthRequest({
100 method: 'GET',
101 }));
102 if (!request.ok) {
103 throw request;
104 }
105 const data = await request.json();
106
107 const user = new UserModel(data);
108 console.debug('ServerApi::userInfo resolves', user);
109
110 return user;
111 }
112
113 async updateUserInfo(data) {
114 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me`, this._prepareAuthRequest({
115 method: 'PUT',
116 body: JSON.stringify(data),
117 }));
118 if (!request.ok) {
119 throw request;
120 }
121 const updatedData = await request.json();
122
123 const user = Object.assign(updatedData, { data: new UserModel(updatedData.data) });
124 console.debug('ServerApi::updateUserInfo resolves', user);
125 return user;
126 }
127
128 // Services
129 async getServices() {
130 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me/services`, this._prepareAuthRequest({
131 method: 'GET',
132 }));
133 if (!request.ok) {
134 throw request;
135 }
136 const data = await request.json();
137
138 let services = await this._mapServiceModels(data);
139 services = services.filter(service => service !== null);
140 console.debug('ServerApi::getServices resolves', services);
141 return services;
142 }
143
144 async createService(recipeId, data) {
145 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service`, this._prepareAuthRequest({
146 method: 'POST',
147 body: JSON.stringify(Object.assign({
148 recipeId,
149 }, data)),
150 }));
151 if (!request.ok) {
152 throw request;
153 }
154 const serviceData = await request.json();
155 const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) });
156
157 console.debug('ServerApi::createService resolves', service);
158 return service;
159 }
160
161 async updateService(recipeId, data) {
162 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${recipeId}`, this._prepareAuthRequest({
163 method: 'PUT',
164 body: JSON.stringify(data),
165 }));
166 if (!request.ok) {
167 throw request;
168 }
169 const serviceData = await request.json();
170 const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) });
171
172 console.debug('ServerApi::updateService resolves', service);
173 return service;
174 }
175
176 async reorderService(data) {
177 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/reorder`, this._prepareAuthRequest({
178 method: 'PUT',
179 body: JSON.stringify(data),
180 }));
181 if (!request.ok) {
182 throw request;
183 }
184 const serviceData = await request.json();
185 console.debug('ServerApi::reorderService resolves', serviceData);
186 return serviceData;
187 }
188
189 async deleteService(id) {
190 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${id}`, this._prepareAuthRequest({
191 method: 'DELETE',
192 }));
193 if (!request.ok) {
194 throw request;
195 }
196 const data = await request.json();
197
198 console.debug('ServerApi::deleteService resolves', data);
199 return data;
200 }
201
202 // Recipes
203 async getInstalledRecipes() {
204 const recipesDirectory = getRecipeDirectory();
205 const paths = fs.readdirSync(recipesDirectory)
206 .filter(file => (
207 fs.statSync(path.join(recipesDirectory, file)).isDirectory()
208 && file !== 'temp'
209 && file !== 'dev'
210 ));
211
212 this.recipes = paths.map((id) => {
213 // eslint-disable-next-line
214 const Recipe = require(id)(RecipeModel);
215 return new Recipe(loadRecipeConfig(id));
216 }).filter(recipe => recipe.id);
217
218 this.recipes = this.recipes.concat(this._getDevRecipes());
219
220 console.debug('StubServerApi::getInstalledRecipes resolves', this.recipes);
221 return this.recipes;
222 }
223
224 async getRecipeUpdates(recipeVersions) {
225 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes/update`, this._prepareAuthRequest({
226 method: 'POST',
227 body: JSON.stringify(recipeVersions),
228 }));
229 if (!request.ok) {
230 throw request;
231 }
232 const recipes = await request.json();
233 console.debug('ServerApi::getRecipeUpdates resolves', recipes);
234 return recipes;
235 }
236
237 // Recipes Previews
238 async getRecipePreviews() {
239 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes`, this._prepareAuthRequest({
240 method: 'GET',
241 }));
242 if (!request.ok) {
243 throw request;
244 }
245 const data = await request.json();
246
247 const recipePreviews = this._mapRecipePreviewModel(data);
248 console.debug('ServerApi::getRecipes resolves', recipePreviews);
249
250 return recipePreviews;
251 }
252
253 async getFeaturedRecipePreviews() {
254 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes/popular`, this._prepareAuthRequest({
255 method: 'GET',
256 }));
257 if (!request.ok) {
258 throw request;
259 }
260 const data = await request.json();
261
262 // data = this._addLocalRecipesToPreviews(data);
263
264 const recipePreviews = this._mapRecipePreviewModel(data);
265 console.debug('ServerApi::getFeaturedRecipes resolves', recipePreviews);
266 return recipePreviews;
267 }
268
269 async searchRecipePreviews(needle) {
270 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes/search?needle=${needle}`, this._prepareAuthRequest({
271 method: 'GET',
272 }));
273 if (!request.ok) {
274 throw request;
275 }
276 const data = await request.json();
277
278 const recipePreviews = this._mapRecipePreviewModel(data);
279 console.debug('ServerApi::searchRecipePreviews resolves', recipePreviews);
280 return recipePreviews;
281 }
282
283 async getRecipePackage(recipeId) {
284 try {
285 const recipesDirectory = path.join(app.getPath('userData'), 'recipes');
286
287 const recipeTempDirectory = path.join(recipesDirectory, 'temp', recipeId);
288 const archivePath = path.join(recipeTempDirectory, 'recipe.tar.gz');
289 const packageUrl = `${SERVER_URL}/${API_VERSION}/recipes/download/${recipeId}`;
290
291 fs.ensureDirSync(recipeTempDirectory);
292 const res = await fetch(packageUrl);
293 const buffer = await res.buffer();
294 fs.writeFileSync(archivePath, buffer);
295
296 await targz().extract(archivePath, recipeTempDirectory);
297
298 const { id } = fs.readJsonSync(path.join(recipeTempDirectory, 'package.json'));
299 const recipeDirectory = path.join(recipesDirectory, id);
300
301 fs.copySync(recipeTempDirectory, recipeDirectory);
302 fs.remove(recipeTempDirectory);
303 fs.remove(path.join(recipesDirectory, recipeId, 'recipe.tar.gz'));
304
305 return id;
306 } catch (err) {
307 console.error(err);
308
309 return false;
310 }
311 }
312
313 // Payment
314 async getPlans() {
315 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/payment/plans`, this._prepareAuthRequest({
316 method: 'GET',
317 }));
318 if (!request.ok) {
319 throw request;
320 }
321 const data = await request.json();
322
323 const plan = new PlanModel(data);
324 console.debug('ServerApi::getPlans resolves', plan);
325 return plan;
326 }
327
328 async getHostedPage(planId) {
329 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/payment/init`, this._prepareAuthRequest({
330 method: 'POST',
331 body: JSON.stringify({
332 planId,
333 }),
334 }));
335 if (!request.ok) {
336 throw request;
337 }
338 const data = await request.json();
339
340 console.debug('ServerApi::getHostedPage resolves', data);
341 return data;
342 }
343
344 async getPaymentDashboardUrl() {
345 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me/billing`, this._prepareAuthRequest({
346 method: 'GET',
347 }));
348 if (!request.ok) {
349 throw request;
350 }
351 const data = await request.json();
352
353 console.debug('ServerApi::getPaymentDashboardUrl resolves', data);
354 return data;
355 }
356
357 async getSubscriptionOrders() {
358 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me/subscription`, this._prepareAuthRequest({
359 method: 'GET',
360 }));
361 if (!request.ok) {
362 throw request;
363 }
364 const data = await request.json();
365 const orders = this._mapOrderModels(data);
366 console.debug('ServerApi::getSubscriptionOrders resolves', orders);
367 return orders;
368 }
369
370 // News
371 async getLatestNews() {
372 // eslint-disable-next-line
373 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/news?platform=${os.platform()}&arch=${os.arch()}version=${app.getVersion()}`,
374 this._prepareAuthRequest({
375 method: 'GET',
376 }));
377
378 if (!request.ok) {
379 throw request;
380 }
381 const data = await request.json();
382 const news = this._mapNewsModels(data);
383 console.debug('ServerApi::getLatestNews resolves', news);
384 return news;
385 }
386
387 async hideNews(id) {
388 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/news/${id}/read`,
389 this._prepareAuthRequest({
390 method: 'GET',
391 }));
392
393 if (!request.ok) {
394 throw request;
395 }
396
397 console.debug('ServerApi::hideNews resolves', id);
398 }
399
400 // Health Check
401 async healthCheck() {
402 const request = await window.fetch(`${SERVER_URL}/health`, this._prepareAuthRequest({
403 method: 'GET',
404 }, false));
405 if (!request.ok) {
406 throw request;
407 }
408 console.debug('ServerApi::healthCheck resolves');
409 }
410
411 async getLegacyServices() {
412 const file = path.join(app.getPath('userData'), 'settings', 'services.json');
413
414 try {
415 const config = fs.readJsonSync(file);
416
417 if (Object.prototype.hasOwnProperty.call(config, 'services')) {
418 const services = await Promise.all(config.services.map(async (s) => {
419 const service = s;
420 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes/${s.service}`,
421 this._prepareAuthRequest({
422 method: 'GET',
423 }),
424 );
425
426 if (request.status === 200) {
427 const data = await request.json();
428 service.recipe = new RecipePreviewModel(data);
429 }
430
431 return service;
432 }));
433
434 console.debug('ServerApi::getLegacyServices resolves', services);
435 return services;
436 }
437 } catch (err) {
438 throw (new Error('ServerApi::getLegacyServices no config found'));
439 }
440
441 return [];
442 }
443
444 // Helper
445 async _mapServiceModels(services) {
446 return Promise.all(services
447 .map(async service => await this._prepareServiceModel(service)) // eslint-disable-line
448 );
449 }
450
451 async _prepareServiceModel(service) {
452 let recipe;
453 try {
454 recipe = this.recipes.find(r => r.id === service.recipeId);
455
456 if (!recipe) {
457 console.warn(`Recipe '${service.recipeId}' not installed, trying to fetch from server`);
458
459 await this.getRecipePackage(service.recipeId);
460
461 console.debug('Rerun ServerAPI::getInstalledRecipes');
462 await this.getInstalledRecipes();
463
464 recipe = this.recipes.find(r => r.id === service.recipeId);
465
466 if (!recipe) {
467 console.warn(`Could not load recipe ${service.recipeId}`);
468 return null;
469 }
470 }
471
472 return new ServiceModel(service, recipe);
473 } catch (e) {
474 console.debug(e);
475 return null;
476 }
477 }
478
479 _mapRecipePreviewModel(recipes) {
480 return recipes.map((recipe) => {
481 try {
482 return new RecipePreviewModel(recipe);
483 } catch (e) {
484 console.error(e);
485 return null;
486 }
487 }).filter(recipe => recipe !== null);
488 }
489
490 _mapNewsModels(news) {
491 return news.map((newsItem) => {
492 try {
493 return new NewsModel(newsItem);
494 } catch (e) {
495 console.error(e);
496 return null;
497 }
498 }).filter(newsItem => newsItem !== null);
499 }
500
501 _mapOrderModels(orders) {
502 return orders.map((orderItem) => {
503 try {
504 return new OrderModel(orderItem);
505 } catch (e) {
506 console.error(e);
507 return null;
508 }
509 }).filter(orderItem => orderItem !== null);
510 }
511
512 _prepareAuthRequest(options, auth = true) {
513 const request = Object.assign(options, {
514 mode: 'cors',
515 headers: {
516 'Content-Type': 'application/json',
517 'X-Franz-Source': 'desktop',
518 'X-Franz-Version': app.getVersion(),
519 'X-Franz-platform': process.platform,
520 'X-Franz-Timezone-Offset': new Date().getTimezoneOffset(),
521 'X-Franz-System-Locale': app.getLocale(),
522 },
523 });
524
525 // const headers = new window.Headers();
526 // headers.append('foo', 'bar');
527 // console.log(headers, request.headers);
528 //
529 //
530 // // request.headers.map((value, header) => headers.append(header, value));
531 // Object.keys(request.headers).map((key) => {
532 // console.log(key);
533 // return headers.append(key, request.headers[key]);
534 // });
535 // request.headers = headers;
536
537 // console.log(request);
538
539 if (auth) {
540 request.headers.Authorization = `Bearer ${localStorage.getItem('authToken')}`;
541 }
542
543 return request;
544 }
545
546 _getDevRecipes() {
547 const recipesDirectory = getDevRecipeDirectory();
548 try {
549 const paths = fs.readdirSync(recipesDirectory)
550 .filter(file => fs.statSync(path.join(recipesDirectory, file)).isDirectory() && file !== 'temp');
551
552 const recipes = paths.map((id) => {
553 // eslint-disable-next-line
554 const Recipe = require(id)(RecipeModel);
555 return new Recipe(loadRecipeConfig(id));
556 }).filter(recipe => recipe.id).map((data) => {
557 const recipe = data;
558
559 recipe.icons = {
560 svg: `${recipe.path}/icon.svg`,
561 png: `${recipe.path}/icon.png`,
562 };
563 recipe.local = true;
564
565 return data;
566 });
567
568 return recipes;
569 } catch (err) {
570 console.debug('Folder `recipe/dev` does not exist');
571 return false;
572 }
573 }
574}
diff --git a/src/app.js b/src/app.js
new file mode 100644
index 000000000..b539ea494
--- /dev/null
+++ b/src/app.js
@@ -0,0 +1,103 @@
1import { webFrame } from 'electron';
2
3import React from 'react';
4import { render } from 'react-dom';
5import { Provider } from 'mobx-react';
6import { syncHistoryWithStore, RouterStore } from 'mobx-react-router';
7import { Router, Route, hashHistory, IndexRedirect } from 'react-router';
8
9import 'babel-polyfill';
10import smoothScroll from 'smoothscroll-polyfill';
11
12import ServerApi from './api/server/ServerApi';
13import LocalApi from './api/server/LocalApi';
14import storeFactory from './stores';
15import apiFactory from './api';
16import actions from './actions';
17import MenuFactory from './lib/Menu';
18import TouchBarFactory from './lib/TouchBar';
19import * as analytics from './lib/analytics';
20
21import I18N from './I18n';
22import AppLayoutContainer from './containers/layout/AppLayoutContainer';
23import SettingsWindow from './containers/settings/SettingsWindow';
24import RecipesScreen from './containers/settings/RecipesScreen';
25import ServicesScreen from './containers/settings/ServicesScreen';
26import EditServiceScreen from './containers/settings/EditServiceScreen';
27import AccountScreen from './containers/settings/AccountScreen';
28import EditUserScreen from './containers/settings/EditUserScreen';
29import EditSettingsScreen from './containers/settings/EditSettingsScreen';
30import WelcomeScreen from './containers/auth/WelcomeScreen';
31import LoginScreen from './containers/auth/LoginScreen';
32import PasswordScreen from './containers/auth/PasswordScreen';
33import SignupScreen from './containers/auth/SignupScreen';
34import ImportScreen from './containers/auth/ImportScreen';
35import PricingScreen from './containers/auth/PricingScreen';
36import InviteScreen from './containers/auth/InviteScreen';
37import AuthLayoutContainer from './containers/auth/AuthLayoutContainer';
38import SubscriptionPopupScreen from './containers/ui/SubscriptionPopupScreen';
39
40// Add Polyfills
41smoothScroll.polyfill();
42
43// Basic electron Setup
44webFrame.setVisualZoomLevelLimits(1, 1);
45webFrame.setLayoutZoomLevelLimits(0, 0);
46
47window.addEventListener('load', () => {
48 const api = apiFactory(new ServerApi(), new LocalApi());
49 const router = new RouterStore();
50 const history = syncHistoryWithStore(hashHistory, router);
51 const stores = storeFactory(api, actions, router);
52 const menu = new MenuFactory(stores, actions);
53 const touchBar = new TouchBarFactory(stores, actions);
54
55 window.franz = {
56 stores,
57 actions,
58 api,
59 menu,
60 touchBar,
61 analytics,
62 render() {
63 const preparedApp = (
64 <Provider stores={stores} actions={actions}>
65 <I18N>
66 <Router history={history}>
67 <Route path="/" component={AppLayoutContainer}>
68 <Route path="/settings" component={SettingsWindow}>
69 <IndexRedirect to="/settings/recipes" />
70 <Route path="/settings/recipes" component={RecipesScreen} />
71 <Route path="/settings/recipes/:filter" component={RecipesScreen} />
72 <Route path="/settings/services" component={ServicesScreen} />
73 <Route path="/settings/services/:action/:id" component={EditServiceScreen} />
74 <Route path="/settings/user" component={AccountScreen} />
75 <Route path="/settings/user/edit" component={EditUserScreen} />
76 <Route path="/settings/app" component={EditSettingsScreen} />
77 </Route>
78 </Route>
79 <Route path="/auth" component={AuthLayoutContainer}>
80 <IndexRedirect to="/auth/welcome" />
81 <Route path="/auth/welcome" component={WelcomeScreen} />
82 <Route path="/auth/login" component={LoginScreen} />
83 <Route path="/auth/signup">
84 <IndexRedirect to="/auth/signup/form" />
85 <Route path="/auth/signup/form" component={SignupScreen} />
86 <Route path="/auth/signup/pricing" component={PricingScreen} />
87 <Route path="/auth/signup/import" component={ImportScreen} />
88 <Route path="/auth/signup/invite" component={InviteScreen} />
89 </Route>
90 <Route path="/auth/password" component={PasswordScreen} />
91 <Route path="/auth/logout" component={LoginScreen} />
92 </Route>
93 <Route path="/payment/:url" component={SubscriptionPopupScreen} />
94 <Route path="*" component={AppLayoutContainer} />
95 </Router>
96 </I18N>
97 </Provider>
98 );
99 render(preparedApp, document.getElementById('root'));
100 },
101 };
102 window.franz.render();
103});
diff --git a/src/assets/fonts/OpenSans-Bold.ttf b/src/assets/fonts/OpenSans-Bold.ttf
new file mode 100755
index 000000000..fd79d43be
--- /dev/null
+++ b/src/assets/fonts/OpenSans-Bold.ttf
Binary files differ
diff --git a/src/assets/fonts/OpenSans-BoldItalic.ttf b/src/assets/fonts/OpenSans-BoldItalic.ttf
new file mode 100755
index 000000000..9bc800958
--- /dev/null
+++ b/src/assets/fonts/OpenSans-BoldItalic.ttf
Binary files differ
diff --git a/src/assets/fonts/OpenSans-ExtraBold.ttf b/src/assets/fonts/OpenSans-ExtraBold.ttf
new file mode 100755
index 000000000..21f6f84a0
--- /dev/null
+++ b/src/assets/fonts/OpenSans-ExtraBold.ttf
Binary files differ
diff --git a/src/assets/fonts/OpenSans-ExtraBoldItalic.ttf b/src/assets/fonts/OpenSans-ExtraBoldItalic.ttf
new file mode 100755
index 000000000..31cb68834
--- /dev/null
+++ b/src/assets/fonts/OpenSans-ExtraBoldItalic.ttf
Binary files differ
diff --git a/src/assets/fonts/OpenSans-Light.ttf b/src/assets/fonts/OpenSans-Light.ttf
new file mode 100755
index 000000000..0d381897d
--- /dev/null
+++ b/src/assets/fonts/OpenSans-Light.ttf
Binary files differ
diff --git a/src/assets/fonts/OpenSans-Regular.ttf b/src/assets/fonts/OpenSans-Regular.ttf
new file mode 100755
index 000000000..db433349b
--- /dev/null
+++ b/src/assets/fonts/OpenSans-Regular.ttf
Binary files differ
diff --git a/src/assets/images/adlk.svg b/src/assets/images/adlk.svg
new file mode 100644
index 000000000..eb50f345a
--- /dev/null
+++ b/src/assets/images/adlk.svg
@@ -0,0 +1,53 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<svg width="120px" height="65px" viewBox="0 0 120 65" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3 <!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
4 <title>adlk-group</title>
5 <desc>Created with Sketch.</desc>
6 <defs>
7 <filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-1">
8 <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
9 <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
10 <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
11 <feMerge>
12 <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
13 <feMergeNode in="SourceGraphic"></feMergeNode>
14 </feMerge>
15 </filter>
16 <linearGradient x1="30.4719662%" y1="95.2408507%" x2="68.063052%" y2="3.5648771%" id="linearGradient-2">
17 <stop stop-color="#318CC8" offset="0%"></stop>
18 <stop stop-color="#5EC0FF" offset="100%"></stop>
19 </linearGradient>
20 <path d="M29.5671186,59.9675705 C13.237647,59.9675705 0,46.7299236 0,30.4004519 C0,14.0709803 13.237647,0.833333333 29.5671186,0.833333333 C45.8965902,0.833333333 59.1342372,14.0709803 59.1342372,30.4004519 C59.1342372,46.7299236 45.8965902,59.9675705 29.5671186,59.9675705 Z" id="path-3"></path>
21 <filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-4">
22 <feMorphology radius="1.25" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
23 <feOffset dx="0" dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
24 <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
25 <feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"></feComposite>
26 <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
27 </filter>
28 </defs>
29 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
30 <g id="logo" transform="translate(-1.000000, 0.000000)">
31 <g id="adlk-group" transform="translate(4.000000, 1.000000)">
32 <g id="Page-1" filter="url(#filter-1)" transform="translate(53.333333, 0.000000)">
33 <path d="M29.951734,59.6153846 C13.6222624,59.6153846 0.384615385,46.3777376 0.384615385,30.048266 C0.384615385,13.7187944 13.6222624,0.481147424 29.951734,0.481147424 C46.2812056,0.481147424 59.5188526,13.7187944 59.5188526,30.048266 C59.5188526,46.3777376 46.2812056,59.6153846 29.951734,59.6153846 Z" id="Fill-1" stroke="#FFFFFF" stroke-width="2.5" fill="url(#linearGradient-2)"></path>
34 <path d="M40.8193218,39.7759515 C40.2991334,39.7759515 39.777752,39.7461242 39.2560451,39.6885303 L39.2560451,43.1660699 C39.2560451,44.2684875 40.4320006,44.8564652 42.0489665,44.8564652 C43.6659324,44.8564652 47.3407661,45.1505083 48.8106562,42.5780922 C49.5666431,41.2550609 50.049954,39.777253 50.341611,38.5380637 C47.7706048,39.3483905 44.5464347,39.7759515 40.8193218,39.7759515" id="Fill-3" fill="#FEFEFE"></path>
35 <path d="M50.721253,36.0215602 C50.7213615,36.014727 50.7215784,36.0067007 50.7215784,36.0001929 C50.7215784,35.7047398 50.6497759,34.0216115 50.5562809,31.8479052 C50.5492308,31.8480137 50.5428315,31.8482306 50.5356729,31.8482306 C48.6476581,31.8482306 45.8548452,30.9165338 44.4370173,29.8136823 C44.0945997,29.5474057 43.7123762,29.1917557 43.3041216,28.7844772 C41.4215299,31.2340049 39.6248409,34.689418 39.3072613,37.4257217 C39.8033709,37.4891725 40.3081576,37.5235553 40.8193435,37.5235553 C44.8576366,37.5235553 48.2724847,36.9929545 50.721253,36.0215602" id="Fill-5" fill="#FEFEFE"></path>
36 <path d="M45.8200177,28.0358333 C46.832411,28.8233829 49.0605657,29.5681981 50.4596295,29.5945546 C50.3027921,25.9124538 50.1335898,21.76201 50.1335898,20.3820357 C50.1335898,17.809728 50.2079954,15.494586 49.3986448,15.494586 C47.3407769,15.494586 47.8552168,24.4244504 45.2828006,26.6292856 C45.1214077,26.7676843 44.956544,26.9196409 44.7892941,27.0838539 C45.1781339,27.4760559 45.5348686,27.8140265 45.8200177,28.0358333" id="Fill-7" fill="#FEFEFE"></path>
37 <path d="M10.0610578,29.5501499 C11.4368021,29.3757414 13.2421681,28.7220351 14.1242974,28.0358984 C14.475609,27.7625717 14.9354919,27.3128843 15.4306254,26.8023491 C15.3653307,26.7431283 15.3002529,26.6843414 15.2360428,26.6293506 C12.6636267,24.424407 13.1780665,15.4945426 11.1201987,15.4945426 C10.3109565,15.4945426 10.3852536,17.8096846 10.3852536,20.3821008 C10.3852536,21.756435 10.2173529,25.8792208 10.0610578,29.5501499" id="Fill-9" fill="#FEFEFE"></path>
38 <path d="M21.2005731,37.3395804 C20.8402592,34.4934037 18.9129807,30.8970972 16.9604304,28.4603682 C16.4311312,29.0030084 15.9377331,29.4788355 15.5071352,29.8137691 C14.2229336,30.812713 11.8100665,31.6713058 9.96359298,31.8240217 C9.86966405,34.0092251 9.79721079,35.7036335 9.79721079,36.0001713 C9.79721079,36.0653575 9.80024775,36.1457286 9.80577936,36.2371628 C12.2015093,37.0708092 15.3970452,37.5235336 19.1249174,37.5235336 C19.8311197,37.5235336 20.5248489,37.4573712 21.2005731,37.3395804" id="Fill-11" fill="#FEFEFE"></path>
39 <path d="M19.1249499,39.7759515 C15.6917717,39.7759515 12.6843105,39.4137936 10.2218758,38.7234269 C10.517329,39.9228103 10.9894682,41.3204641 11.7081439,42.5780922 C13.1781424,45.1505083 16.8529762,44.8564652 18.4698336,44.8564652 C20.0867995,44.8564652 21.262755,44.2684875 21.262755,43.1660699 L21.262755,39.6133655 C20.549394,39.7209608 19.8361416,39.7759515 19.1249499,39.7759515" id="Fill-13" fill="#FEFEFE"></path>
40 <path d="M52.954538,31.2252844 C52.5192762,31.2252844 52.057007,31.3746379 51.5802038,31.6691149 C51.5121975,31.7110901 51.2357254,31.8481872 50.5357054,31.8481872 C48.6475821,31.8481872 45.8548777,30.9165988 44.4370498,29.8137474 C43.7290036,29.2630809 42.8517551,28.3324687 41.9229867,27.3472996 C39.9696772,25.2753315 38.1247221,23.3183342 36.5281473,23.3183342 C35.3931908,23.3183342 34.0862121,23.8471997 32.8221847,24.3588195 C31.7693347,24.784862 30.6805834,25.2254386 29.9721033,25.2254386 C29.2636232,25.2254386 28.1749804,24.784862 27.1221304,24.3588195 C25.858103,23.8471997 24.5510158,23.3183342 23.4161678,23.3183342 C21.8194845,23.3183342 19.974421,25.27544 18.0212199,27.347408 C17.0924515,28.3325771 16.2152031,29.2630809 15.5071568,29.8137474 C14.0895459,30.9164904 11.296733,31.8480788 9.40850121,31.8481872 C8.7085897,31.8481872 8.4321176,31.7110901 8.36411132,31.6691149 C7.88730812,31.3746379 7.4249305,31.2252844 6.98977708,31.2252844 C6.3738159,31.2252844 5.85416983,31.547311 5.63366462,32.0656555 C5.36185643,32.7047193 5.58821864,33.4896658 6.23899643,34.1654985 C8.29393578,36.2996158 12.9907076,37.5235119 19.1249391,37.5235119 C23.1527113,37.5235119 26.7888237,35.4106533 28.8485355,33.4333735 C29.2664433,33.0322774 29.8115781,33.0118864 29.9165703,33.0118864 L29.9199326,33.0114525 L29.9396729,33.0186111 L30.0136446,33.0121033 L30.0276364,33.0118864 C30.1326285,33.0118864 30.6778718,33.0322774 31.0957796,33.4333735 C33.1554914,35.4106533 36.7914954,37.5235119 40.8192676,37.5235119 C46.9534991,37.5235119 51.6502709,36.2996158 53.7053187,34.1654985 C54.3560965,33.4897742 54.5822418,32.7047193 54.310542,32.0656555 C54.0900368,31.547311 53.5703908,31.2252844 52.954538,31.2252844" id="Fill-15" fill="#FEFEFE"></path>
41 </g>
42 <g id="Fill-1">
43 <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use>
44 <use stroke="#FFFFFF" stroke-width="2.5" fill="#E51F5B" fill-rule="evenodd" xlink:href="#path-3"></use>
45 </g>
46 <path d="M18.53613,36.1383525 C18.402105,36.1383525 18.134205,36.1215525 17.882955,36.0378525 C17.531205,35.9373525 17.045505,35.6693775 16.44258,34.1787525 C15.42093,31.6330275 12.79143,24.7158525 12.54018,24.0794775 C12.339255,23.5602525 12.272205,23.4764775 12.171705,23.4764775 C12.071205,23.4764775 12.00423,23.5769775 11.803305,24.1297275 L8.001405,34.5305025 C7.699905,35.3679525 7.364955,36.0043275 6.59448,36.1048275 C6.46053,36.1215525 6.30978,36.1383525 6.192555,36.1383525 C6.041805,36.1383525 5.95803,36.1718025 5.95803,36.2388525 C5.95803,36.3393525 6.05853,36.3728025 6.276255,36.3728025 C7.063455,36.3728025 7.984605,36.3225525 8.16888,36.3225525 C8.35308,36.3225525 9.023055,36.3728025 9.42498,36.3728025 C9.57573,36.3728025 9.72648,36.3393525 9.72648,36.2388525 C9.72648,36.1718025 9.67623,36.1383525 9.508755,36.1383525 L9.358005,36.1383525 C9.073305,36.1383525 8.755005,35.9876025 8.755005,35.6525775 C8.755005,35.3511525 8.83878,34.9492275 8.98953,34.5305025 L11.76978,26.1897525 C11.85348,25.9385025 11.937255,25.9385025 12.020955,26.1897525 L15.47118,35.6525775 C15.57168,35.9206275 15.47118,36.0713025 15.37068,36.1048275 C15.303705,36.1215525 15.253455,36.1550775 15.253455,36.2220525 C15.253455,36.3225525 15.437655,36.3225525 15.75588,36.3393525 C16.911555,36.3728025 18.134205,36.3728025 18.38538,36.3728025 C18.569655,36.3728025 18.720405,36.3393525 18.720405,36.2388525 C18.720405,36.1550775 18.63663,36.1383525 18.53613,36.1383525" id="Fill-3" fill="#FEFEFE"></path>
47 <path d="M22.0194975,30.37683 C22.0194975,32.15223 22.0362225,33.693105 22.0530225,34.011255 C22.0697475,34.42998 22.1032725,35.099955 22.2372225,35.284155 C22.4549475,35.60238 23.1081225,35.954055 25.0844475,35.954055 C26.6420475,35.954055 28.0824225,35.384655 29.0706225,34.39653 C29.9415225,33.54228 30.4104225,31.934505 30.4104225,30.39363 C30.4104225,28.266555 29.4892725,26.893155 28.8025725,26.173005 C27.2282475,24.514905 25.3189725,24.280455 23.3258475,24.280455 C22.9908975,24.280455 22.3711725,24.330705 22.2372225,24.397605 C22.0864725,24.464655 22.0362225,24.548355 22.0362225,24.73263 C22.0194975,25.302105 22.0194975,27.01038 22.0194975,28.30008 L22.0194975,30.37683 Z M20.6126475,28.568055 C20.6126475,25.95528 20.6126475,25.486305 20.5791225,24.950355 C20.5456725,24.380955 20.4618975,24.112905 19.9092225,23.99568 C19.7751975,23.96223 19.4904975,23.94543 19.3397475,23.94543 C19.2225225,23.94543 19.1554725,23.91198 19.1554725,23.84493 C19.1554725,23.74443 19.2392475,23.71098 19.4235225,23.71098 C20.1771975,23.71098 21.2322975,23.76123 21.3160725,23.76123 C21.5170725,23.76123 22.5721725,23.71098 23.5770975,23.71098 C25.2351975,23.71098 28.3001475,23.56023 30.2932725,25.620255 C31.1307225,26.491155 31.9177725,27.881355 31.9177725,29.874405 C31.9177725,31.98468 31.0469475,33.60933 30.1089975,34.597455 C29.3887725,35.35113 27.8814225,36.52353 25.0341975,36.52353 C24.3140475,36.52353 23.4263475,36.47328 22.7061975,36.42303 C21.9692475,36.37278 21.3997725,36.32253 21.3160725,36.32253 C21.2825475,36.32253 20.9978475,36.32253 20.6460975,36.33933 C20.3111475,36.33933 19.9259475,36.37278 19.6746975,36.37278 C19.4904975,36.37278 19.4067225,36.33933 19.4067225,36.23883 C19.4067225,36.18858 19.4402475,36.13833 19.5741975,36.13833 C19.7584725,36.121605 19.8924225,36.104805 20.0431725,36.071355 C20.3781225,36.004305 20.4618975,35.63583 20.5288725,35.150205 C20.6126475,34.44678 20.6126475,33.12363 20.6126475,31.51578 L20.6126475,28.568055 Z" id="Fill-5" fill="#FEFEFE"></path>
48 <path d="M35.61885,31.54929 C35.61885,34.01124 35.685825,35.08314 35.98725,35.38464 C36.2553,35.652615 36.690675,35.76984 37.997025,35.76984 C38.884725,35.76984 39.621675,35.753115 40.0236,35.267415 C40.241325,34.99944 40.408875,34.580715 40.459125,34.26249 C40.47585,34.12854 40.509375,34.044765 40.609875,34.044765 C40.69365,34.044765 40.710375,34.11174 40.710375,34.296015 C40.710375,34.480215 40.59315,35.48514 40.459125,35.97084 C40.341975,36.35604 40.291575,36.423015 39.404025,36.423015 C37.96365,36.423015 36.539925,36.32259 34.915425,36.32259 C34.396125,36.32259 33.8937,36.372765 33.27405,36.372765 C33.089775,36.372765 33.006,36.339315 33.006,36.238815 C33.006,36.188565 33.039525,36.13839 33.140025,36.13839 C33.290775,36.13839 33.491775,36.10479 33.64245,36.07134 C33.977475,36.00429 34.06125,35.635815 34.128225,35.15019 C34.212,34.446765 34.212,33.123615 34.212,31.515765 L34.212,28.56804 C34.212,25.955265 34.212,25.48629 34.178475,24.950415 C34.14495,24.38094 34.011075,24.112965 33.458175,23.995665 C33.3243,23.962215 33.039525,23.945415 32.88885,23.945415 C32.821875,23.945415 32.75475,23.911965 32.75475,23.844915 C32.75475,23.74449 32.8386,23.710965 33.022875,23.710965 C33.77655,23.710965 34.83165,23.761215 34.915425,23.761215 C34.99905,23.761215 36.221775,23.710965 36.7242,23.710965 C36.908475,23.710965 36.992175,23.74449 36.992175,23.844915 C36.992175,23.911965 36.9252,23.945415 36.858225,23.945415 C36.740925,23.945415 36.50655,23.962215 36.30555,23.995665 C35.81985,24.07944 35.685825,24.36414 35.652225,24.950415 C35.61885,25.48629 35.61885,25.955265 35.61885,28.56804 L35.61885,31.54929 Z" id="Fill-7" fill="#FEFEFE"></path>
49 <path d="M44.428275,29.63994 C44.947575,29.137515 47.2254,26.74239 48.246975,25.68729 C49.251825,24.64884 49.31895,24.48144 49.31895,24.28044 C49.31895,24.146415 49.235175,24.012465 49.10115,23.962215 C48.983925,23.911965 48.9504,23.878515 48.9504,23.811465 C48.9504,23.74449 49.0509,23.710965 49.20165,23.710965 C49.68735,23.710965 49.6371,23.761215 50.2065,23.761215 C50.7258,23.761215 51.71385,23.710965 51.9819,23.710965 C52.216425,23.710965 52.266675,23.761215 52.266675,23.828265 C52.266675,23.895165 52.233075,23.928765 52.0824,23.945415 C51.76425,23.979015 51.46275,24.045915 51.228225,24.146415 C50.809425,24.31389 50.491275,24.51489 49.48635,25.48629 C48.01245,26.90994 46.103175,28.80249 45.66765,29.288265 C46.722825,30.443865 49.771125,33.542265 50.558175,34.279215 C51.93165,35.568915 52.51785,35.93739 53.321775,36.08814 C53.472525,36.12159 53.639925,36.13839 53.80755,36.13839 C53.941425,36.13839 54.041925,36.17184 54.041925,36.25554 C54.041925,36.339315 53.97495,36.372765 53.790675,36.372765 L52.5513,36.372765 C51.0942,36.372765 50.708925,36.188565 49.921875,35.568915 C48.916875,34.781715 45.902175,31.54929 44.428275,29.79069 L44.428275,31.515765 C44.428275,33.123615 44.428275,34.446765 44.51205,35.15019 C44.5623,35.635815 44.6628,36.00429 45.165225,36.07134 C45.39975,36.10479 45.734775,36.13839 45.835275,36.13839 C45.986025,36.13839 46.0362,36.20529 46.0362,36.25554 C46.0362,36.339315 45.96915,36.372765 45.785025,36.372765 C44.8638,36.372765 43.808625,36.32259 43.72485,36.32259 C43.641225,36.32259 42.653025,36.372765 42.1506,36.372765 C41.966325,36.372765 41.88255,36.35604 41.88255,36.25554 C41.88255,36.20529 41.916075,36.13839 42.066825,36.13839 C42.167325,36.13839 42.3516,36.12159 42.502275,36.08814 C42.8373,36.02109 42.9378,35.635815 43.004775,35.15019 C43.08855,34.446765 43.08855,33.123615 43.08855,31.515765 L43.08855,28.56804 C43.08855,25.955265 43.08855,25.48629 43.05495,24.950415 C43.021425,24.38094 42.854025,24.096165 42.485475,24.012465 C42.30135,23.962215 41.99985,23.945415 41.88255,23.945415 C41.7486,23.945415 41.698425,23.911965 41.698425,23.844915 C41.698425,23.74449 41.78205,23.710965 41.966325,23.710965 C42.519,23.710965 43.641225,23.761215 43.72485,23.761215 C43.808625,23.761215 44.8638,23.710965 45.366225,23.710965 C45.5505,23.710965 45.634275,23.74449 45.634275,23.828265 C45.634275,23.895165 45.60075,23.928765 45.45,23.945415 C45.265725,23.962215 45.249,23.962215 45.0816,23.979015 C44.629275,24.02919 44.4954,24.36414 44.4618,24.950415 C44.428275,25.48629 44.428275,25.955265 44.428275,28.56804 L44.428275,29.63994 Z" id="Fill-9" fill="#FEFEFE"></path>
50 </g>
51 </g>
52 </g>
53</svg> \ No newline at end of file
diff --git a/src/assets/images/emoji/dontknow.png b/src/assets/images/emoji/dontknow.png
new file mode 100644
index 000000000..a4473d862
--- /dev/null
+++ b/src/assets/images/emoji/dontknow.png
Binary files differ
diff --git a/src/assets/images/emoji/sad.png b/src/assets/images/emoji/sad.png
new file mode 100644
index 000000000..b8b6ff69b
--- /dev/null
+++ b/src/assets/images/emoji/sad.png
Binary files differ
diff --git a/src/assets/images/emoji/star.png b/src/assets/images/emoji/star.png
new file mode 100755
index 000000000..0b9aa67da
--- /dev/null
+++ b/src/assets/images/emoji/star.png
Binary files differ
diff --git a/src/assets/images/logo.svg b/src/assets/images/logo.svg
new file mode 100644
index 000000000..87188f4aa
--- /dev/null
+++ b/src/assets/images/logo.svg
@@ -0,0 +1,35 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<svg width="80px" height="80px" viewBox="0 0 80 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3 <!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
4 <title>franz</title>
5 <desc>Created with Sketch.</desc>
6 <defs>
7 <filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-1">
8 <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
9 <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
10 <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
11 <feMerge>
12 <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
13 <feMergeNode in="SourceGraphic"></feMergeNode>
14 </feMerge>
15 </filter>
16 <linearGradient x1="30.4719662%" y1="95.2408507%" x2="68.063052%" y2="3.5648771%" id="linearGradient-2">
17 <stop stop-color="#318CC8" offset="0%"></stop>
18 <stop stop-color="#5EC0FF" offset="100%"></stop>
19 </linearGradient>
20 </defs>
21 <g id="icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
22 <g id="franz">
23 <g id="Page-1" filter="url(#filter-1)" transform="translate(4.000000, 4.000000)">
24 <path d="M35.9420808,71.5384615 C16.3467148,71.5384615 0.461538462,55.6532852 0.461538462,36.0579192 C0.461538462,16.4625533 16.3467148,0.577376909 35.9420808,0.577376909 C55.5374467,0.577376909 71.4226231,16.4625533 71.4226231,36.0579192 C71.4226231,55.6532852 55.5374467,71.5384615 35.9420808,71.5384615 Z" id="Fill-1" stroke="#FFFFFF" stroke-width="2.5" fill="url(#linearGradient-2)"></path>
25 <path d="M48.9831862,47.7311418 C48.3589601,47.7311418 47.7333024,47.695349 47.1072541,47.6262364 L47.1072541,51.7992839 C47.1072541,53.122185 48.5184007,53.8277583 50.4587598,53.8277583 C52.3991188,53.8277583 56.8089193,54.18061 58.5727874,51.0937106 C59.4799717,49.5060731 60.0599449,47.7327036 60.4099332,46.2456764 C57.3247258,47.2180686 53.4557216,47.7311418 48.9831862,47.7311418" id="Fill-3" fill="#FEFEFE"></path>
26 <path d="M60.8655036,43.2258722 C60.8656338,43.2176724 60.8658941,43.2080409 60.8658941,43.2002315 C60.8658941,42.8456878 60.7797311,40.8259338 60.667537,38.2174863 C60.6590769,38.2176164 60.6513977,38.2178768 60.6428075,38.2178768 C58.3771897,38.2178768 55.0258142,37.0998405 53.3244207,35.7764188 C52.9135196,35.4568869 52.4548514,35.0301068 51.9649459,34.5413727 C49.7058359,37.4808058 47.549809,41.6273016 47.1687136,44.910866 C47.7640451,44.9870071 48.3697891,45.0282664 48.9832122,45.0282664 C53.829164,45.0282664 57.9269816,44.3915454 60.8655036,43.2258722" id="Fill-5" fill="#FEFEFE"></path>
27 <path d="M54.9840212,33.643 C56.1988932,34.5880595 58.8726789,35.4818377 60.5515554,35.5134655 C60.3633505,31.0949446 60.1603078,26.114412 60.1603078,24.4584428 C60.1603078,21.3716736 60.2495945,18.5935032 59.2783737,18.5935032 C56.8089323,18.5935032 57.4262601,29.3093405 54.3393608,31.9551427 C54.1456893,32.1212212 53.9478528,32.3035691 53.747153,32.5006246 C54.2137606,32.9712671 54.6418423,33.3768319 54.9840212,33.643" id="Fill-7" fill="#FEFEFE"></path>
28 <path d="M12.0732694,35.4601798 C13.7241625,35.2508897 15.8906017,34.4664421 16.9491569,33.6430781 C17.3707308,33.3150861 17.9225903,32.7754611 18.5167504,32.1628189 C18.4383968,32.091754 18.3603035,32.0212096 18.2832514,31.9552208 C15.196352,29.3092884 15.8136798,18.5934511 13.3442384,18.5934511 C12.3731478,18.5934511 12.4623043,21.3716215 12.4623043,24.4585209 C12.4623043,26.107722 12.2608235,31.0550649 12.0732694,35.4601798" id="Fill-9" fill="#FEFEFE"></path>
29 <path d="M25.4406878,44.8074965 C25.008311,41.3920845 22.6955769,37.0765166 20.3525165,34.1524418 C19.7173574,34.8036101 19.1252797,35.3746025 18.6085622,35.7765229 C17.0675204,36.9752556 14.1720798,38.005567 11.9563116,38.188826 C11.8435969,40.8110701 11.7566529,42.8443602 11.7566529,43.2002055 C11.7566529,43.278429 11.7602973,43.3748743 11.7669352,43.4845954 C14.6418112,44.484971 18.4764543,45.0282403 22.9499008,45.0282403 C23.7973437,45.0282403 24.6298186,44.9488454 25.4406878,44.8074965" id="Fill-11" fill="#FEFEFE"></path>
30 <path d="M22.9499399,47.7311418 C18.830126,47.7311418 15.2211727,47.2965524 12.266251,46.4681122 C12.6207947,47.9073724 13.1873619,49.5845569 14.0497726,51.0937106 C15.8137709,54.18061 20.2235714,53.8277583 22.1638003,53.8277583 C24.1041594,53.8277583 25.515306,53.122185 25.515306,51.7992839 L25.515306,47.5360386 C24.6592728,47.6651529 23.8033699,47.7311418 22.9499399,47.7311418" id="Fill-13" fill="#FEFEFE"></path>
31 <path d="M63.5454456,37.4703413 C63.0231314,37.4703413 62.4684084,37.6495655 61.8962446,38.0029379 C61.814637,38.0533081 61.4828705,38.2178247 60.6428465,38.2178247 C58.3770986,38.2178247 55.0258532,37.0999186 53.3244598,35.7764969 C52.4748043,35.1156971 51.4221061,33.9989624 50.3075841,32.8167595 C47.9636126,30.3303978 45.7496666,27.9820011 43.8337767,27.9820011 C42.471829,27.9820011 40.9034545,28.6166396 39.3866216,29.2305834 C38.1232016,29.7418344 36.8167001,30.2705263 35.966524,30.2705263 C35.1163479,30.2705263 33.8099765,29.7418344 32.5465565,29.2305834 C31.0297236,28.6166396 29.461219,27.9820011 28.0994014,27.9820011 C26.1833814,27.9820011 23.9693052,30.330528 21.6254639,32.8168896 C20.5109419,33.9990926 19.4582437,35.1156971 18.6085882,35.7764969 C16.9074551,37.0997884 13.5560796,38.2176945 11.2902015,38.2178247 C10.4503076,38.2178247 10.1185411,38.0533081 10.0369336,38.0029379 C9.46476974,37.6495655 8.9099166,37.4703413 8.3877325,37.4703413 C7.64857908,37.4703413 7.0250038,37.8567732 6.76039755,38.4787866 C6.43422772,39.2456631 6.70586237,40.1875989 7.48679572,40.9985982 C9.95272293,43.559539 15.5888491,45.0282143 22.9499269,45.0282143 C27.7832535,45.0282143 32.1465885,42.492784 34.6182426,40.1200482 C35.1197319,39.6387329 35.7738938,39.6142637 35.8998843,39.6142637 L35.9039192,39.6137431 L35.9276075,39.6223333 L36.0163736,39.614524 L36.0331636,39.6142637 C36.1591542,39.6142637 36.8134462,39.6387329 37.3149356,40.1200482 C39.7865896,42.492784 44.1497944,45.0282143 48.9831211,45.0282143 C56.3441989,45.0282143 61.980325,43.559539 64.4463824,40.9985982 C65.2273158,40.1877291 65.4986901,39.2456631 65.1726504,38.4787866 C64.9080442,37.8567732 64.2844689,37.4703413 63.5454456,37.4703413" id="Fill-15" fill="#FEFEFE"></path>
32 </g>
33 </g>
34 </g>
35</svg> \ No newline at end of file
diff --git a/src/assets/images/sm.png b/src/assets/images/sm.png
new file mode 100644
index 000000000..2bf169bee
--- /dev/null
+++ b/src/assets/images/sm.png
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-1.ico b/src/assets/images/taskbar/win32/taskbar-1.ico
new file mode 100644
index 000000000..32ba334f3
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-1.ico
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-10.ico b/src/assets/images/taskbar/win32/taskbar-10.ico
new file mode 100644
index 000000000..815a02ef7
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-10.ico
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-2.ico b/src/assets/images/taskbar/win32/taskbar-2.ico
new file mode 100644
index 000000000..975295f3c
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-2.ico
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-3.ico b/src/assets/images/taskbar/win32/taskbar-3.ico
new file mode 100644
index 000000000..a3798e486
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-3.ico
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-4.ico b/src/assets/images/taskbar/win32/taskbar-4.ico
new file mode 100644
index 000000000..cd01ce0bf
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-4.ico
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-5.ico b/src/assets/images/taskbar/win32/taskbar-5.ico
new file mode 100644
index 000000000..7119a5544
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-5.ico
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-6.ico b/src/assets/images/taskbar/win32/taskbar-6.ico
new file mode 100644
index 000000000..17f41f04b
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-6.ico
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-7.ico b/src/assets/images/taskbar/win32/taskbar-7.ico
new file mode 100644
index 000000000..c4aa16200
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-7.ico
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-8.ico b/src/assets/images/taskbar/win32/taskbar-8.ico
new file mode 100644
index 000000000..896ff7b4b
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-8.ico
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-9.ico b/src/assets/images/taskbar/win32/taskbar-9.ico
new file mode 100644
index 000000000..e3613e344
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-9.ico
Binary files differ
diff --git a/src/assets/images/taskbar/win32/taskbar-alert.ico b/src/assets/images/taskbar/win32/taskbar-alert.ico
new file mode 100644
index 000000000..5b349c2b6
--- /dev/null
+++ b/src/assets/images/taskbar/win32/taskbar-alert.ico
Binary files differ
diff --git a/src/assets/images/tray/darwin/tray-active.png b/src/assets/images/tray/darwin/tray-active.png
new file mode 100644
index 000000000..489533dbf
--- /dev/null
+++ b/src/assets/images/tray/darwin/tray-active.png
Binary files differ
diff --git a/src/assets/images/tray/darwin/tray-active@2x.png b/src/assets/images/tray/darwin/tray-active@2x.png
new file mode 100644
index 000000000..76f212b52
--- /dev/null
+++ b/src/assets/images/tray/darwin/tray-active@2x.png
Binary files differ
diff --git a/src/assets/images/tray/darwin/tray-unread-active.png b/src/assets/images/tray/darwin/tray-unread-active.png
new file mode 100644
index 000000000..e2fd1a822
--- /dev/null
+++ b/src/assets/images/tray/darwin/tray-unread-active.png
Binary files differ
diff --git a/src/assets/images/tray/darwin/tray-unread-active@2x.png b/src/assets/images/tray/darwin/tray-unread-active@2x.png
new file mode 100644
index 000000000..9a64b3ef8
--- /dev/null
+++ b/src/assets/images/tray/darwin/tray-unread-active@2x.png
Binary files differ
diff --git a/src/assets/images/tray/darwin/tray-unread.png b/src/assets/images/tray/darwin/tray-unread.png
new file mode 100644
index 000000000..a94ad81fb
--- /dev/null
+++ b/src/assets/images/tray/darwin/tray-unread.png
Binary files differ
diff --git a/src/assets/images/tray/darwin/tray-unread@2x.png b/src/assets/images/tray/darwin/tray-unread@2x.png
new file mode 100644
index 000000000..56e74b16a
--- /dev/null
+++ b/src/assets/images/tray/darwin/tray-unread@2x.png
Binary files differ
diff --git a/src/assets/images/tray/darwin/tray.png b/src/assets/images/tray/darwin/tray.png
new file mode 100644
index 000000000..583f34df8
--- /dev/null
+++ b/src/assets/images/tray/darwin/tray.png
Binary files differ
diff --git a/src/assets/images/tray/darwin/tray@2x.png b/src/assets/images/tray/darwin/tray@2x.png
new file mode 100644
index 000000000..479a2cf95
--- /dev/null
+++ b/src/assets/images/tray/darwin/tray@2x.png
Binary files differ
diff --git a/src/assets/images/tray/linux/tray-unread.png b/src/assets/images/tray/linux/tray-unread.png
new file mode 100644
index 000000000..a94ad81fb
--- /dev/null
+++ b/src/assets/images/tray/linux/tray-unread.png
Binary files differ
diff --git a/src/assets/images/tray/linux/tray-unread@2x.png b/src/assets/images/tray/linux/tray-unread@2x.png
new file mode 100644
index 000000000..56e74b16a
--- /dev/null
+++ b/src/assets/images/tray/linux/tray-unread@2x.png
Binary files differ
diff --git a/src/assets/images/tray/linux/tray.png b/src/assets/images/tray/linux/tray.png
new file mode 100644
index 000000000..583f34df8
--- /dev/null
+++ b/src/assets/images/tray/linux/tray.png
Binary files differ
diff --git a/src/assets/images/tray/linux/tray@2x.png b/src/assets/images/tray/linux/tray@2x.png
new file mode 100644
index 000000000..479a2cf95
--- /dev/null
+++ b/src/assets/images/tray/linux/tray@2x.png
Binary files differ
diff --git a/src/assets/images/tray/win32/tray-unread.ico b/src/assets/images/tray/win32/tray-unread.ico
new file mode 100644
index 000000000..a59428cfb
--- /dev/null
+++ b/src/assets/images/tray/win32/tray-unread.ico
Binary files differ
diff --git a/src/assets/images/tray/win32/tray-unread@2x.ico b/src/assets/images/tray/win32/tray-unread@2x.ico
new file mode 100644
index 000000000..f6fe65093
--- /dev/null
+++ b/src/assets/images/tray/win32/tray-unread@2x.ico
Binary files differ
diff --git a/src/assets/images/tray/win32/tray.ico b/src/assets/images/tray/win32/tray.ico
new file mode 100644
index 000000000..d3aa25d68
--- /dev/null
+++ b/src/assets/images/tray/win32/tray.ico
Binary files differ
diff --git a/src/assets/images/tray/win32/tray@2x.ico b/src/assets/images/tray/win32/tray@2x.ico
new file mode 100644
index 000000000..c1b7a73a5
--- /dev/null
+++ b/src/assets/images/tray/win32/tray@2x.ico
Binary files differ
diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js
new file mode 100644
index 000000000..2741b8a15
--- /dev/null
+++ b/src/components/auth/AuthLayout.js
@@ -0,0 +1,88 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { RouteTransition } from 'react-router-transition';
5import { intlShape } from 'react-intl';
6
7import Link from '../ui/Link';
8import InfoBar from '../ui/InfoBar';
9
10import { oneOrManyChildElements, globalError as globalErrorPropType } from '../../prop-types';
11import globalMessages from '../../i18n/globalMessages';
12
13@observer
14export default class AuthLayout extends Component {
15 static propTypes = {
16 children: oneOrManyChildElements.isRequired,
17 pathname: PropTypes.string.isRequired,
18 error: globalErrorPropType.isRequired,
19 isOnline: PropTypes.bool.isRequired,
20 isAPIHealthy: PropTypes.bool.isRequired,
21 retryHealthCheck: PropTypes.func.isRequired,
22 isHealthCheckLoading: PropTypes.bool.isRequired,
23 };
24
25 static contextTypes = {
26 intl: intlShape,
27 };
28
29 render() {
30 const {
31 children,
32 pathname,
33 error,
34 isOnline,
35 isAPIHealthy,
36 retryHealthCheck,
37 isHealthCheckLoading,
38 } = this.props;
39 const { intl } = this.context;
40
41 return (
42 <div className="auth">
43 {!isOnline && (
44 <InfoBar
45 type="warning"
46 >
47 <span className="mdi mdi-flash" />
48 {intl.formatMessage(globalMessages.notConnectedToTheInternet)}
49 </InfoBar>
50 )}
51 {isOnline && !isAPIHealthy && (
52 <InfoBar
53 type="danger"
54 ctaLabel="Try again"
55 ctaLoading={isHealthCheckLoading}
56 sticky
57 onClick={retryHealthCheck}
58 >
59 <span className="mdi mdi-flash" />
60 {intl.formatMessage(globalMessages.APIUnhealthy)}
61 </InfoBar>
62 )}
63 <div className="auth__layout">
64 <RouteTransition
65 pathname={pathname}
66 atEnter={{ opacity: 0 }}
67 atLeave={{ opacity: 0 }}
68 atActive={{ opacity: 1 }}
69 mapStyles={styles => ({
70 transform: `translateX(${styles.translateX}%)`,
71 opacity: styles.opacity,
72 })}
73 component="span"
74 >
75 {/* Inject globalError into children */}
76 {React.cloneElement(children, {
77 error,
78 })}
79 </RouteTransition>
80 </div>
81 {/* </div> */}
82 <Link to="https://adlk.io" className="auth__adlk" target="_blank">
83 <img src="./assets/images/adlk.svg" alt="" />
84 </Link>
85 </div>
86 );
87 }
88}
diff --git a/src/components/auth/Import.js b/src/components/auth/Import.js
new file mode 100644
index 000000000..cf83aa9c8
--- /dev/null
+++ b/src/components/auth/Import.js
@@ -0,0 +1,168 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import { Link } from 'react-router';
6import classnames from 'classnames';
7
8import Form from '../../lib/Form';
9import Toggle from '../ui/Toggle';
10import Button from '../ui/Button';
11
12const messages = defineMessages({
13 headline: {
14 id: 'import.headline',
15 defaultMessage: '!!!Import your Franz 4 services',
16 },
17 notSupportedHeadline: {
18 id: 'import.notSupportedHeadline',
19 defaultMessage: '!!!Services not yet supported in Franz 5',
20 },
21 submitButtonLabel: {
22 id: 'import.submit.label',
23 defaultMessage: '!!!Import {count} services',
24 },
25 skipButtonLabel: {
26 id: 'import.skip.label',
27 defaultMessage: '!!!I want add services manually',
28 },
29});
30
31@observer
32export default class Import extends Component {
33 static propTypes = {
34 services: MobxPropTypes.arrayOrObservableArray.isRequired,
35 onSubmit: PropTypes.func.isRequired,
36 isSubmitting: PropTypes.bool.isRequired,
37 inviteRoute: PropTypes.string.isRequired,
38 };
39
40 static contextTypes = {
41 intl: intlShape,
42 };
43
44 prepareForm() {
45 const { services } = this.props;
46
47 const config = {
48 fields: {
49 import: [...services.filter(s => s.recipe).map(s => ({
50 add: {
51 default: true,
52 options: s,
53 },
54 }))],
55 },
56 };
57
58 return new Form(config, this.context.intl);
59 }
60
61 submit(e) {
62 const { services } = this.props;
63 e.preventDefault();
64 this.form.submit({
65 onSuccess: (form) => {
66 const servicesImport = form.values().import
67 .map((value, i) => !value.add || services.filter(s => s.recipe)[i])
68 .filter(s => typeof s !== 'boolean');
69
70 this.props.onSubmit({ services: servicesImport });
71 },
72 onError: () => {},
73 });
74 }
75
76 render() {
77 this.form = this.prepareForm();
78 const { intl } = this.context;
79 const { services, isSubmitting, inviteRoute } = this.props;
80
81 const availableServices = services.filter(s => s.recipe);
82 const unavailableServices = services.filter(s => !s.recipe);
83
84 return (
85 <div className="auth__scroll-container">
86 <div className="auth__container auth__container--signup">
87 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
88 <img
89 src="./assets/images/logo.svg"
90 className="auth__logo"
91 alt=""
92 />
93 <h1>
94 {intl.formatMessage(messages.headline)}
95 </h1>
96 <table className="service-table available-services">
97 <tbody>
98 {this.form.$('import').map((service, i) => (
99 <tr
100 key={service.id}
101 className="service-table__row"
102 onClick={() => service.$('add').set(!service.$('add').value)}
103 >
104 <td className="service-table__toggle">
105 <Toggle
106 field={service.$('add')}
107 showLabel={false}
108 />
109 </td>
110 <td className="service-table__column-icon">
111 <img
112 src={availableServices[i].custom_icon || availableServices[i].recipe.icons.svg}
113 className={classnames({
114 'service-table__icon': true,
115 'has-custom-icon': availableServices[i].custom_icon,
116 })}
117 alt=""
118 />
119 </td>
120 <td className="service-table__column-name">
121 {availableServices[i].name !== ''
122 ? availableServices[i].name
123 : availableServices[i].recipe.name}
124 </td>
125 </tr>
126 ))}
127 </tbody>
128 </table>
129 {unavailableServices.length > 0 && (
130 <div className="unavailable-services">
131 <strong>{intl.formatMessage(messages.notSupportedHeadline)}</strong>
132 <p>
133 {services.filter(s => !s.recipe).map((service, i) => (
134 <span key={service.id}>
135 {service.name !== '' ? service.name : service.service}
136 {unavailableServices.length > i + 1 ? ', ' : ''}
137 </span>
138 ))}
139 </p>
140 </div>
141 )}
142
143 {isSubmitting ? (
144 <Button
145 className="auth__button is-loading"
146 label={`${intl.formatMessage(messages.submitButtonLabel)} ...`}
147 loaded={false}
148 disabled
149 />
150 ) : (
151 <Button
152 type="submit"
153 className="auth__button"
154 label={intl.formatMessage(messages.submitButtonLabel)}
155 />
156 )}
157 <Link
158 to={inviteRoute}
159 className="franz-form__button franz-form__button--secondary auth__button auth__button--skip"
160 >
161 {intl.formatMessage(messages.skipButtonLabel)}
162 </Link>
163 </form>
164 </div>
165 </div>
166 );
167 }
168}
diff --git a/src/components/auth/Invite.js b/src/components/auth/Invite.js
new file mode 100644
index 000000000..c1d815dcd
--- /dev/null
+++ b/src/components/auth/Invite.js
@@ -0,0 +1,111 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import { Link } from 'react-router';
6
7import Form from '../../lib/Form';
8import { email } from '../../helpers/validation-helpers';
9import Input from '../ui/Input';
10import Button from '../ui/Button';
11
12const messages = defineMessages({
13 headline: {
14 id: 'invite.headline.friends',
15 defaultMessage: '!!!Invite 3 of your friends or colleagues',
16 },
17 nameLabel: {
18 id: 'invite.name.label',
19 defaultMessage: '!!!Name',
20 },
21 emailLabel: {
22 id: 'invite.email.label',
23 defaultMessage: '!!!Email address',
24 },
25 submitButtonLabel: {
26 id: 'invite.submit.label',
27 defaultMessage: '!!!Send invites',
28 },
29 skipButtonLabel: {
30 id: 'invite.skip.label',
31 defaultMessage: '!!!I want to do this later',
32 },
33});
34
35@observer
36export default class Invite extends Component {
37 static propTypes = {
38 onSubmit: PropTypes.func.isRequired,
39 };
40
41 static contextTypes = {
42 intl: intlShape,
43 };
44
45 form = new Form({
46 fields: {
47 invite: [...Array(3).fill({
48 name: {
49 label: this.context.intl.formatMessage(messages.nameLabel),
50 // value: '',
51 placeholder: this.context.intl.formatMessage(messages.nameLabel),
52 },
53 email: {
54 label: this.context.intl.formatMessage(messages.emailLabel),
55 // value: '',
56 validate: [email],
57 placeholder: this.context.intl.formatMessage(messages.emailLabel),
58 },
59 })],
60 },
61 }, this.context.intl);
62
63 submit(e) {
64 e.preventDefault();
65 this.form.submit({
66 onSuccess: (form) => {
67 this.props.onSubmit({ invites: form.values().invite });
68 },
69 onError: () => {},
70 });
71 }
72
73 render() {
74 const { form } = this;
75 const { intl } = this.context;
76
77 return (
78 <div className="auth__container auth__container--signup">
79 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
80 <img
81 src="./assets/images/logo.svg"
82 className="auth__logo"
83 alt=""
84 />
85 <h1>
86 {intl.formatMessage(messages.headline)}
87 </h1>
88 {form.$('invite').map(invite => (
89 <div className="grid" key={invite.key}>
90 <div className="grid__row">
91 <Input field={invite.$('name')} showLabel={false} />
92 <Input field={invite.$('email')} showLabel={false} />
93 </div>
94 </div>
95 ))}
96 <Button
97 type="submit"
98 className="auth__button"
99 label={intl.formatMessage(messages.submitButtonLabel)}
100 />
101 <Link
102 to="/"
103 className="franz-form__button franz-form__button--secondary auth__button auth__button--skip"
104 >
105 {intl.formatMessage(messages.skipButtonLabel)}
106 </Link>
107 </form>
108 </div>
109 );
110 }
111}
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js
new file mode 100644
index 000000000..015079f02
--- /dev/null
+++ b/src/components/auth/Login.js
@@ -0,0 +1,161 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Form from '../../lib/Form';
7import { required, email } from '../../helpers/validation-helpers';
8import Input from '../ui/Input';
9import Button from '../ui/Button';
10import Link from '../ui/Link';
11
12import { globalError as globalErrorPropType } from '../../prop-types';
13
14// import Appear from '../ui/effects/Appear';
15
16const messages = defineMessages({
17 headline: {
18 id: 'login.headline',
19 defaultMessage: '!!!Sign in',
20 },
21 emailLabel: {
22 id: 'login.email.label',
23 defaultMessage: '!!!Email address',
24 },
25 passwordLabel: {
26 id: 'login.password.label',
27 defaultMessage: '!!!Password',
28 },
29 submitButtonLabel: {
30 id: 'login.submit.label',
31 defaultMessage: '!!!Sign in',
32 },
33 invalidCredentials: {
34 id: 'login.invalidCredentials',
35 defaultMessage: '!!!Email or password not valid',
36 },
37 tokenExpired: {
38 id: 'login.tokenExpired',
39 defaultMessage: '!!!Your session expired, please login again.',
40 },
41 serverLogout: {
42 id: 'login.serverLogout',
43 defaultMessage: '!!!Your session expired, please login again.',
44 },
45 signupLink: {
46 id: 'login.link.signup',
47 defaultMessage: '!!!Create a free account',
48 },
49 passwordLink: {
50 id: 'login.link.password',
51 defaultMessage: '!!!Forgot password',
52 },
53});
54
55@observer
56export default class Login extends Component {
57 static propTypes = {
58 onSubmit: PropTypes.func.isRequired,
59 isSubmitting: PropTypes.bool.isRequired,
60 isTokenExpired: PropTypes.bool.isRequired,
61 isServerLogout: PropTypes.bool.isRequired,
62 signupRoute: PropTypes.string.isRequired,
63 passwordRoute: PropTypes.string.isRequired,
64 error: globalErrorPropType.isRequired,
65 };
66
67 static contextTypes = {
68 intl: intlShape,
69 };
70
71 form = new Form({
72 fields: {
73 email: {
74 label: this.context.intl.formatMessage(messages.emailLabel),
75 value: '',
76 validate: [required, email],
77 },
78 password: {
79 label: this.context.intl.formatMessage(messages.passwordLabel),
80 value: '',
81 validate: [required],
82 type: 'password',
83 },
84 },
85 }, this.context.intl);
86
87 submit(e) {
88 e.preventDefault();
89 this.form.submit({
90 onSuccess: (form) => {
91 this.props.onSubmit(form.values());
92 },
93 onError: () => {},
94 });
95 }
96
97 emailField = null;
98
99 render() {
100 const { form } = this;
101 const { intl } = this.context;
102 const {
103 isSubmitting,
104 isTokenExpired,
105 isServerLogout,
106 signupRoute,
107 passwordRoute,
108 error,
109 } = this.props;
110
111 return (
112 <div className="auth__container">
113 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
114 <img
115 src="./assets/images/logo.svg"
116 className="auth__logo"
117 alt=""
118 />
119 <h1>{intl.formatMessage(messages.headline)}</h1>
120 {isTokenExpired && (
121 <p className="error-message center">{intl.formatMessage(messages.tokenExpired)}</p>
122 )}
123 {isServerLogout && (
124 <p className="error-message center">{intl.formatMessage(messages.serverLogout)}</p>
125 )}
126 <Input
127 field={form.$('email')}
128 ref={(element) => { this.emailField = element; }}
129 focus
130 />
131 <Input
132 field={form.$('password')}
133 showPasswordToggle
134 />
135 {error.code === 'invalid-credentials' && (
136 <p className="error-message center">{intl.formatMessage(messages.invalidCredentials)}</p>
137 )}
138 {isSubmitting ? (
139 <Button
140 className="auth__button is-loading"
141 buttonType="secondary"
142 label={`${intl.formatMessage(messages.submitButtonLabel)} ...`}
143 loaded={false}
144 disabled
145 />
146 ) : (
147 <Button
148 type="submit"
149 className="auth__button"
150 label={intl.formatMessage(messages.submitButtonLabel)}
151 />
152 )}
153 </form>
154 <div className="auth__links">
155 <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link>
156 <Link to={passwordRoute}>{intl.formatMessage(messages.passwordLink)}</Link>
157 </div>
158 </div>
159 );
160 }
161}
diff --git a/src/components/auth/Password.js b/src/components/auth/Password.js
new file mode 100644
index 000000000..d2b196853
--- /dev/null
+++ b/src/components/auth/Password.js
@@ -0,0 +1,135 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Form from '../../lib/Form';
7import { required, email } from '../../helpers/validation-helpers';
8import Input from '../ui/Input';
9import Button from '../ui/Button';
10import Link from '../ui/Link';
11import Infobox from '../ui/Infobox';
12
13const messages = defineMessages({
14 headline: {
15 id: 'password.headline',
16 defaultMessage: '!!!Forgot password',
17 },
18 emailLabel: {
19 id: 'password.email.label',
20 defaultMessage: '!!!Email address',
21 },
22 submitButtonLabel: {
23 id: 'password.submit.label',
24 defaultMessage: '!!!Submit',
25 },
26 successInfo: {
27 id: 'password.successInfo',
28 defaultMessage: '!!!Your new password was sent to your email address',
29 },
30 noUser: {
31 id: 'password.noUser',
32 defaultMessage: '!!!No user affiliated with that email address',
33 },
34 signupLink: {
35 id: 'password.link.signup',
36 defaultMessage: '!!!Create a free account',
37 },
38 loginLink: {
39 id: 'password.link.login',
40 defaultMessage: '!!!Sign in to your account',
41 },
42});
43
44@observer
45export default class Password extends Component {
46 static propTypes = {
47 onSubmit: PropTypes.func.isRequired,
48 isSubmitting: PropTypes.bool.isRequired,
49 signupRoute: PropTypes.string.isRequired,
50 loginRoute: PropTypes.string.isRequired,
51 status: MobxPropTypes.arrayOrObservableArray.isRequired,
52 };
53
54 static contextTypes = {
55 intl: intlShape,
56 };
57
58 form = new Form({
59 fields: {
60 email: {
61 label: this.context.intl.formatMessage(messages.emailLabel),
62 value: '',
63 validate: [required, email],
64 },
65 },
66 }, this.context.intl);
67
68 submit(e) {
69 e.preventDefault();
70 this.form.submit({
71 onSuccess: (form) => {
72 this.props.onSubmit(form.values());
73 },
74 onError: () => {},
75 });
76 }
77
78 render() {
79 const { form } = this;
80 const { intl } = this.context;
81 const {
82 isSubmitting,
83 signupRoute,
84 loginRoute,
85 status,
86 } = this.props;
87
88 return (
89 <div className="auth__container">
90 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
91 <img
92 src="./assets/images/logo.svg"
93 className="auth__logo"
94 alt=""
95 />
96 <h1>{intl.formatMessage(messages.headline)}</h1>
97 {status.length > 0 && status.includes('sent') && (
98 <Infobox
99 type="success"
100 icon="checkbox-marked-circle-outline"
101 >
102 {intl.formatMessage(messages.successInfo)}
103 </Infobox>
104 )}
105 <Input
106 field={form.$('email')}
107 focus
108 />
109 {status.length > 0 && status.includes('no-user') && (
110 <p className="error-message center">{intl.formatMessage(messages.noUser)}</p>
111 )}
112 {isSubmitting ? (
113 <Button
114 className="auth__button is-loading"
115 buttonType="secondary"
116 label={`${intl.formatMessage(messages.submitButtonLabel)} ...`}
117 loaded={false}
118 disabled
119 />
120 ) : (
121 <Button
122 type="submit"
123 className="auth__button"
124 label={intl.formatMessage(messages.submitButtonLabel)}
125 />
126 )}
127 </form>
128 <div className="auth__links">
129 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link>
130 <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link>
131 </div>
132 </div>
133 );
134 }
135}
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js
new file mode 100644
index 000000000..761561a89
--- /dev/null
+++ b/src/components/auth/Pricing.js
@@ -0,0 +1,130 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5// import { Link } from 'react-router';
6
7// import Button from '../ui/Button';
8import Loader from '../ui/Loader';
9import Appear from '../ui/effects/Appear';
10import SubscriptionForm from '../../containers/ui/SubscriptionFormScreen';
11
12const messages = defineMessages({
13 headline: {
14 id: 'pricing.headline',
15 defaultMessage: '!!!Support Franz',
16 },
17 monthlySupportLabel: {
18 id: 'pricing.support.label',
19 defaultMessage: '!!!Select your support plan',
20 },
21 submitButtonLabel: {
22 id: 'pricing.submit.label',
23 defaultMessage: '!!!Support the development of Franz',
24 },
25 skipPayment: {
26 id: 'pricing.link.skipPayment',
27 defaultMessage: '!!!I don\'t want to support the development of Franz.',
28 },
29});
30
31@observer
32export default class Signup extends Component {
33 static propTypes = {
34 donor: MobxPropTypes.objectOrObservableObject.isRequired,
35 isLoading: PropTypes.bool.isRequired,
36 isLoadingUser: PropTypes.bool.isRequired,
37 onCloseSubscriptionWindow: PropTypes.func.isRequired,
38 skipAction: PropTypes.func.isRequired,
39 };
40
41 static contextTypes = {
42 intl: intlShape,
43 };
44
45 render() {
46 const {
47 donor,
48 isLoading,
49 isLoadingUser,
50 onCloseSubscriptionWindow,
51 skipAction,
52 } = this.props;
53 const { intl } = this.context;
54
55 return (
56 <div className="auth__scroll-container">
57 <div className="auth__container auth__container--signup">
58 <form className="franz-form auth__form">
59 <img
60 src="./assets/images/sm.png"
61 className="auth__logo auth__logo--sm"
62 alt=""
63 />
64 <h1>{intl.formatMessage(messages.headline)}</h1>
65 <div className="auth__letter">
66 {isLoadingUser && (
67 <p>Loading</p>
68 )}
69 {!isLoadingUser && (
70 donor.amount ? (
71 <span>
72 <p>
73 Thank you so much for your previous donation of <strong>$ {donor.amount}</strong>.
74 <br />
75 Your support allowed us to get where we are today.
76 <br />
77 </p>
78 <p>
79 As an early supporter, you get <strong>a lifetime premium supporter license</strong> without any
80 additional charges.
81 </p>
82 <p>
83 However, If you want to keep supporting us, you are more than welcome to subscribe to a plan.
84 <br /><br />
85 </p>
86 </span>
87 ) : (
88 <span>
89 <p>
90 We built Franz with a lot of effort, manpower and love,
91 to bring you the best messaging experience.
92 <br />
93 </p>
94 <p>
95 Getting a Franz Premium Supporter License will allow us to keep improving Franz for you.
96 </p>
97 </span>
98 )
99 )}
100 <p>
101 Thanks for being a hero.
102 </p>
103 <p>
104 <strong>Stefan Malzner</strong>
105 </p>
106 </div>
107 <Loader loaded={!isLoading}>
108 <Appear transitionName="slideDown">
109 <span className="label">{intl.formatMessage(messages.monthlySupportLabel)}</span>
110 <SubscriptionForm
111 onCloseWindow={onCloseSubscriptionWindow}
112 showSkipOption
113 skipAction={skipAction}
114 hideInfo={Boolean(donor.amount)}
115 skipButtonLabel={intl.formatMessage(messages.skipPayment)}
116 />
117 {/* <Link
118 to={inviteRoute}
119 className="franz-form__button franz-form__button--secondary auth__button auth__button--skip"
120 >
121 {intl.formatMessage(messages.skipPayment)}
122 </Link> */}
123 </Appear>
124 </Loader>
125 </form>
126 </div>
127 </div>
128 );
129 }
130}
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js
new file mode 100644
index 000000000..71ca16111
--- /dev/null
+++ b/src/components/auth/Signup.js
@@ -0,0 +1,206 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Form from '../../lib/Form';
7import { required, email, minLength } from '../../helpers/validation-helpers';
8import Input from '../ui/Input';
9import Radio from '../ui/Radio';
10import Button from '../ui/Button';
11import Link from '../ui/Link';
12
13import { globalError as globalErrorPropType } from '../../prop-types';
14
15const messages = defineMessages({
16 headline: {
17 id: 'signup.headline',
18 defaultMessage: '!!!Sign up',
19 },
20 firstnameLabel: {
21 id: 'signup.firstname.label',
22 defaultMessage: '!!!Firstname',
23 },
24 lastnameLabel: {
25 id: 'signup.lastname.label',
26 defaultMessage: '!!!Lastname',
27 },
28 emailLabel: {
29 id: 'signup.email.label',
30 defaultMessage: '!!!Email address',
31 },
32 companyLabel: {
33 id: 'signup.company.label',
34 defaultMessage: '!!!Company',
35 },
36 passwordLabel: {
37 id: 'signup.password.label',
38 defaultMessage: '!!!Password',
39 },
40 legalInfo: {
41 id: 'signup.legal.info',
42 defaultMessage: '!!!By creating a Franz account you accept the',
43 },
44 terms: {
45 id: 'signup.legal.terms',
46 defaultMessage: '!!!Terms of service',
47 },
48 privacy: {
49 id: 'signup.legal.privacy',
50 defaultMessage: '!!!Privacy Statement',
51 },
52 submitButtonLabel: {
53 id: 'signup.submit.label',
54 defaultMessage: '!!!Create account',
55 },
56 loginLink: {
57 id: 'signup.link.login',
58 defaultMessage: '!!!Already have an account, sign in?',
59 },
60 emailDuplicate: {
61 id: 'signup.emailDuplicate',
62 defaultMessage: '!!!A user with that email address already exists',
63 },
64});
65
66@observer
67export default class Signup extends Component {
68 static propTypes = {
69 onSubmit: PropTypes.func.isRequired,
70 isSubmitting: PropTypes.bool.isRequired,
71 loginRoute: PropTypes.string.isRequired,
72 error: globalErrorPropType.isRequired,
73 };
74
75 static contextTypes = {
76 intl: intlShape,
77 };
78
79 form = new Form({
80 fields: {
81 accountType: {
82 value: 'individual',
83 validate: [required],
84 options: [{
85 value: 'individual',
86 label: 'Individual',
87 }, {
88 value: 'non-profit',
89 label: 'Non-Profit',
90 }, {
91 value: 'company',
92 label: 'Company',
93 }],
94 },
95 firstname: {
96 label: this.context.intl.formatMessage(messages.firstnameLabel),
97 value: '',
98 validate: [required],
99 },
100 lastname: {
101 label: this.context.intl.formatMessage(messages.lastnameLabel),
102 value: '',
103 validate: [required],
104 },
105 email: {
106 label: this.context.intl.formatMessage(messages.emailLabel),
107 value: '',
108 validate: [required, email],
109 },
110 organization: {
111 label: this.context.intl.formatMessage(messages.companyLabel),
112 value: '', // TODO: make required when accountType: company
113 },
114 password: {
115 label: this.context.intl.formatMessage(messages.passwordLabel),
116 value: '',
117 validate: [required, minLength(6)],
118 type: 'password',
119 },
120 },
121 }, this.context.intl);
122
123 submit(e) {
124 e.preventDefault();
125 this.form.submit({
126 onSuccess: (form) => {
127 this.props.onSubmit(form.values());
128 },
129 onError: () => {},
130 });
131 }
132
133 render() {
134 const { form } = this;
135 const { intl } = this.context;
136 const { isSubmitting, loginRoute, error } = this.props;
137
138 return (
139 <div className="auth__scroll-container">
140 <div className="auth__container auth__container--signup">
141 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
142 <img
143 src="./assets/images/logo.svg"
144 className="auth__logo"
145 alt=""
146 />
147 <h1>{intl.formatMessage(messages.headline)}</h1>
148 <Radio field={form.$('accountType')} showLabel={false} />
149 <div className="grid__row">
150 <Input field={form.$('firstname')} focus />
151 <Input field={form.$('lastname')} />
152 </div>
153 <Input field={form.$('email')} />
154 <Input
155 field={form.$('password')}
156 showPasswordToggle
157 scorePassword
158 />
159 {form.$('accountType').value === 'company' && (
160 <Input field={form.$('organization')} />
161 )}
162 {error.code === 'email-duplicate' && (
163 <p className="error-message center">{intl.formatMessage(messages.emailDuplicate)}</p>
164 )}
165 {isSubmitting ? (
166 <Button
167 className="auth__button is-loading"
168 label={`${intl.formatMessage(messages.submitButtonLabel)} ...`}
169 loaded={false}
170 disabled
171 />
172 ) : (
173 <Button
174 type="submit"
175 className="auth__button"
176 label={intl.formatMessage(messages.submitButtonLabel)}
177 />
178 )}
179 <p className="legal">
180 {intl.formatMessage(messages.legalInfo)}
181 <br />
182 <Link
183 to="http://meetfranz.com/terms"
184 target="_blank"
185 className="link"
186 >
187 {intl.formatMessage(messages.terms)}
188 </Link>
189 &nbsp;&amp;&nbsp;
190 <Link
191 to="http://meetfranz.com/privacy"
192 target="_blank"
193 className="link"
194 >
195 {intl.formatMessage(messages.privacy)}
196 </Link>.
197 </p>
198 </form>
199 <div className="auth__links">
200 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link>
201 </div>
202 </div>
203 </div>
204 );
205 }
206}
diff --git a/src/components/auth/Welcome.js b/src/components/auth/Welcome.js
new file mode 100644
index 000000000..06b10ecfe
--- /dev/null
+++ b/src/components/auth/Welcome.js
@@ -0,0 +1,69 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Link from '../ui/Link';
7
8const messages = defineMessages({
9 signupButton: {
10 id: 'welcome.signupButton',
11 defaultMessage: '!!!Create a free account',
12 },
13 loginButton: {
14 id: 'welcome.loginButton',
15 defaultMessage: '!!!Login to your account',
16 },
17});
18
19@observer
20export default class Login extends Component {
21 static propTypes = {
22 loginRoute: PropTypes.string.isRequired,
23 signupRoute: PropTypes.string.isRequired,
24 recipes: MobxPropTypes.arrayOrObservableArray.isRequired,
25 };
26
27 static contextTypes = {
28 intl: intlShape,
29 };
30
31 render() {
32 const { intl } = this.context;
33 const {
34 loginRoute,
35 signupRoute,
36 recipes,
37 } = this.props;
38
39 return (
40 <div className="welcome">
41 <div className="welcome__content">
42 <img src="./assets/images/logo.svg" className="welcome__logo" alt="" />
43 {/* <img src="./assets/images/welcome.png" className="welcome__services" alt="" /> */}
44 <div className="welcome__text">
45 <h1>Franz</h1>
46 </div>
47 </div>
48 <div className="welcome__buttons">
49 <Link to={signupRoute} className="button">
50 {intl.formatMessage(messages.signupButton)}
51 </Link>
52 <Link to={loginRoute} className="button">
53 {intl.formatMessage(messages.loginButton)}
54 </Link>
55 </div>
56 <div className="welcome__featured-services">
57 {recipes.map(recipe => (
58 <img
59 key={recipe.id}
60 src={recipe.icons.svg}
61 className="welcome__featured-service"
62 alt=""
63 />
64 ))}
65 </div>
66 </div>
67 );
68 }
69}
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
new file mode 100644
index 000000000..f60c170a8
--- /dev/null
+++ b/src/components/layout/AppLayout.js
@@ -0,0 +1,148 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import InfoBar from '../ui/InfoBar';
7import globalMessages from '../../i18n/globalMessages';
8
9function createMarkup(HTMLString) {
10 return { __html: HTMLString };
11}
12
13const messages = defineMessages({
14 servicesUpdated: {
15 id: 'infobar.servicesUpdated',
16 defaultMessage: '!!!Your services have been updated.',
17 },
18 updateAvailable: {
19 id: 'infobar.updateAvailable',
20 defaultMessage: '!!!A new update for Franz is available.',
21 },
22 buttonReloadServices: {
23 id: 'infobar.buttonReloadServices',
24 defaultMessage: '!!!Reload services',
25 },
26 buttonInstallUpdate: {
27 id: 'infobar.buttonInstallUpdate',
28 defaultMessage: '!!!Restart & install update',
29 },
30 requiredRequestsFailed: {
31 id: 'infobar.requiredRequestsFailed',
32 defaultMessage: '!!!Could not load services and user information',
33 },
34});
35
36@observer
37export default class AppLayout extends Component {
38 static propTypes = {
39 sidebar: PropTypes.element.isRequired,
40 services: PropTypes.element.isRequired,
41 children: PropTypes.element,
42 news: MobxPropTypes.arrayOrObservableArray.isRequired,
43 isOnline: PropTypes.bool.isRequired,
44 showServicesUpdatedInfoBar: PropTypes.bool.isRequired,
45 appUpdateIsDownloaded: PropTypes.bool.isRequired,
46 removeNewsItem: PropTypes.func.isRequired,
47 reloadServicesAfterUpdate: PropTypes.func.isRequired,
48 installAppUpdate: PropTypes.func.isRequired,
49 showRequiredRequestsError: PropTypes.bool.isRequired,
50 areRequiredRequestsSuccessful: PropTypes.bool.isRequired,
51 retryRequiredRequests: PropTypes.func.isRequired,
52 areRequiredRequestsLoading: PropTypes.bool.isRequired,
53 };
54
55 static defaultProps = {
56 children: [],
57 };
58
59 static contextTypes = {
60 intl: intlShape,
61 };
62
63 render() {
64 const {
65 sidebar,
66 services,
67 children,
68 isOnline,
69 news,
70 showServicesUpdatedInfoBar,
71 appUpdateIsDownloaded,
72 removeNewsItem,
73 reloadServicesAfterUpdate,
74 installAppUpdate,
75 showRequiredRequestsError,
76 areRequiredRequestsSuccessful,
77 retryRequiredRequests,
78 areRequiredRequestsLoading,
79 } = this.props;
80
81 const { intl } = this.context;
82
83 return (
84 <div>
85 <div className="app">
86 {sidebar}
87 <div className="app__service">
88 {news.length > 0 && news.map(item => (
89 <InfoBar
90 key={item.id}
91 position="top"
92 type={item.type}
93 sticky={item.sticky}
94 onHide={() => removeNewsItem({ newsId: item.id })}
95 >
96 <span dangerouslySetInnerHTML={createMarkup(item.message)} />
97 </InfoBar>
98 ))}
99 {!isOnline && (
100 <InfoBar
101 type="danger"
102 >
103 <span className="mdi mdi-flash" />
104 {intl.formatMessage(globalMessages.notConnectedToTheInternet)}
105 </InfoBar>
106 )}
107 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
108 <InfoBar
109 type="danger"
110 ctaLabel="Try again"
111 ctaLoading={areRequiredRequestsLoading}
112 sticky
113 onClick={retryRequiredRequests}
114 >
115 <span className="mdi mdi-flash" />
116 {intl.formatMessage(messages.requiredRequestsFailed)}
117 </InfoBar>
118 )}
119 {showServicesUpdatedInfoBar && (
120 <InfoBar
121 type="primary"
122 ctaLabel={intl.formatMessage(messages.buttonReloadServices)}
123 onClick={reloadServicesAfterUpdate}
124 sticky
125 >
126 <span className="mdi mdi-power-plug" />
127 {intl.formatMessage(messages.servicesUpdated)}
128 </InfoBar>
129 )}
130 {appUpdateIsDownloaded && (
131 <InfoBar
132 type="primary"
133 ctaLabel={intl.formatMessage(messages.buttonInstallUpdate)}
134 onClick={installAppUpdate}
135 sticky
136 >
137 <span className="mdi mdi-information" />
138 {intl.formatMessage(messages.updateAvailable)}
139 </InfoBar>
140 )}
141 {services}
142 </div>
143 </div>
144 {children}
145 </div>
146 );
147 }
148}
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js
new file mode 100644
index 000000000..4aee1ec60
--- /dev/null
+++ b/src/components/layout/Sidebar.js
@@ -0,0 +1,75 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import ReactTooltip from 'react-tooltip';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Tabbar from '../services/tabs/Tabbar';
7import { ctrlKey } from '../../environment';
8
9const messages = defineMessages({
10 settings: {
11 id: 'sidebar.settings',
12 defaultMessage: '!!!Settings',
13 },
14});
15
16export default class Sidebar extends Component {
17 static propTypes = {
18 openSettings: PropTypes.func.isRequired,
19 isPremiumUser: PropTypes.bool,
20 }
21
22 static defaultProps = {
23 isPremiumUser: false,
24 }
25
26 static contextTypes = {
27 intl: intlShape,
28 };
29
30 state = {
31 tooltipEnabled: true,
32 };
33
34 enableToolTip() {
35 this.setState({ tooltipEnabled: true });
36 }
37
38 disableToolTip() {
39 this.setState({ tooltipEnabled: false });
40 }
41
42 render() {
43 const { openSettings, isPremiumUser } = this.props;
44 const { intl } = this.context;
45 return (
46 <div className="sidebar">
47 <Tabbar
48 {...this.props}
49 enableToolTip={() => this.enableToolTip()}
50 disableToolTip={() => this.disableToolTip()}
51 />
52 <button
53 onClick={openSettings}
54 className="sidebar__settings-button"
55 data-tip={`Settings (${ctrlKey}+,)`}
56 >
57 {isPremiumUser && (
58 <span className="emoji">
59 <img src="./assets/images/emoji/star.png" alt="" />
60 </span>
61 )}
62 <img
63 src="./assets/images/logo.svg"
64 className="sidebar__logo"
65 alt=""
66 />
67 {intl.formatMessage(messages.settings)}
68 </button>
69 {this.state.tooltipEnabled && (
70 <ReactTooltip place="right" type="dark" effect="solid" />
71 )}
72 </div>
73 );
74 }
75}
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
new file mode 100644
index 000000000..043ff42ea
--- /dev/null
+++ b/src/components/services/content/ServiceWebview.js
@@ -0,0 +1,73 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { autorun } from 'mobx';
4import { observer } from 'mobx-react';
5import Webview from 'react-electron-web-view';
6import classnames from 'classnames';
7
8import ServiceModel from '../../../models/Service';
9
10@observer
11export default class ServiceWebview extends Component {
12 static propTypes = {
13 service: PropTypes.instanceOf(ServiceModel).isRequired,
14 setWebviewReference: PropTypes.func.isRequired,
15 };
16
17 static defaultProps = {
18 isActive: false,
19 };
20
21 state = {
22 forceRepaint: false,
23 };
24
25 componentDidMount() {
26 autorun(() => {
27 if (this.props.service.isActive) {
28 this.setState({ forceRepaint: true });
29 setTimeout(() => {
30 this.setState({ forceRepaint: false });
31 }, 100);
32 }
33 });
34 }
35
36 webview = null;
37
38 render() {
39 const {
40 service,
41 setWebviewReference,
42 } = this.props;
43
44 const webviewClasses = classnames({
45 services__webview: true,
46 'is-active': service.isActive,
47 'services__webview--force-repaint': this.state.forceRepaint,
48 });
49
50 return (
51 <div className={webviewClasses}>
52 <Webview
53 ref={(element) => { this.webview = element; }}
54
55 autosize
56 src={service.url}
57 preload="./webview/plugin.js"
58 partition={`persist:service-${service.id}`}
59
60 onDidAttach={() => setWebviewReference({
61 serviceId: service.id,
62 webview: this.webview.view,
63 })}
64
65 useragent={service.userAgent}
66
67 disablewebsecurity
68 allowpopups
69 />
70 </div>
71 );
72 }
73}
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
new file mode 100644
index 000000000..03c68b06f
--- /dev/null
+++ b/src/components/services/content/Services.js
@@ -0,0 +1,81 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl';
6
7import Webview from './ServiceWebview';
8import Appear from '../../ui/effects/Appear';
9
10const messages = defineMessages({
11 welcome: {
12 id: 'services.welcome',
13 defaultMessage: '!!!Welcome to Franz',
14 },
15 getStarted: {
16 id: 'services.getStarted',
17 defaultMessage: '!!!Get started',
18 },
19});
20
21@observer
22export default class Services extends Component {
23 static propTypes = {
24 services: MobxPropTypes.arrayOrObservableArray.isRequired,
25 setWebviewReference: PropTypes.func.isRequired,
26 handleIPCMessage: PropTypes.func.isRequired,
27 openWindow: PropTypes.func.isRequired,
28 };
29
30 static defaultProps = {
31 services: [],
32 activeService: '',
33 };
34
35 static contextTypes = {
36 intl: intlShape,
37 };
38
39 render() {
40 const {
41 services,
42 handleIPCMessage,
43 setWebviewReference,
44 openWindow,
45 } = this.props;
46 const { intl } = this.context;
47
48 return (
49 <div className="services">
50 {services.length === 0 && (
51 <Appear
52 timeout={1500}
53 transitionName="slideUp"
54 >
55 <div className="services__no-service">
56 <img src="./assets/images/logo.svg" alt="" />
57 <h1>{intl.formatMessage(messages.welcome)}</h1>
58 <Appear
59 timeout={300}
60 transitionName="slideUp"
61 >
62 <Link to="/settings/recipes" className="button">
63 {intl.formatMessage(messages.getStarted)}
64 </Link>
65 </Appear>
66 </div>
67 </Appear>
68 )}
69 {services.map(service => (
70 <Webview
71 key={service.id}
72 service={service}
73 handleIPCMessage={handleIPCMessage}
74 setWebviewReference={setWebviewReference}
75 openWindow={openWindow}
76 />
77 ))}
78 </div>
79 );
80 }
81}
diff --git a/src/components/services/tabs/TabBarSortableList.js b/src/components/services/tabs/TabBarSortableList.js
new file mode 100644
index 000000000..c0a68d1a5
--- /dev/null
+++ b/src/components/services/tabs/TabBarSortableList.js
@@ -0,0 +1,44 @@
1import React from 'react';
2import { observer } from 'mobx-react';
3import { SortableContainer } from 'react-sortable-hoc';
4
5import TabItem from './TabItem';
6import { ctrlKey } from '../../../environment';
7
8export default SortableContainer(observer(({
9 services,
10 setActive,
11 reload,
12 toggleNotifications,
13 deleteService,
14 disableService,
15 openSettings,
16}) => (
17 <ul
18 className="tabs"
19 >
20 {services.map((service, index) => (
21 <TabItem
22 key={service.id}
23 clickHandler={() => setActive({ serviceId: service.id })}
24 service={service}
25 index={index}
26 shortcutIndex={index + 1}
27 reload={() => reload({ serviceId: service.id })}
28 toggleNotifications={() => toggleNotifications({ serviceId: service.id })}
29 deleteService={() => deleteService({ serviceId: service.id })}
30 disableService={() => disableService({ serviceId: service.id })}
31 openSettings={openSettings}
32 />
33 ))}
34 <li>
35 <button
36 className="sidebar__add-service"
37 onClick={() => openSettings({ path: 'recipes' })}
38 data-tip={`Add new service (${ctrlKey}+N)`}
39 >
40 <span className="mdi mdi-plus" />
41 </button>
42 </li>
43 </ul>
44)));
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js
new file mode 100644
index 000000000..9e03d2e21
--- /dev/null
+++ b/src/components/services/tabs/TabItem.js
@@ -0,0 +1,136 @@
1import { remote } from 'electron';
2import React, { Component } from 'react';
3import { defineMessages, intlShape } from 'react-intl';
4import PropTypes from 'prop-types';
5import { observer } from 'mobx-react';
6import classnames from 'classnames';
7import { SortableElement } from 'react-sortable-hoc';
8
9import ServiceModel from '../../../models/Service';
10import { ctrlKey } from '../../../environment';
11
12const { Menu } = remote;
13
14const messages = defineMessages({
15 reload: {
16 id: 'tabs.item.reload',
17 defaultMessage: '!!!Reload',
18 },
19 edit: {
20 id: 'tabs.item.edit',
21 defaultMessage: '!!!Edit',
22 },
23 disableNotifications: {
24 id: 'tabs.item.disableNotifications',
25 defaultMessage: '!!!Disable notifications',
26 },
27 enableNotifications: {
28 id: 'tabs.item.enableNotification',
29 defaultMessage: '!!!Enable notifications',
30 },
31 disableService: {
32 id: 'tabs.item.disableService',
33 defaultMessage: '!!!Disable Service',
34 },
35 deleteService: {
36 id: 'tabs.item.deleteService',
37 defaultMessage: '!!!Delete Service',
38 },
39});
40
41@observer
42class TabItem extends Component {
43 static propTypes = {
44 service: PropTypes.instanceOf(ServiceModel).isRequired,
45 clickHandler: PropTypes.func.isRequired,
46 shortcutIndex: PropTypes.number.isRequired,
47 reload: PropTypes.func.isRequired,
48 toggleNotifications: PropTypes.func.isRequired,
49 openSettings: PropTypes.func.isRequired,
50 deleteService: PropTypes.func.isRequired,
51 disableService: PropTypes.func.isRequired,
52 };
53
54 static contextTypes = {
55 intl: intlShape,
56 };
57
58 render() {
59 const {
60 service,
61 clickHandler,
62 shortcutIndex,
63 reload,
64 toggleNotifications,
65 deleteService,
66 disableService,
67 openSettings,
68 } = this.props;
69 const { intl } = this.context;
70
71
72 const menuTemplate = [{
73 label: service.name || service.recipe.name,
74 enabled: false,
75 }, {
76 type: 'separator',
77 }, {
78 label: intl.formatMessage(messages.reload),
79 click: reload,
80 }, {
81 label: intl.formatMessage(messages.edit),
82 click: () => openSettings({
83 path: `services/edit/${service.id}`,
84 }),
85 }, {
86 type: 'separator',
87 }, {
88 label: service.isNotificationEnabled
89 ? intl.formatMessage(messages.disableNotifications)
90 : intl.formatMessage(messages.enableNotifications),
91 click: () => toggleNotifications(),
92 }, {
93 label: intl.formatMessage(messages.disableService),
94 click: () => disableService(),
95 }, {
96 type: 'separator',
97 }, {
98 label: intl.formatMessage(messages.deleteService),
99 click: () => deleteService(),
100 }];
101 const menu = Menu.buildFromTemplate(menuTemplate);
102
103 return (
104 <li
105 className={classnames({
106 'tab-item': true,
107 'is-active': service.isActive,
108 'has-custom-icon': service.hasCustomIcon,
109 })}
110 onClick={clickHandler}
111 onContextMenu={() => menu.popup(remote.getCurrentWindow())}
112 data-tip={`${service.name} ${shortcutIndex <= 9 ? `(${ctrlKey}+${shortcutIndex})` : ''}`}
113 >
114 <img
115 src={service.icon}
116 className="tab-item__icon"
117 alt=""
118 />
119 {service.unreadDirectMessageCount > 0 && (
120 <span className="tab-item__message-count">
121 {service.unreadDirectMessageCount}
122 </span>
123 )}
124 {service.unreadIndirectMessageCount > 0
125 && service.unreadDirectMessageCount === 0
126 && service.isIndirectMessageBadgeEnabled && (
127 <span className="tab-item__message-count is-indirect">
128
129 </span>
130 )}
131 </li>
132 );
133 }
134}
135
136export default SortableElement(TabItem);
diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js
new file mode 100644
index 000000000..fdb2c0a59
--- /dev/null
+++ b/src/components/services/tabs/Tabbar.js
@@ -0,0 +1,77 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4
5import TabBarSortableList from './TabBarSortableList';
6
7@observer
8export default class TabBar extends Component {
9 static propTypes = {
10 services: MobxPropTypes.arrayOrObservableArray.isRequired,
11 setActive: PropTypes.func.isRequired,
12 openSettings: PropTypes.func.isRequired,
13 enableToolTip: PropTypes.func.isRequired,
14 disableToolTip: PropTypes.func.isRequired,
15 reorder: PropTypes.func.isRequired,
16 reload: PropTypes.func.isRequired,
17 toggleNotifications: PropTypes.func.isRequired,
18 deleteService: PropTypes.func.isRequired,
19 updateService: PropTypes.func.isRequired,
20 }
21
22 onSortEnd = ({ oldIndex, newIndex }) => {
23 const {
24 enableToolTip,
25 reorder,
26 } = this.props;
27
28 enableToolTip();
29 reorder({ oldIndex, newIndex });
30 };
31
32 disableService = ({ serviceId }) => {
33 const { updateService } = this.props;
34
35 if (serviceId) {
36 updateService({
37 serviceId,
38 serviceData: {
39 isEnabled: false,
40 },
41 redirect: false,
42 });
43 }
44 }
45
46 render() {
47 const {
48 services,
49 setActive,
50 openSettings,
51 disableToolTip,
52 reload,
53 toggleNotifications,
54 deleteService,
55 } = this.props;
56
57 return (
58 <div>
59 <TabBarSortableList
60 services={services}
61 setActive={setActive}
62 onSortEnd={this.onSortEnd}
63 onSortStart={disableToolTip}
64 reload={reload}
65 toggleNotifications={toggleNotifications}
66 deleteService={deleteService}
67 disableService={this.disableService}
68 openSettings={openSettings}
69 distance={20}
70 axis="y"
71 lockAxis="y"
72 helperClass="is-reordering"
73 />
74 </div>
75 );
76 }
77}
diff --git a/src/components/settings/SettingsLayout.js b/src/components/settings/SettingsLayout.js
new file mode 100644
index 000000000..d5392ddba
--- /dev/null
+++ b/src/components/settings/SettingsLayout.js
@@ -0,0 +1,56 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4
5import { oneOrManyChildElements } from '../../prop-types';
6import Appear from '../ui/effects/Appear';
7
8@observer
9export default class SettingsLayout extends Component {
10 static propTypes = {
11 navigation: PropTypes.element.isRequired,
12 children: oneOrManyChildElements.isRequired,
13 closeSettings: PropTypes.func.isRequired,
14 };
15
16 componentWillMount() {
17 document.addEventListener('keydown', this.handleKeyDown.bind(this), false);
18 }
19
20 componentWillUnmount() {
21 document.removeEventListener('keydown', this.handleKeyDown.bind(this), false);
22 }
23
24 handleKeyDown(e) {
25 if (e.keyCode === 27) { // escape key
26 this.props.closeSettings();
27 }
28 }
29
30 render() {
31 const {
32 navigation,
33 children,
34 closeSettings,
35 } = this.props;
36
37 return (
38 <Appear transitionName="fadeIn-fast">
39 <div className="settings-wrapper">
40 <button
41 className="settings-wrapper__action"
42 onClick={closeSettings}
43 />
44 <div className="settings franz-form">
45 {navigation}
46 {children}
47 <button
48 className="settings__close mdi mdi-close"
49 onClick={closeSettings}
50 />
51 </div>
52 </div>
53 </Appear>
54 );
55 }
56}
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
new file mode 100644
index 000000000..75dbdef49
--- /dev/null
+++ b/src/components/settings/account/AccountDashboard.js
@@ -0,0 +1,286 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape, FormattedMessage } from 'react-intl';
5import ReactTooltip from 'react-tooltip';
6import moment from 'moment';
7
8import Loader from '../../ui/Loader';
9import Button from '../../ui/Button';
10import Infobox from '../../ui/Infobox';
11import Link from '../../ui/Link';
12import SubscriptionForm from '../../../containers/ui/SubscriptionFormScreen';
13
14const messages = defineMessages({
15 headline: {
16 id: 'settings.account.headline',
17 defaultMessage: '!!!Account',
18 },
19 headlineSubscription: {
20 id: 'settings.account.headlineSubscription',
21 defaultMessage: '!!!Your Subscription',
22 },
23 headlineUpgrade: {
24 id: 'settings.account.headlineUpgrade',
25 defaultMessage: '!!!Upgrade your Account',
26 },
27 headlineInvoices: {
28 id: 'settings.account.headlineInvoices',
29 defaultMessage: '!!Invoices',
30 },
31 manageSubscriptionButtonLabel: {
32 id: 'settings.account.manageSubscription.label',
33 defaultMessage: '!!!Manage your subscription',
34 },
35 accountTypeBasic: {
36 id: 'settings.account.accountType.basic',
37 defaultMessage: '!!!Basic Account',
38 },
39 accountTypePremium: {
40 id: 'settings.account.accountType.premium',
41 defaultMessage: '!!!Premium Supporter Account',
42 },
43 accountEditButton: {
44 id: 'settings.account.account.editButton',
45 defaultMessage: '!!!Edit Account',
46 },
47 invoiceDownload: {
48 id: 'settings.account.invoiceDownload',
49 defaultMessage: '!!!Download',
50 },
51 userInfoRequestFailed: {
52 id: 'settings.account.userInfoRequestFailed',
53 defaultMessage: '!!!Could not load user information',
54 },
55 tryReloadUserInfoRequest: {
56 id: 'settings.account.tryReloadUserInfoRequest',
57 defaultMessage: '!!!Try again',
58 },
59 miningActive: {
60 id: 'settings.account.mining.active',
61 defaultMessage: '!!!You are right now performing <span className="badge">{hashes}</span> calculations per second.',
62 },
63 miningThankYou: {
64 id: 'settings.account.mining.thankyou',
65 defaultMessage: '!!!Thank you for supporting Franz with your processing power.',
66 },
67 miningMoreInfo: {
68 id: 'settings.account.mining.moreInformation',
69 defaultMessage: '!!!Get more information',
70 },
71 cancelMining: {
72 id: 'settings.account.mining.cancel',
73 defaultMessage: '!!!Cancel mining',
74 },
75});
76
77@observer
78export default class AccountDashboard extends Component {
79 static propTypes = {
80 user: MobxPropTypes.observableObject.isRequired,
81 orders: MobxPropTypes.arrayOrObservableArray.isRequired,
82 hashrate: PropTypes.number.isRequired,
83 isLoading: PropTypes.bool.isRequired,
84 isLoadingOrdersInfo: PropTypes.bool.isRequired,
85 isLoadingPlans: PropTypes.bool.isRequired,
86 isCreatingPaymentDashboardUrl: PropTypes.bool.isRequired,
87 userInfoRequestFailed: PropTypes.bool.isRequired,
88 retryUserInfoRequest: PropTypes.func.isRequired,
89 openDashboard: PropTypes.func.isRequired,
90 openExternalUrl: PropTypes.func.isRequired,
91 onCloseSubscriptionWindow: PropTypes.func.isRequired,
92 stopMiner: PropTypes.func.isRequired,
93 };
94
95 static contextTypes = {
96 intl: intlShape,
97 };
98
99 render() {
100 const {
101 user,
102 orders,
103 hashrate,
104 isLoading,
105 isCreatingPaymentDashboardUrl,
106 openDashboard,
107 openExternalUrl,
108 isLoadingOrdersInfo,
109 isLoadingPlans,
110 userInfoRequestFailed,
111 retryUserInfoRequest,
112 onCloseSubscriptionWindow,
113 stopMiner,
114 } = this.props;
115 const { intl } = this.context;
116
117 return (
118 <div className="settings__main">
119 <div className="settings__header">
120 <span className="settings__header-item">
121 {intl.formatMessage(messages.headline)}
122 </span>
123 </div>
124 <div className="settings__body">
125 {isLoading && (
126 <Loader />
127 )}
128
129 {!isLoading && userInfoRequestFailed && (
130 <div>
131 <Infobox
132 icon="alert"
133 type="danger"
134 ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)}
135 ctaLoading={isLoading}
136 ctaOnClick={retryUserInfoRequest}
137 >
138 {intl.formatMessage(messages.userInfoRequestFailed)}
139 </Infobox>
140 </div>
141 )}
142
143 {!userInfoRequestFailed && (
144 <div>
145 {!isLoading && (
146 <div className="account">
147 <div className="account__box account__box--flex">
148 <div className="account__avatar">
149 <img
150 src="./assets/images/logo.svg"
151 alt=""
152 />
153 {user.isPremium && (
154 <span
155 className="account__avatar-premium emoji"
156 data-tip="Premium Supporter Account"
157 >
158 <img src="./assets/images/emoji/star.png" alt="" />
159 </span>
160 )}
161 </div>
162 <div className="account__info">
163 <h2>
164 {`${user.firstname} ${user.lastname}`}
165 </h2>
166 {user.organization && `${user.organization}, `}
167 {user.email}<br />
168 {!user.isPremium && (
169 <span className="badge badge">{intl.formatMessage(messages.accountTypeBasic)}</span>
170 )}
171 {user.isPremium && (
172 <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span>
173 )}
174 </div>
175 <Link to="/settings/user/edit" className="button">
176 {intl.formatMessage(messages.accountEditButton)}
177 </Link>
178
179 {user.emailValidated}
180 </div>
181 </div>
182 )}
183
184 {user.isSubscriptionOwner && (
185 isLoadingOrdersInfo ? (
186 <Loader />
187 ) : (
188 <div className="account franz-form">
189 {orders.length > 0 && (
190 <div>
191 <div className="account__box">
192 <h2>{intl.formatMessage(messages.headlineSubscription)}</h2>
193 <div className="account__subscription">
194 {orders[0].name}
195 <span className="badge">{orders[0].price}</span>
196 <Button
197 label={intl.formatMessage(messages.manageSubscriptionButtonLabel)}
198 className="account__subscription-button franz-form__button--inverted"
199 loaded={!isCreatingPaymentDashboardUrl}
200 onClick={() => openDashboard()}
201 />
202 </div>
203 </div>
204 <div className="account__box account__box--last">
205 <h2>{intl.formatMessage(messages.headlineInvoices)}</h2>
206 <table className="invoices">
207 <tbody>
208 {orders.map(order => (
209 <tr key={order.id}>
210 <td className="invoices__date">
211 {moment(order.date).format('DD.MM.YYYY')}
212 </td>
213 <td className="invoices__action">
214 <button
215 onClick={() => openExternalUrl(order.invoiceUrl)}
216 >
217 {intl.formatMessage(messages.invoiceDownload)}
218 </button>
219 </td>
220 </tr>
221 ))}
222 </tbody>
223 </table>
224 </div>
225 </div>
226 )}
227 </div>
228 )
229 )}
230
231 {user.isMiner && (
232 <div className="account franz-form">
233 <div className="account__box">
234 <h2>{intl.formatMessage(messages.headlineSubscription)}</h2>
235 <div className="account__subscription">
236 <div>
237 <p>{intl.formatMessage(messages.miningThankYou)}</p>
238 <FormattedMessage
239 {...messages.miningActive}
240 values={{
241 hashes: <span className="badge">{hashrate.toFixed(2)}</span>,
242 }}
243 tagName="p"
244 />
245 <p>
246 <Link
247 to="http://meetfranz.com/mining"
248 target="_blank"
249 className="link"
250 >
251 {intl.formatMessage(messages.miningMoreInfo)}
252 </Link>
253 </p>
254 </div>
255 <Button
256 label={intl.formatMessage(messages.cancelMining)}
257 className="account__subscription-button franz-form__button--inverted"
258 onClick={() => stopMiner()}
259 />
260 </div>
261 </div>
262 </div>
263 )}
264
265 {!user.isPremium && !user.isMiner && (
266 isLoadingPlans ? (
267 <Loader />
268 ) : (
269 <div className="account franz-form">
270 <div className="account__box account__box--last">
271 <h2>{intl.formatMessage(messages.headlineUpgrade)}</h2>
272 <SubscriptionForm
273 onCloseWindow={onCloseSubscriptionWindow}
274 />
275 </div>
276 </div>
277 )
278 )}
279 </div>
280 )}
281 </div>
282 <ReactTooltip place="right" type="dark" effect="solid" />
283 </div>
284 );
285 }
286}
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
new file mode 100644
index 000000000..3b21a7765
--- /dev/null
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -0,0 +1,84 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl';
4
5import Link from '../../ui/Link';
6
7const messages = defineMessages({
8 availableServices: {
9 id: 'settings.navigation.availableServices',
10 defaultMessage: '!!!Available services',
11 },
12 yourServices: {
13 id: 'settings.navigation.yourServices',
14 defaultMessage: '!!!Your services',
15 },
16 account: {
17 id: 'settings.navigation.account',
18 defaultMessage: '!!!Account',
19 },
20 settings: {
21 id: 'settings.navigation.settings',
22 defaultMessage: '!!!Settings',
23 },
24 logout: {
25 id: 'settings.navigation.logout',
26 defaultMessage: '!!!Logout',
27 },
28});
29
30export default class SettingsNavigation extends Component {
31 static propTypes = {
32 serviceCount: PropTypes.number.isRequired,
33 };
34
35 static contextTypes = {
36 intl: intlShape,
37 };
38
39 render() {
40 const { serviceCount } = this.props;
41 const { intl } = this.context;
42
43 return (
44 <div className="settings-navigation">
45 <Link
46 to="/settings/recipes"
47 className="settings-navigation__link"
48 activeClassName="is-active"
49 >
50 {intl.formatMessage(messages.availableServices)}
51 </Link>
52 <Link
53 to="/settings/services"
54 className="settings-navigation__link"
55 activeClassName="is-active"
56 >
57 {intl.formatMessage(messages.yourServices)} <span className="badge">{serviceCount}</span>
58 </Link>
59 <Link
60 to="/settings/user"
61 className="settings-navigation__link"
62 activeClassName="is-active"
63 >
64 {intl.formatMessage(messages.account)}
65 </Link>
66 <Link
67 to="/settings/app"
68 className="settings-navigation__link"
69 activeClassName="is-active"
70 >
71 {intl.formatMessage(messages.settings)}
72 </Link>
73 <span className="settings-navigation__expander" />
74 <Link
75 to="/auth/logout"
76 className="settings-navigation__link"
77 activeClassName="is-active"
78 >
79 {intl.formatMessage(messages.logout)}
80 </Link>
81 </div>
82 );
83 }
84}
diff --git a/src/components/settings/recipes/RecipeItem.js b/src/components/settings/recipes/RecipeItem.js
new file mode 100644
index 000000000..7b2f64d26
--- /dev/null
+++ b/src/components/settings/recipes/RecipeItem.js
@@ -0,0 +1,34 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4
5import RecipePreviewModel from '../../../models/RecipePreview';
6
7@observer
8export default class RecipeItem extends Component {
9 static propTypes = {
10 recipe: PropTypes.instanceOf(RecipePreviewModel).isRequired,
11 onClick: PropTypes.func.isRequired,
12 };
13
14 render() {
15 const { recipe, onClick } = this.props;
16
17 return (
18 <button
19 className="recipe-teaser"
20 onClick={onClick}
21 >
22 {recipe.local && (
23 <span className="recipe-teaser__dev-badge">dev</span>
24 )}
25 <img
26 src={recipe.icons.svg}
27 className="recipe-teaser__icon"
28 alt=""
29 />
30 <span className="recipe-teaser__label">{recipe.name}</span>
31 </button>
32 );
33 }
34}
diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js
new file mode 100644
index 000000000..02ea04e35
--- /dev/null
+++ b/src/components/settings/recipes/RecipesDashboard.js
@@ -0,0 +1,151 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import { Link } from 'react-router';
6
7import SearchInput from '../../ui/SearchInput';
8import Infobox from '../../ui/Infobox';
9import RecipeItem from './RecipeItem';
10import Loader from '../../ui/Loader';
11import Appear from '../../ui/effects/Appear';
12
13const messages = defineMessages({
14 headline: {
15 id: 'settings.recipes.headline',
16 defaultMessage: '!!!Available Services',
17 },
18 mostPopularRecipes: {
19 id: 'settings.recipes.mostPopular',
20 defaultMessage: '!!!Most popular',
21 },
22 allRecipes: {
23 id: 'settings.recipes.all',
24 defaultMessage: '!!!All services',
25 },
26 devRecipes: {
27 id: 'settings.recipes.dev',
28 defaultMessage: '!!!Development',
29 },
30 nothingFound: {
31 id: 'settings.recipes.nothingFound',
32 defaultMessage: '!!!Sorry, but no service matched your search term.',
33 },
34 servicesSuccessfulAddedInfo: {
35 id: 'settings.recipes.servicesSuccessfulAddedInfo',
36 defaultMessage: '!!!Service successfully added',
37 },
38});
39
40@observer
41export default class RecipesDashboard extends Component {
42 static propTypes = {
43 recipes: MobxPropTypes.arrayOrObservableArray.isRequired,
44 isLoading: PropTypes.bool.isRequired,
45 hasLoadedRecipes: PropTypes.bool.isRequired,
46 showAddServiceInterface: PropTypes.func.isRequired,
47 searchRecipes: PropTypes.func.isRequired,
48 resetSearch: PropTypes.func.isRequired,
49 serviceStatus: MobxPropTypes.arrayOrObservableArray.isRequired,
50 devRecipesCount: PropTypes.number.isRequired,
51 searchNeedle: PropTypes.string,
52 };
53
54 static defaultProps = {
55 searchNeedle: '',
56 }
57
58 static contextTypes = {
59 intl: intlShape,
60 };
61
62 render() {
63 const {
64 recipes,
65 isLoading,
66 hasLoadedRecipes,
67 showAddServiceInterface,
68 searchRecipes,
69 resetSearch,
70 serviceStatus,
71 devRecipesCount,
72 searchNeedle,
73 } = this.props;
74 const { intl } = this.context;
75
76 return (
77 <div className="settings__main">
78 <div className="settings__header">
79 <SearchInput
80 className="settings__search-header"
81 defaultValue={intl.formatMessage(messages.headline)}
82 onChange={e => searchRecipes(e)}
83 onReset={() => resetSearch()}
84 throttle
85 />
86 </div>
87 <div className="settings__body recipes">
88 {serviceStatus.length > 0 && serviceStatus.includes('created') && (
89 <Appear>
90 <Infobox
91 type="success"
92 icon="checkbox-marked-circle-outline"
93 dismissable
94 >
95 {intl.formatMessage(messages.servicesSuccessfulAddedInfo)}
96 </Infobox>
97 </Appear>
98 )}
99 {!searchNeedle && (
100 <div className="recipes__navigation">
101 <Link
102 to="/settings/recipes"
103 className="badge"
104 activeClassName="badge--primary"
105 >
106 {intl.formatMessage(messages.mostPopularRecipes)}
107 </Link>
108 <Link
109 to="/settings/recipes/all"
110 className="badge"
111 activeClassName="badge--primary"
112 >
113 {intl.formatMessage(messages.allRecipes)}
114 </Link>
115 {devRecipesCount > 0 && (
116 <Link
117 to="/settings/recipes/dev"
118 className="badge"
119 activeClassName="badge--primary"
120 >
121 {intl.formatMessage(messages.devRecipes)} ({devRecipesCount})
122 </Link>
123 )}
124 </div>
125 )}
126 {isLoading ? (
127 <Loader />
128 ) : (
129 <div className="recipes__list">
130 {hasLoadedRecipes && recipes.length === 0 && (
131 <p className="align-middle settings__empty-state">
132 <span className="emoji">
133 <img src="./assets/images/emoji/dontknow.png" alt="" />
134 </span>
135 {intl.formatMessage(messages.nothingFound)}
136 </p>
137 )}
138 {recipes.map(recipe => (
139 <RecipeItem
140 key={recipe.id}
141 recipe={recipe}
142 onClick={() => showAddServiceInterface({ recipeId: recipe.id })}
143 />
144 ))}
145 </div>
146 )}
147 </div>
148 </div>
149 );
150 }
151}
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
new file mode 100644
index 000000000..fac0f6b9a
--- /dev/null
+++ b/src/components/settings/services/EditServiceForm.js
@@ -0,0 +1,277 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl';
6import normalizeUrl from 'normalize-url';
7
8import Form from '../../../lib/Form';
9import User from '../../../models/User';
10import Recipe from '../../../models/Recipe';
11import Service from '../../../models/Service';
12import Tabs, { TabItem } from '../../ui/Tabs';
13import Input from '../../ui/Input';
14import Toggle from '../../ui/Toggle';
15import Button from '../../ui/Button';
16
17const messages = defineMessages({
18 saveService: {
19 id: 'settings.service.form.saveButton',
20 defaultMessage: '!!!Save service',
21 },
22 deleteService: {
23 id: 'settings.service.form.deleteButton',
24 defaultMessage: '!!!Delete Service',
25 },
26 availableServices: {
27 id: 'settings.service.form.availableServices',
28 defaultMessage: '!!!Available services',
29 },
30 yourServices: {
31 id: 'settings.service.form.yourServices',
32 defaultMessage: '!!!Your services',
33 },
34 addServiceHeadline: {
35 id: 'settings.service.form.addServiceHeadline',
36 defaultMessage: '!!!Add {name}',
37 },
38 editServiceHeadline: {
39 id: 'settings.service.form.editServiceHeadline',
40 defaultMessage: '!!!Edit {name}',
41 },
42 tabHosted: {
43 id: 'settings.service.form.tabHosted',
44 defaultMessage: '!!!Hosted',
45 },
46 tabOnPremise: {
47 id: 'settings.service.form.tabOnPremise',
48 defaultMessage: '!!!Self hosted ⭐️',
49 },
50 customUrlValidationError: {
51 id: 'settings.service.form.customUrlValidationError',
52 defaultMessage: '!!!Could not validate custom {name} server.',
53 },
54 customUrlPremiumInfo: {
55 id: 'settings.service.form.customUrlPremiumInfo',
56 defaultMessage: '!!!To add self hosted services, you need a Franz Premium Supporter Account.',
57 },
58 customUrlUpgradeAccount: {
59 id: 'settings.service.form.customUrlUpgradeAccount',
60 defaultMessage: '!!!Upgrade your account',
61 },
62 indirectMessageInfo: {
63 id: 'settings.service.form.indirectMessageInfo',
64 defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', // eslint-disable-line
65 },
66});
67
68@observer
69export default class EditServiceForm extends Component {
70 static propTypes = {
71 recipe: PropTypes.instanceOf(Recipe).isRequired,
72 // service: PropTypes.oneOfType([
73 // PropTypes.object,
74 // PropTypes.instanceOf(Service),
75 // ]),
76 service(props, propName) {
77 if (props.action === 'edit' && !(props[propName] instanceof Service)) {
78 return new Error(`'${propName}'' is expected to be of type 'Service'
79 when editing a Service`);
80 }
81
82 return null;
83 },
84 user: PropTypes.instanceOf(User).isRequired,
85 action: PropTypes.string.isRequired,
86 form: PropTypes.instanceOf(Form).isRequired,
87 onSubmit: PropTypes.func.isRequired,
88 onDelete: PropTypes.func.isRequired,
89 isSaving: PropTypes.bool.isRequired,
90 isDeleting: PropTypes.bool.isRequired,
91 };
92
93 static defaultProps = {
94 service: {},
95 };
96 static contextTypes = {
97 intl: intlShape,
98 };
99
100 state = {
101 isValidatingCustomUrl: false,
102 }
103
104 submit(e) {
105 const { recipe } = this.props;
106
107 e.preventDefault();
108 this.props.form.submit({
109 onSuccess: async (form) => {
110 const values = form.values();
111
112 let isValid = true;
113
114 if (recipe.validateUrl && values.customUrl) {
115 this.setState({ isValidatingCustomUrl: true });
116 try {
117 values.customUrl = normalizeUrl(values.customUrl);
118 isValid = await recipe.validateUrl(values.customUrl);
119 } catch (err) {
120 console.warn('ValidateURL', err);
121 isValid = false;
122 }
123 }
124
125 if (isValid) {
126 this.props.onSubmit(values);
127 } else {
128 form.invalidate('url-validation-error');
129 }
130
131 this.setState({ isValidatingCustomUrl: false });
132 },
133 onError: () => {},
134 });
135 }
136
137 render() {
138 const {
139 recipe,
140 service,
141 action,
142 user,
143 form,
144 isSaving,
145 isDeleting,
146 onDelete,
147 } = this.props;
148 const { intl } = this.context;
149
150 const { isValidatingCustomUrl } = this.state;
151
152 const deleteButton = isDeleting ? (
153 <Button
154 label={intl.formatMessage(messages.deleteService)}
155 loaded={false}
156 buttonType="secondary"
157 className="settings__delete-button"
158 disabled
159 />
160 ) : (
161 <Button
162 buttonType="danger"
163 label={intl.formatMessage(messages.deleteService)}
164 className="settings__delete-button"
165 onClick={onDelete}
166 />
167 );
168
169 return (
170 <div className="settings__main">
171 <div className="settings__header">
172 <span className="settings__header-item">
173 {action === 'add' ? (
174 <Link to="/settings/recipes">
175 {intl.formatMessage(messages.availableServices)}
176 </Link>
177 ) : (
178 <Link to="/settings/services">
179 {intl.formatMessage(messages.yourServices)}
180 </Link>
181 )}
182 </span>
183 <span className="separator" />
184 <span className="settings__header-item">
185 {action === 'add' ? (
186 intl.formatMessage(messages.addServiceHeadline, {
187 name: recipe.name,
188 })
189 ) : (
190 intl.formatMessage(messages.editServiceHeadline, {
191 name: service.name !== '' ? service.name : recipe.name,
192 })
193 )}
194 </span>
195 </div>
196 <div className="settings__body">
197 <form onSubmit={e => this.submit(e)} id="form">
198 <Input field={form.$('name')} focus />
199 {(recipe.hasTeamId || recipe.hasCustomUrl) && (
200 <Tabs
201 active={service.customUrl ? 1 : 0}
202 >
203 {recipe.hasTeamId && (
204 <TabItem title={intl.formatMessage(messages.tabHosted)}>
205 <Input field={form.$('team')} suffix={recipe.urlInputSuffix} />
206 </TabItem>
207 )}
208 {recipe.hasCustomUrl && (
209 <TabItem title={intl.formatMessage(messages.tabOnPremise)}>
210 {user.isPremium ? (
211 <div>
212 <Input field={form.$('customUrl')} />
213 {form.error === 'url-validation-error' && (
214 <p className="franz-form__error">
215 {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })}
216 </p>
217 )}
218 </div>
219 ) : (
220 <div className="center premium-info">
221 <p>{intl.formatMessage(messages.customUrlPremiumInfo)}</p>
222 <p>
223 <Link to="/settings/user" className="button">
224 {intl.formatMessage(messages.customUrlUpgradeAccount)}
225 </Link>
226 </p>
227 </div>
228 )}
229 </TabItem>
230 )}
231 </Tabs>
232 )}
233 <div className="settings__options">
234 <Toggle field={form.$('isNotificationEnabled')} />
235 {recipe.hasIndirectMessages && (
236 <div>
237 <Toggle field={form.$('isIndirectMessageBadgeEnabled')} />
238 <p className="settings__indirect-message-help">
239 {intl.formatMessage(messages.indirectMessageInfo)}
240 </p>
241 </div>
242 )}
243 <Toggle field={form.$('isEnabled')} />
244 </div>
245 {recipe.message && (
246 <p className="settings__message">
247 <span className="mdi mdi-information" />
248 {recipe.message}
249 </p>
250 )}
251 </form>
252 </div>
253 <div className="settings__controls">
254 {/* Delete Button */}
255 {action === 'edit' && deleteButton}
256
257 {/* Save Button */}
258 {isSaving || isValidatingCustomUrl ? (
259 <Button
260 type="submit"
261 label={intl.formatMessage(messages.saveService)}
262 loaded={false}
263 buttonType="secondary"
264 disabled
265 />
266 ) : (
267 <Button
268 type="submit"
269 label={intl.formatMessage(messages.saveService)}
270 htmlForm="form"
271 />
272 )}
273 </div>
274 </div>
275 );
276 }
277}
diff --git a/src/components/settings/services/ServiceError.js b/src/components/settings/services/ServiceError.js
new file mode 100644
index 000000000..923053296
--- /dev/null
+++ b/src/components/settings/services/ServiceError.js
@@ -0,0 +1,68 @@
1import React, { Component } from 'react';
2import { observer } from 'mobx-react';
3import { Link } from 'react-router';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Infobox from '../../ui/Infobox';
7import Button from '../../ui/Button';
8
9const messages = defineMessages({
10 headline: {
11 id: 'settings.service.error.headline',
12 defaultMessage: '!!!Error',
13 },
14 goBack: {
15 id: 'settings.service.error.goBack',
16 defaultMessage: '!!!Back to services',
17 },
18 availableServices: {
19 id: 'settings.service.form.availableServices',
20 defaultMessage: '!!!Available services',
21 },
22 errorMessage: {
23 id: 'settings.service.error.message',
24 defaultMessage: '!!!Could not load service recipe.',
25 },
26});
27
28@observer
29export default class EditServiceForm extends Component {
30 static contextTypes = {
31 intl: intlShape,
32 };
33
34 render() {
35 const { intl } = this.context;
36
37 return (
38 <div className="settings__main">
39 <div className="settings__header">
40 <span className="settings__header-item">
41 <Link to="/settings/recipes">
42 {intl.formatMessage(messages.availableServices)}
43 </Link>
44 </span>
45 <span className="separator" />
46 <span className="settings__header-item">
47 {intl.formatMessage(messages.headline)}
48 </span>
49 </div>
50 <div className="settings__body">
51 <Infobox
52 type="danger"
53 icon="alert"
54 >
55 {intl.formatMessage(messages.errorMessage)}
56 </Infobox>
57 </div>
58 <div className="settings__controls">
59 <Button
60 label={intl.formatMessage(messages.goBack)}
61 htmlForm="form"
62 onClick={() => window.history.back()}
63 />
64 </div>
65 </div>
66 );
67 }
68}
diff --git a/src/components/settings/services/ServiceItem.js b/src/components/settings/services/ServiceItem.js
new file mode 100644
index 000000000..20d8581d0
--- /dev/null
+++ b/src/components/settings/services/ServiceItem.js
@@ -0,0 +1,98 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl';
4import ReactTooltip from 'react-tooltip';
5import { observer } from 'mobx-react';
6import classnames from 'classnames';
7
8import ServiceModel from '../../../models/Service';
9
10const messages = defineMessages({
11 tooltipIsDisabled: {
12 id: 'settings.services.tooltip.isDisabled',
13 defaultMessage: '!!!Service is disabled',
14 },
15 tooltipNotificationsDisabled: {
16 id: 'settings.services.tooltip.notificationsDisabled',
17 defaultMessage: '!!!Notifications are disabled',
18 },
19});
20
21@observer
22export default class ServiceItem extends Component {
23 static propTypes = {
24 service: PropTypes.instanceOf(ServiceModel).isRequired,
25 goToServiceForm: PropTypes.func.isRequired,
26 };
27 static contextTypes = {
28 intl: intlShape,
29 };
30
31 render() {
32 const {
33 service,
34 // toggleAction,
35 goToServiceForm,
36 } = this.props;
37 const { intl } = this.context;
38
39 return (
40 <tr
41 className={classnames({
42 'service-table__row': true,
43 'service-table__row--disabled': !service.isEnabled,
44 })}
45 >
46 <td
47 className="service-table__column-icon"
48 onClick={goToServiceForm}
49 >
50 <img
51 src={service.icon}
52 className={classnames({
53 'service-table__icon': true,
54 'has-custom-icon': service.hasCustomIcon,
55 })}
56 alt=""
57 />
58 </td>
59 <td
60 className="service-table__column-name"
61 onClick={goToServiceForm}
62 >
63 {service.name !== '' ? service.name : service.recipe.name}
64 </td>
65 <td
66 className="service-table__column-info"
67 onClick={goToServiceForm}
68 >
69 {!service.isEnabled && (
70 <span
71 className="mdi mdi-power"
72 data-tip={intl.formatMessage(messages.tooltipIsDisabled)}
73 />
74 )}
75 </td>
76 <td
77 className="service-table__column-info"
78 onClick={goToServiceForm}
79 >
80 {!service.isNotificationEnabled && (
81 <span
82 className="mdi mdi-message-bulleted-off"
83 data-tip={intl.formatMessage(messages.tooltipNotificationsDisabled)}
84 />
85 )}
86 <ReactTooltip place="top" type="dark" effect="solid" />
87 </td>
88 {/* <td className="service-table__column-action">
89 <input
90 type="checkbox"
91 onChange={toggleAction}
92 checked={service.isEnabled}
93 />
94 </td> */}
95 </tr>
96 );
97 }
98}
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
new file mode 100644
index 000000000..5f146b5f3
--- /dev/null
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -0,0 +1,155 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl';
6
7import SearchInput from '../../ui/SearchInput';
8import Infobox from '../../ui/Infobox';
9import Loader from '../../ui/Loader';
10import ServiceItem from './ServiceItem';
11import Appear from '../../ui/effects/Appear';
12
13const messages = defineMessages({
14 headline: {
15 id: 'settings.services.headline',
16 defaultMessage: '!!!Your services',
17 },
18 noServicesAdded: {
19 id: 'settings.services.noServicesAdded',
20 defaultMessage: '!!!You haven\'t added any services yet.',
21 },
22 discoverServices: {
23 id: 'settings.services.discoverServices',
24 defaultMessage: '!!!Discover services',
25 },
26 servicesRequestFailed: {
27 id: 'settings.services.servicesRequestFailed',
28 defaultMessage: '!!!Could not load your services',
29 },
30 tryReloadServices: {
31 id: 'settings.account.tryReloadServices',
32 defaultMessage: '!!!Try again',
33 },
34 updatedInfo: {
35 id: 'settings.services.updatedInfo',
36 defaultMessage: '!!!Your changes have been saved',
37 },
38 deletedInfo: {
39 id: 'settings.services.deletedInfo',
40 defaultMessage: '!!!Service has been deleted',
41 },
42});
43
44@observer
45export default class ServicesDashboard extends Component {
46 static propTypes = {
47 services: MobxPropTypes.arrayOrObservableArray.isRequired,
48 isLoading: PropTypes.bool.isRequired,
49 toggleService: PropTypes.func.isRequired,
50 filterServices: PropTypes.func.isRequired,
51 resetFilter: PropTypes.func.isRequired,
52 goTo: PropTypes.func.isRequired,
53 servicesRequestFailed: PropTypes.bool.isRequired,
54 retryServicesRequest: PropTypes.func.isRequired,
55 status: MobxPropTypes.arrayOrObservableArray.isRequired,
56 };
57 static contextTypes = {
58 intl: intlShape,
59 };
60
61 render() {
62 const {
63 services,
64 isLoading,
65 toggleService,
66 filterServices,
67 resetFilter,
68 goTo,
69 servicesRequestFailed,
70 retryServicesRequest,
71 status,
72 } = this.props;
73 const { intl } = this.context;
74
75 return (
76 <div className="settings__main">
77 <div className="settings__header">
78 <SearchInput
79 className="settings__search-header"
80 defaultValue={intl.formatMessage(messages.headline)}
81 onChange={needle => filterServices({ needle })}
82 onReset={() => resetFilter()}
83 />
84 </div>
85 <div className="settings__body">
86 {!isLoading && servicesRequestFailed && (
87 <div>
88 <Infobox
89 icon="alert"
90 type="danger"
91 ctaLabel={intl.formatMessage(messages.tryReloadServices)}
92 ctaLoading={isLoading}
93 ctaOnClick={retryServicesRequest}
94 >
95 {intl.formatMessage(messages.servicesRequestFailed)}
96 </Infobox>
97 </div>
98 )}
99
100 {status.length > 0 && status.includes('updated') && (
101 <Appear>
102 <Infobox
103 type="success"
104 icon="checkbox-marked-circle-outline"
105 dismissable
106 >
107 {intl.formatMessage(messages.updatedInfo)}
108 </Infobox>
109 </Appear>
110 )}
111
112 {status.length > 0 && status.includes('service-deleted') && (
113 <Appear>
114 <Infobox
115 type="success"
116 icon="checkbox-marked-circle-outline"
117 dismissable
118 >
119 {intl.formatMessage(messages.deletedInfo)}
120 </Infobox>
121 </Appear>
122 )}
123
124 {!isLoading && services.length === 0 && (
125 <div className="align-middle settings__empty-state">
126 <p className="settings__empty-text">
127 <span className="emoji">
128 <img src="./assets/images/emoji/sad.png" alt="" />
129 </span>
130 {intl.formatMessage(messages.noServicesAdded)}
131 </p>
132 <Link to="/settings/recipes" className="button">{intl.formatMessage(messages.discoverServices)}</Link>
133 </div>
134 )}
135 {isLoading ? (
136 <Loader />
137 ) : (
138 <table className="service-table">
139 <tbody>
140 {services.map(service => (
141 <ServiceItem
142 key={service.id}
143 service={service}
144 toggleAction={() => toggleService({ serviceId: service.id })}
145 goToServiceForm={() => goTo(`/settings/services/edit/${service.id}`)}
146 />
147 ))}
148 </tbody>
149 </table>
150 )}
151 </div>
152 </div>
153 );
154 }
155}
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
new file mode 100644
index 000000000..02736dbb9
--- /dev/null
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -0,0 +1,148 @@
1import { remote } from 'electron';
2import React, { Component } from 'react';
3import PropTypes from 'prop-types';
4import { observer } from 'mobx-react';
5import { defineMessages, intlShape } from 'react-intl';
6
7import Form from '../../../lib/Form';
8import Button from '../../ui/Button';
9import Toggle from '../../ui/Toggle';
10import Select from '../../ui/Select';
11
12const messages = defineMessages({
13 headline: {
14 id: 'settings.app.headline',
15 defaultMessage: '!!!Settings',
16 },
17 headlineGeneral: {
18 id: 'settings.app.headlineGeneral',
19 defaultMessage: '!!!General',
20 },
21 headlineLanguage: {
22 id: 'settings.app.headlineLanguage',
23 defaultMessage: '!!!Language',
24 },
25 headlineUpdates: {
26 id: 'settings.app.headlineUpdates',
27 defaultMessage: '!!!Updates',
28 },
29 buttonSearchForUpdate: {
30 id: 'settings.app.buttonSearchForUpdate',
31 defaultMessage: '!!!Check for updates',
32 },
33 buttonInstallUpdate: {
34 id: 'settings.app.buttonInstallUpdate',
35 defaultMessage: '!!!Restart & install update',
36 },
37 updateStatusSearching: {
38 id: 'settings.app.updateStatusSearching',
39 defaultMessage: '!!!Is searching for update',
40 },
41 updateStatusAvailable: {
42 id: 'settings.app.updateStatusAvailable',
43 defaultMessage: '!!!Update available, downloading...',
44 },
45 updateStatusUpToDate: {
46 id: 'settings.app.updateStatusUpToDate',
47 defaultMessage: '!!!You are using the latest version of Franz',
48 },
49 currentVersion: {
50 id: 'settings.app.currentVersion',
51 defaultMessage: '!!!Current version:',
52 },
53});
54
55@observer
56export default class EditSettingsForm extends Component {
57 static propTypes = {
58 checkForUpdates: PropTypes.func.isRequired,
59 installUpdate: PropTypes.func.isRequired,
60 form: PropTypes.instanceOf(Form).isRequired,
61 onSubmit: PropTypes.func.isRequired,
62 isCheckingForUpdates: PropTypes.bool.isRequired,
63 isUpdateAvailable: PropTypes.bool.isRequired,
64 noUpdateAvailable: PropTypes.bool.isRequired,
65 updateIsReadyToInstall: PropTypes.bool.isRequired,
66 };
67
68 static contextTypes = {
69 intl: intlShape,
70 };
71
72 submit(e) {
73 e.preventDefault();
74 this.props.form.submit({
75 onSuccess: (form) => {
76 const values = form.values();
77 this.props.onSubmit(values);
78 },
79 onError: () => {},
80 });
81 }
82
83 render() {
84 const {
85 checkForUpdates,
86 installUpdate,
87 form,
88 isCheckingForUpdates,
89 isUpdateAvailable,
90 noUpdateAvailable,
91 updateIsReadyToInstall,
92 } = this.props;
93 const { intl } = this.context;
94
95 let updateButtonLabelMessage = messages.buttonSearchForUpdate;
96 if (isCheckingForUpdates) {
97 updateButtonLabelMessage = messages.updateStatusSearching;
98 } else if (isUpdateAvailable) {
99 updateButtonLabelMessage = messages.updateStatusAvailable;
100 } else {
101 updateButtonLabelMessage = messages.buttonSearchForUpdate;
102 }
103
104 return (
105 <div className="settings__main">
106 <div className="settings__header">
107 <h1>{intl.formatMessage(messages.headline)}</h1>
108 </div>
109 <div className="settings__body">
110 <form
111 onSubmit={e => this.submit(e)}
112 onChange={e => this.submit(e)}
113 id="form"
114 >
115 <h2>{intl.formatMessage(messages.headlineGeneral)}</h2>
116 <Toggle field={form.$('autoLaunchOnStart')} />
117 <Toggle field={form.$('runInBackground')} />
118 {process.platform === 'win32' && (
119 <Toggle field={form.$('minimizeToSystemTray')} />
120 )}
121 <h2>{intl.formatMessage(messages.headlineLanguage)}</h2>
122 <Select field={form.$('locale')} showLabel={false} />
123 <h2>{intl.formatMessage(messages.headlineUpdates)}</h2>
124 {updateIsReadyToInstall ? (
125 <Button
126 label={intl.formatMessage(messages.buttonInstallUpdate)}
127 onClick={installUpdate}
128 />
129 ) : (
130 <Button
131 label={intl.formatMessage(updateButtonLabelMessage)}
132 onClick={checkForUpdates}
133 disabled={isCheckingForUpdates || isUpdateAvailable}
134 loaded={!isCheckingForUpdates || !isUpdateAvailable}
135 />
136 )}
137 {noUpdateAvailable && (
138 <p>{intl.formatMessage(messages.updateStatusUpToDate)}</p>
139 )}
140 <br />
141 <Toggle field={form.$('beta')} />
142 {intl.formatMessage(messages.currentVersion)} {remote.app.getVersion()}
143 </form>
144 </div>
145 </div>
146 );
147 }
148}
diff --git a/src/components/settings/user/EditUserForm.js b/src/components/settings/user/EditUserForm.js
new file mode 100644
index 000000000..f36887fc2
--- /dev/null
+++ b/src/components/settings/user/EditUserForm.js
@@ -0,0 +1,145 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import { Link } from 'react-router';
6
7// import { Link } from 'react-router';
8
9import Form from '../../../lib/Form';
10import Input from '../../ui/Input';
11import Button from '../../ui/Button';
12import Radio from '../../ui/Radio';
13import Infobox from '../../ui/Infobox';
14
15const messages = defineMessages({
16 headline: {
17 id: 'settings.account.headline',
18 defaultMessage: '!!!Account',
19 },
20 headlineProfile: {
21 id: 'settings.account.headlineProfile',
22 defaultMessage: '!!!Update Profile',
23 },
24 headlineAccount: {
25 id: 'settings.account.headlineAccount',
26 defaultMessage: '!!!Account Information',
27 },
28 headlinePassword: {
29 id: 'settings.account.headlinePassword',
30 defaultMessage: '!!!Change Password',
31 },
32 successInfo: {
33 id: 'settings.account.successInfo',
34 defaultMessage: '!!!Your changes have been saved',
35 },
36 buttonSave: {
37 id: 'settings.account.buttonSave',
38 defaultMessage: '!!!Update profile',
39 },
40});
41
42@observer
43export default class EditServiceForm extends Component {
44 static propTypes = {
45 status: MobxPropTypes.observableArray.isRequired,
46 form: PropTypes.instanceOf(Form).isRequired,
47 onSubmit: PropTypes.func.isRequired,
48 isSaving: PropTypes.bool.isRequired,
49 };
50
51 static defaultProps = {
52 service: {},
53 };
54
55 static contextTypes = {
56 intl: intlShape,
57 };
58
59 submit(e) {
60 e.preventDefault();
61 this.props.form.submit({
62 onSuccess: (form) => {
63 const values = form.values();
64 this.props.onSubmit(values);
65 },
66 onError: () => {},
67 });
68 }
69
70 render() {
71 const {
72 // user,
73 status,
74 form,
75 isSaving,
76 } = this.props;
77 const { intl } = this.context;
78
79 return (
80 <div className="settings__main">
81 <div className="settings__header">
82 <span className="settings__header-item">
83 <Link to="/settings/user">
84 {intl.formatMessage(messages.headline)}
85 </Link>
86 </span>
87 <span className="separator" />
88 <span className="settings__header-item">
89 {intl.formatMessage(messages.headlineProfile)}
90 </span>
91 </div>
92 <div className="settings__body">
93 <form onSubmit={e => this.submit(e)} id="form">
94 {status.length > 0 && status.includes('data-updated') && (
95 <Infobox
96 type="success"
97 icon="checkbox-marked-circle-outline"
98 >
99 {intl.formatMessage(messages.successInfo)}
100 </Infobox>
101 )}
102 <h2>{intl.formatMessage(messages.headlineAccount)}</h2>
103 <div className="grid__row">
104 <Input field={form.$('firstname')} focus />
105 <Input field={form.$('lastname')} />
106 </div>
107 <Input field={form.$('email')} />
108 <Radio field={form.$('accountType')} />
109 {form.$('accountType').value === 'company' && (
110 <Input field={form.$('organization')} />
111 )}
112 <h2>{intl.formatMessage(messages.headlinePassword)}</h2>
113 <Input
114 field={form.$('oldPassword')}
115 showPasswordToggle
116 />
117 <Input
118 field={form.$('newPassword')}
119 showPasswordToggle
120 scorePassword
121 />
122 </form>
123 </div>
124 <div className="settings__controls">
125 {/* Save Button */}
126 {isSaving ? (
127 <Button
128 type="submit"
129 label={intl.formatMessage(messages.buttonSave)}
130 loaded={!isSaving}
131 buttonType="secondary"
132 disabled
133 />
134 ) : (
135 <Button
136 type="submit"
137 label={intl.formatMessage(messages.buttonSave)}
138 htmlForm="form"
139 />
140 )}
141 </div>
142 </div>
143 );
144 }
145}
diff --git a/src/components/ui/AppLoader.js b/src/components/ui/AppLoader.js
new file mode 100644
index 000000000..64a212969
--- /dev/null
+++ b/src/components/ui/AppLoader.js
@@ -0,0 +1,15 @@
1import React from 'react';
2
3import Appear from '../../components/ui/effects/Appear';
4import Loader from '../../components/ui/Loader';
5
6export default function () {
7 return (
8 <div className="app-loader">
9 <Appear>
10 <h1 className="app-loader__title">Franz</h1>
11 <Loader />
12 </Appear>
13 </div>
14 );
15}
diff --git a/src/components/ui/Button.js b/src/components/ui/Button.js
new file mode 100644
index 000000000..07e94192f
--- /dev/null
+++ b/src/components/ui/Button.js
@@ -0,0 +1,78 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import Loader from 'react-loader';
5import classnames from 'classnames';
6
7@observer
8export default class Button extends Component {
9 static propTypes = {
10 className: PropTypes.string,
11 label: PropTypes.string.isRequired,
12 disabled: PropTypes.bool,
13 onClick: PropTypes.func,
14 type: PropTypes.string,
15 buttonType: PropTypes.string,
16 loaded: PropTypes.bool,
17 htmlForm: PropTypes.string,
18 };
19
20 static defaultProps = {
21 className: null,
22 disabled: false,
23 onClick: () => {},
24 type: 'button',
25 buttonType: '',
26 loaded: true,
27 htmlForm: '',
28 };
29
30 element = null;
31
32 render() {
33 const {
34 label,
35 className,
36 disabled,
37 onClick,
38 type,
39 buttonType,
40 loaded,
41 htmlForm,
42 } = this.props;
43
44 const buttonProps = {
45 className: classnames({
46 'franz-form__button': true,
47 [`franz-form__button--${buttonType}`]: buttonType,
48 [`${className}`]: className,
49 }),
50 type,
51 };
52
53 if (disabled) {
54 buttonProps.disabled = true;
55 }
56
57 if (onClick) {
58 buttonProps.onClick = onClick;
59 }
60
61 if (htmlForm) {
62 buttonProps.form = htmlForm;
63 }
64
65 return (
66 <button {...buttonProps}>
67 <Loader
68 loaded={loaded}
69 lines={10}
70 scale={0.4}
71 color={buttonType === '' ? '#FFF' : '#373a3c'}
72 component="span"
73 />
74 {label}
75 </button>
76 );
77 }
78}
diff --git a/src/components/ui/InfoBar.js b/src/components/ui/InfoBar.js
new file mode 100644
index 000000000..aea2bd888
--- /dev/null
+++ b/src/components/ui/InfoBar.js
@@ -0,0 +1,88 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import classnames from 'classnames';
5import Loader from 'react-loader';
6
7// import { oneOrManyChildElements } from '../../prop-types';
8import Appear from '../ui/effects/Appear';
9
10@observer
11export default class InfoBar extends Component {
12 static propTypes = {
13 // eslint-disable-next-line
14 children: PropTypes.any.isRequired,
15 onClick: PropTypes.func,
16 type: PropTypes.string,
17 className: PropTypes.string,
18 ctaLabel: PropTypes.string,
19 ctaLoading: PropTypes.bool,
20 position: PropTypes.string,
21 sticky: PropTypes.bool,
22 onHide: PropTypes.func,
23 };
24
25 static defaultProps = {
26 onClick: () => null,
27 type: 'primary',
28 className: '',
29 ctaLabel: '',
30 ctaLoading: false,
31 position: 'bottom',
32 sticky: false,
33 onHide: () => null,
34 };
35
36 render() {
37 const {
38 children,
39 type,
40 className,
41 ctaLabel,
42 ctaLoading,
43 onClick,
44 position,
45 sticky,
46 onHide,
47 } = this.props;
48
49 let transitionName = 'slideUp';
50 if (position === 'top') {
51 transitionName = 'slideDown';
52 }
53
54 return (
55 <Appear
56 transitionName={transitionName}
57 className={classnames({
58 'info-bar': true,
59 [`info-bar--${type}`]: true,
60 [`info-bar--${position}`]: true,
61 [`${className}`]: true,
62 })}
63 >
64 <div onClick={onClick} className="info-bar__content">
65 {children}
66 {ctaLabel && (
67 <button className="info-bar__cta">
68 <Loader
69 loaded={!ctaLoading}
70 lines={10}
71 scale={0.3}
72 color="#FFF"
73 component="span"
74 />
75 {ctaLabel}
76 </button>
77 )}
78 </div>
79 {!sticky && (
80 <button
81 className="info-bar__close mdi mdi-close"
82 onClick={onHide}
83 />
84 )}
85 </Appear>
86 );
87 }
88}
diff --git a/src/components/ui/Infobox.js b/src/components/ui/Infobox.js
new file mode 100644
index 000000000..2d063c7ef
--- /dev/null
+++ b/src/components/ui/Infobox.js
@@ -0,0 +1,87 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import classnames from 'classnames';
5import Loader from 'react-loader';
6
7@observer
8export default class Infobox extends Component {
9 static propTypes = {
10 children: PropTypes.any.isRequired, // eslint-disable-line
11 icon: PropTypes.string,
12 type: PropTypes.string,
13 ctaOnClick: PropTypes.func,
14 ctaLabel: PropTypes.string,
15 ctaLoading: PropTypes.bool,
16 dismissable: PropTypes.bool,
17 };
18
19 static defaultProps = {
20 icon: '',
21 type: 'primary',
22 dismissable: false,
23 ctaOnClick: () => null,
24 ctaLabel: '',
25 ctaLoading: false,
26 };
27
28 state = {
29 dismissed: false,
30 };
31
32 render() {
33 const {
34 children,
35 icon,
36 type,
37 ctaLabel,
38 ctaLoading,
39 ctaOnClick,
40 dismissable,
41 } = this.props;
42
43 if (this.state.dismissed) {
44 return null;
45 }
46
47 return (
48 <div
49 className={classnames({
50 infobox: true,
51 [`infobox--${type}`]: type,
52 'infobox--default': !type,
53 })}
54 >
55 {icon && (
56 <i className={`mdi mdi-${icon}`} />
57 )}
58 <div className="infobox__content">
59 {children}
60 </div>
61 {ctaLabel && (
62 <button
63 className="infobox__cta"
64 onClick={ctaOnClick}
65 >
66 <Loader
67 loaded={!ctaLoading}
68 lines={10}
69 scale={0.3}
70 color="#FFF"
71 component="span"
72 />
73 {ctaLabel}
74 </button>
75 )}
76 {dismissable && (
77 <button
78 onClick={() => this.setState({
79 dismissed: true,
80 })}
81 className="infobox__delete mdi mdi-close"
82 />
83 )}
84 </div>
85 );
86 }
87}
diff --git a/src/components/ui/Input.js b/src/components/ui/Input.js
new file mode 100644
index 000000000..0bb9f23bf
--- /dev/null
+++ b/src/components/ui/Input.js
@@ -0,0 +1,148 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { Field } from 'mobx-react-form';
5import classnames from 'classnames';
6
7import { scorePassword as scorePasswordFunc } from '../../helpers/password-helpers';
8
9@observer
10export default class Input extends Component {
11 static propTypes = {
12 field: PropTypes.instanceOf(Field).isRequired,
13 className: PropTypes.string,
14 focus: PropTypes.bool,
15 showPasswordToggle: PropTypes.bool,
16 showLabel: PropTypes.bool,
17 scorePassword: PropTypes.bool,
18 prefix: PropTypes.string,
19 suffix: PropTypes.string,
20 };
21
22 static defaultProps = {
23 className: null,
24 focus: false,
25 showPasswordToggle: false,
26 showLabel: true,
27 scorePassword: false,
28 prefix: '',
29 suffix: '',
30 };
31
32 state = {
33 showPassword: false,
34 passwordScore: 0,
35 }
36
37 componentDidMount() {
38 if (this.props.focus) {
39 this.focus();
40 }
41 }
42
43 onChange(e) {
44 const { field, scorePassword } = this.props;
45
46 field.onChange(e);
47
48 if (scorePassword) {
49 this.setState({ passwordScore: scorePasswordFunc(field.value) });
50 }
51 }
52
53 focus() {
54 this.inputElement.focus();
55 }
56
57 inputElement = null;
58
59 render() {
60 const {
61 field,
62 className,
63 showPasswordToggle,
64 showLabel,
65 scorePassword,
66 prefix,
67 suffix,
68 } = this.props;
69
70 const { passwordScore } = this.state;
71
72 let type = field.type;
73 if (type === 'password' && this.state.showPassword) {
74 type = 'text';
75 }
76
77 return (
78 <div
79 className={classnames({
80 'franz-form__field': true,
81 'has-error': field.error,
82 [`${className}`]: className,
83 })}
84 >
85 <div className="franz-form__input-wrapper">
86 {prefix && (
87 <span className="franz-form__input-prefix">{prefix}</span>
88 )}
89 <input
90 id={field.id}
91 type={type}
92 className="franz-form__input"
93 name={field.name}
94 value={field.value}
95 placeholder={field.placeholder}
96 onChange={e => this.onChange(e)}
97 onBlur={field.onBlur}
98 onFocus={field.onFocus}
99 ref={(element) => { this.inputElement = element; }}
100 />
101 {suffix && (
102 <span className="franz-form__input-suffix">{suffix}</span>
103 )}
104 {showPasswordToggle && (
105 <button
106 type="button"
107 className={classnames({
108 'franz-form__input-modifier': true,
109 mdi: true,
110 'mdi-eye': !this.state.showPassword,
111 'mdi-eye-off': this.state.showPassword,
112 })}
113 onClick={() => this.setState({ showPassword: !this.state.showPassword })}
114 tabIndex="-1"
115 />
116 )}
117 {scorePassword && (
118 <div className="franz-form__password-score">
119 {/* <progress value={this.state.passwordScore} max="100" /> */}
120 <meter
121 value={passwordScore < 5 ? 5 : passwordScore}
122 low="30"
123 high="75"
124 optimum="100"
125 max="100"
126 />
127 </div>
128 )}
129 </div>
130 {field.label && showLabel && (
131 <label
132 className="franz-form__label"
133 htmlFor={field.name}
134 >
135 {field.label}
136 </label>
137 )}
138 {field.error && (
139 <div
140 className="franz-form__error"
141 >
142 {field.error}
143 </div>
144 )}
145 </div>
146 );
147 }
148}
diff --git a/src/components/ui/Link.js b/src/components/ui/Link.js
new file mode 100644
index 000000000..f5da921fa
--- /dev/null
+++ b/src/components/ui/Link.js
@@ -0,0 +1,78 @@
1import { shell } from 'electron';
2import React, { Component } from 'react';
3import PropTypes from 'prop-types';
4import { inject, observer } from 'mobx-react';
5import { RouterStore } from 'mobx-react-router';
6import classnames from 'classnames';
7
8import { oneOrManyChildElements } from '../../prop-types';
9import { matchRoute } from '../../helpers/routing-helpers';
10
11// TODO: create container component for this component
12
13@inject('stores') @observer
14export default class Link extends Component {
15 onClick(e) {
16 if (this.props.target === '_blank') {
17 e.preventDefault();
18 shell.openExternal(this.props.to);
19 }
20 }
21
22 render() {
23 const {
24 children,
25 stores,
26 to,
27 className,
28 activeClassName,
29 strictFilter,
30 } = this.props;
31 const { router } = stores;
32
33 let filter = `${to}(*action)`;
34 if (strictFilter) {
35 filter = `${to}`;
36 }
37
38 const match = matchRoute(filter, router.location.pathname);
39
40 const linkClasses = classnames({
41 [`${className}`]: true,
42 [`${activeClassName}`]: match,
43 });
44
45 return (
46 <a
47 href={router.history.createHref(to)}
48 className={linkClasses}
49 onClick={e => this.onClick(e)}
50 >
51 {children}
52 </a>
53 );
54 }
55}
56
57Link.wrappedComponent.propTypes = {
58 stores: PropTypes.shape({
59 router: PropTypes.instanceOf(RouterStore).isRequired,
60 }).isRequired,
61 children: PropTypes.oneOfType([
62 oneOrManyChildElements,
63 PropTypes.string,
64 ]).isRequired,
65 to: PropTypes.string.isRequired,
66 className: PropTypes.string,
67 activeClassName: PropTypes.string,
68 strictFilter: PropTypes.bool,
69 target: PropTypes.string,
70};
71
72Link.wrappedComponent.defaultProps = {
73 className: '',
74 activeClassName: '',
75 strictFilter: false,
76 target: '',
77 openInBrowser: false,
78};
diff --git a/src/components/ui/Loader.js b/src/components/ui/Loader.js
new file mode 100644
index 000000000..e4fbd96a2
--- /dev/null
+++ b/src/components/ui/Loader.js
@@ -0,0 +1,41 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import Loader from 'react-loader';
4
5import { oneOrManyChildElements } from '../../prop-types';
6
7export default class LoaderComponent extends Component {
8 static propTypes = {
9 children: oneOrManyChildElements,
10 loaded: PropTypes.bool,
11 className: PropTypes.string,
12 };
13
14 static defaultProps = {
15 children: null,
16 loaded: false,
17 className: '',
18 };
19
20 render() {
21 const {
22 children,
23 loaded,
24 className,
25 } = this.props;
26
27 return (
28 <Loader
29 loaded={loaded}
30 // lines={10}
31 width={4}
32 scale={0.6}
33 color="#373a3c"
34 component="span"
35 className={className}
36 >
37 {children}
38 </Loader>
39 );
40 }
41}
diff --git a/src/components/ui/Radio.js b/src/components/ui/Radio.js
new file mode 100644
index 000000000..b54cfc820
--- /dev/null
+++ b/src/components/ui/Radio.js
@@ -0,0 +1,89 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { Field } from 'mobx-react-form';
5import classnames from 'classnames';
6
7@observer
8export default class Radio extends Component {
9 static propTypes = {
10 field: PropTypes.instanceOf(Field).isRequired,
11 className: PropTypes.string,
12 focus: PropTypes.bool,
13 showLabel: PropTypes.bool,
14 };
15
16 static defaultProps = {
17 className: null,
18 focus: false,
19 showLabel: true,
20 };
21
22 componentDidMount() {
23 if (this.props.focus) {
24 this.focus();
25 }
26 }
27
28 focus() {
29 this.inputElement.focus();
30 }
31
32 inputElement = null;
33
34 render() {
35 const {
36 field,
37 className,
38 showLabel,
39 } = this.props;
40
41 return (
42 <div
43 className={classnames({
44 'franz-form__field': true,
45 'has-error': field.error,
46 [`${className}`]: className,
47 })}
48 >
49 {field.label && showLabel && (
50 <label
51 className="franz-form__label"
52 htmlFor={field.name}
53 >
54 {field.label}
55 </label>
56 )}
57 <div className="franz-form__radio-wrapper">
58 {field.options.map(type => (
59 <label
60 key={type.value}
61 htmlFor={`${field.id}-${type.value}`}
62 className={classnames({
63 'franz-form__radio': true,
64 'is-selected': field.value === type.value,
65 })}
66 >
67 <input
68 id={`${field.id}-${type.value}`}
69 type="radio"
70 name="type"
71 value={type.value}
72 onChange={field.onChange}
73 checked={field.value === type.value}
74 />
75 {type.label}
76 </label>
77 ))}
78 </div>
79 {field.error && (
80 <div
81 className="franz-form__error"
82 >
83 {field.error}
84 </div>
85 )}
86 </div>
87 );
88 }
89}
diff --git a/src/components/ui/SearchInput.js b/src/components/ui/SearchInput.js
new file mode 100644
index 000000000..bca412cef
--- /dev/null
+++ b/src/components/ui/SearchInput.js
@@ -0,0 +1,124 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import classnames from 'classnames';
5import uuidv1 from 'uuid/v1';
6import { debounce } from 'lodash';
7
8@observer
9export default class SearchInput extends Component {
10 static propTypes = {
11 value: PropTypes.string,
12 defaultValue: PropTypes.string,
13 className: PropTypes.string,
14 onChange: PropTypes.func,
15 onReset: PropTypes.func,
16 name: PropTypes.string,
17 throttle: PropTypes.bool,
18 throttleDelay: PropTypes.number,
19 };
20
21 static defaultProps = {
22 value: '',
23 defaultValue: '',
24 className: '',
25 name: uuidv1(),
26 throttle: false,
27 throttleDelay: 250,
28 onChange: () => null,
29 onReset: () => null,
30 }
31
32 constructor(props) {
33 super(props);
34
35 this.state = {
36 value: props.value || props.defaultValue,
37 };
38
39 this.throttledOnChange = debounce(this.throttledOnChange, this.props.throttleDelay);
40 }
41
42 onChange(e) {
43 const { throttle, onChange } = this.props;
44 const { value } = e.target;
45 this.setState({ value });
46
47 if (throttle) {
48 e.persist();
49 this.throttledOnChange(value);
50 } else {
51 onChange(value);
52 }
53 }
54
55 onClick() {
56 const { defaultValue } = this.props;
57 const { value } = this.state;
58
59 if (value === defaultValue) {
60 this.setState({ value: '' });
61 }
62
63 this.input.focus();
64 }
65
66 onBlur() {
67 const { defaultValue } = this.props;
68 const { value } = this.state;
69
70 if (value === '') {
71 this.setState({ value: defaultValue });
72 }
73 }
74
75 throttledOnChange(e) {
76 const { onChange } = this.props;
77
78 onChange(e);
79 }
80
81 reset() {
82 const { defaultValue, onReset } = this.props;
83 this.setState({ value: defaultValue });
84
85 onReset();
86 }
87
88 input = null;
89
90 render() {
91 const { className, name, defaultValue } = this.props;
92 const { value } = this.state;
93
94 return (
95 <div
96 className={classnames([
97 className,
98 'search-input',
99 ])}
100 >
101 <label
102 htmlFor={name}
103 className="mdi mdi-magnify"
104 onClick={() => this.onClick()}
105 />
106 <input
107 name={name}
108 type="text"
109 value={value}
110 onChange={e => this.onChange(e)}
111 onClick={() => this.onClick()}
112 onBlur={() => this.onBlur()}
113 ref={(ref) => { this.input = ref; }}
114 />
115 {value !== defaultValue && value.length > 0 && (
116 <span
117 className="mdi mdi-close-circle-outline"
118 onClick={() => this.reset()}
119 />
120 )}
121 </div>
122 );
123 }
124}
diff --git a/src/components/ui/Select.js b/src/components/ui/Select.js
new file mode 100644
index 000000000..2a877af3e
--- /dev/null
+++ b/src/components/ui/Select.js
@@ -0,0 +1,70 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { Field } from 'mobx-react-form';
5import classnames from 'classnames';
6
7@observer
8export default class Select extends Component {
9 static propTypes = {
10 field: PropTypes.instanceOf(Field).isRequired,
11 className: PropTypes.string,
12 showLabel: PropTypes.bool,
13 };
14
15 static defaultProps = {
16 className: null,
17 focus: false,
18 showLabel: true,
19 };
20
21 render() {
22 const {
23 field,
24 className,
25 showLabel,
26 } = this.props;
27
28 return (
29 <div
30 className={classnames({
31 'franz-form__field': true,
32 'has-error': field.error,
33 [`${className}`]: className,
34 })}
35 >
36 {field.label && showLabel && (
37 <label
38 className="franz-form__label"
39 htmlFor={field.name}
40 >
41 {field.label}
42 </label>
43 )}
44 <select
45 onChange={field.onChange}
46 id={field.id}
47 defaultValue={field.value}
48 className="franz-form__select"
49 >
50 {field.options.map(type => (
51 <option
52 key={type.value}
53 value={type.value}
54 // selected={field.value === }
55 >
56 {type.label}
57 </option>
58 ))}
59 </select>
60 {field.error && (
61 <div
62 className="franz-form__error"
63 >
64 {field.error}
65 </div>
66 )}
67 </div>
68 );
69 }
70}
diff --git a/src/components/ui/Subscription.js b/src/components/ui/Subscription.js
new file mode 100644
index 000000000..ada5cc3e0
--- /dev/null
+++ b/src/components/ui/Subscription.js
@@ -0,0 +1,265 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Form from '../../lib/Form';
7import Radio from '../ui/Radio';
8import Button from '../ui/Button';
9import Loader from '../ui/Loader';
10
11import { required } from '../../helpers/validation-helpers';
12
13const messages = defineMessages({
14 submitButtonLabel: {
15 id: 'subscription.submit.label',
16 defaultMessage: '!!!Support the development of Franz',
17 },
18 paymentSessionError: {
19 id: 'subscription.paymentSessionError',
20 defaultMessage: '!!!Could not initialize payment form',
21 },
22 typeFree: {
23 id: 'subscription.type.free',
24 defaultMessage: '!!!free',
25 },
26 typeMonthly: {
27 id: 'subscription.type.month',
28 defaultMessage: '!!!month',
29 },
30 typeYearly: {
31 id: 'subscription.type.year',
32 defaultMessage: '!!!year',
33 },
34 typeMining: {
35 id: 'subscription.type.mining',
36 defaultMessage: '!!!Support Franz with processing power',
37 },
38 includedFeatures: {
39 id: 'subscription.includedFeatures',
40 defaultMessage: '!!!The Franz Premium Supporter Account includes',
41 },
42 features: {
43 unlimitedServices: {
44 id: 'subscription.features.unlimitedServices',
45 defaultMessage: '!!!Add unlimited services',
46 },
47 onpremise: {
48 id: 'subscription.features.onpremise',
49 defaultMessage: '!!!Add on-premise/hosted services like HipChat',
50 },
51 customServices: {
52 id: 'subscription.features.customServices',
53 defaultMessage: '!!!Add your custom services',
54 },
55 encryptedSync: {
56 id: 'subscription.features.encryptedSync',
57 defaultMessage: '!!!Encrypted session synchronization',
58 },
59 vpn: {
60 id: 'subscription.features.vpn',
61 defaultMessage: '!!!Proxy & VPN support',
62 },
63 ads: {
64 id: 'subscription.features.ads',
65 defaultMessage: '!!!No ads, ever!',
66 },
67 comingSoon: {
68 id: 'subscription.features.comingSoon',
69 defaultMessage: '!!!coming soon',
70 },
71 },
72 miningHeadline: {
73 id: 'subscription.mining.headline',
74 defaultMessage: '!!!How does this work?',
75 },
76 experimental: {
77 id: 'subscription.mining.experimental',
78 defaultMessage: '!!!experimental',
79 },
80 miningDetail1: {
81 id: 'subscription.mining.line1',
82 defaultMessage: '!!!By enabling "Support with processing power", Franz will use about 20-50% of your CPU to mine cryptocurrency Monero which equals approximately $ 5/year.',
83 },
84 miningDetail2: {
85 id: 'subscription.mining.line2',
86 defaultMessage: '!!!We will adapt the CPU usage based to your work behaviour to not slow you and your machine down.',
87 },
88 miningDetail3: {
89 id: 'subscription.mining.line3',
90 defaultMessage: '!!!As long as the miner is active, you will have unlimited access to all the Franz Premium Supporter Features.',
91 },
92 miningMoreInfo: {
93 id: 'subscription.mining.moreInformation',
94 defaultMessage: '!!!Get more information about this plan',
95 },
96});
97
98@observer
99export default class SubscriptionForm extends Component {
100 static propTypes = {
101 plan: MobxPropTypes.objectOrObservableObject.isRequired,
102 isLoading: PropTypes.bool.isRequired,
103 handlePayment: PropTypes.func.isRequired,
104 retryPlanRequest: PropTypes.func.isRequired,
105 isCreatingHostedPage: PropTypes.bool.isRequired,
106 error: PropTypes.bool.isRequired,
107 showSkipOption: PropTypes.bool,
108 skipAction: PropTypes.func,
109 skipButtonLabel: PropTypes.string,
110 hideInfo: PropTypes.bool.isRequired,
111 openExternalUrl: PropTypes.func.isRequired,
112 };
113
114 static defaultProps ={
115 content: '',
116 showSkipOption: false,
117 skipAction: () => null,
118 skipButtonLabel: '',
119 }
120
121 static contextTypes = {
122 intl: intlShape,
123 };
124
125 componentWillMount() {
126 this.form = this.prepareForm();
127 }
128
129 prepareForm() {
130 const { intl } = this.context;
131
132 const form = {
133 fields: {
134 paymentTier: {
135 value: 'year',
136 validate: [required],
137 options: [{
138 value: 'month',
139 label: `$ ${Object.hasOwnProperty.call(this.props.plan, 'month')
140 ? `${this.props.plan.month.price} / ${intl.formatMessage(messages.typeMonthly)}`
141 : 'monthly'}`,
142 }, {
143 value: 'year',
144 label: `$ ${Object.hasOwnProperty.call(this.props.plan, 'year')
145 ? `${this.props.plan.year.price} / ${intl.formatMessage(messages.typeYearly)}`
146 : 'yearly'}`,
147 }, {
148 value: 'mining',
149 label: intl.formatMessage(messages.typeMining),
150 }],
151 },
152 },
153 };
154
155 if (this.props.showSkipOption) {
156 form.fields.paymentTier.options.unshift({
157 value: 'skip',
158 label: `$ 0 / ${intl.formatMessage(messages.typeFree)}`,
159 });
160 }
161
162 return new Form(form, this.context.intl);
163 }
164
165 render() {
166 const {
167 isLoading,
168 isCreatingHostedPage,
169 handlePayment,
170 retryPlanRequest,
171 error,
172 showSkipOption,
173 skipAction,
174 skipButtonLabel,
175 hideInfo,
176 openExternalUrl,
177 } = this.props;
178 const { intl } = this.context;
179
180 if (error) {
181 return (
182 <Button
183 label="Reload"
184 onClick={retryPlanRequest}
185 isLoaded={!isLoading}
186 />
187 );
188 }
189
190 return (
191 <Loader loaded={!isLoading}>
192 <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" />
193 {!hideInfo && (
194 <div className="subscription__premium-info">
195 {this.form.$('paymentTier').value !== 'mining' && (
196 <div>
197 <p>
198 <strong>{intl.formatMessage(messages.includedFeatures)}</strong>
199 </p>
200 <div className="subscription">
201 <ul className="subscription__premium-features">
202 <li>{intl.formatMessage(messages.features.onpremise)}</li>
203 <li>
204 {intl.formatMessage(messages.features.encryptedSync)}
205 <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span>
206 </li>
207 <li>
208 {intl.formatMessage(messages.features.customServices)}
209 <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span>
210 </li>
211 <li>
212 {intl.formatMessage(messages.features.vpn)}
213 <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span>
214 </li>
215 <li>
216 {intl.formatMessage(messages.features.ads)}
217 </li>
218 </ul>
219 </div>
220 </div>
221 )}
222 {this.form.$('paymentTier').value === 'mining' && (
223 <div className="subscription mining-details">
224 <p>
225 <strong>{intl.formatMessage(messages.miningHeadline)}</strong>
226 &nbsp;
227 <span className="badge">{intl.formatMessage(messages.experimental)}</span>
228 </p>
229 <p>{intl.formatMessage(messages.miningDetail1)}</p>
230 <p>{intl.formatMessage(messages.miningDetail2)}</p>
231 <p>{intl.formatMessage(messages.miningDetail3)}</p>
232 <p>
233 <button
234 onClick={() => openExternalUrl({ url: 'http://meetfranz.com/mining' })}
235 >
236 {intl.formatMessage(messages.miningMoreInfo)}
237 </button>
238 </p>
239 </div>
240 )}
241 </div>
242 )}
243 <div>
244 {error.code === 'no-payment-session' && (
245 <p className="error-message center">{intl.formatMessage(messages.paymentSessionError)}</p>
246 )}
247 </div>
248 {showSkipOption && this.form.$('paymentTier').value === 'skip' ? (
249 <Button
250 label={skipButtonLabel}
251 className="auth__button"
252 onClick={skipAction}
253 />
254 ) : (
255 <Button
256 label={intl.formatMessage(messages.submitButtonLabel)}
257 className="auth__button"
258 loaded={!isCreatingHostedPage}
259 onClick={() => handlePayment(this.form.$('paymentTier').value)}
260 />
261 )}
262 </Loader>
263 );
264 }
265}
diff --git a/src/components/ui/SubscriptionPopup.js b/src/components/ui/SubscriptionPopup.js
new file mode 100644
index 000000000..72b6ccd98
--- /dev/null
+++ b/src/components/ui/SubscriptionPopup.js
@@ -0,0 +1,84 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import Webview from 'react-electron-web-view';
6
7import Button from '../ui/Button';
8
9const messages = defineMessages({
10 buttonCancel: {
11 id: 'subscriptionPopup.buttonCancel',
12 defaultMessage: '!!!Cancel',
13 },
14 buttonDone: {
15 id: 'subscriptionPopup.buttonDone',
16 defaultMessage: '!!!Done',
17 },
18});
19
20@observer
21export default class SubscriptionPopup extends Component {
22 static propTypes = {
23 url: PropTypes.string.isRequired,
24 closeWindow: PropTypes.func.isRequired,
25 completeCheck: PropTypes.func.isRequired,
26 isCompleted: PropTypes.bool.isRequired,
27 };
28
29 static contextTypes = {
30 intl: intlShape,
31 };
32
33 state = {
34 isFakeLoading: false,
35 };
36
37 // We delay the window closing a bit in order to give
38 // the Recurly webhook a few seconds to do it's magic
39 delayedCloseWindow() {
40 this.setState({
41 isFakeLoading: true,
42 });
43
44 setTimeout(() => {
45 this.props.closeWindow();
46 }, 4000);
47 }
48
49 render() {
50 const { url, closeWindow, completeCheck, isCompleted } = this.props;
51 const { intl } = this.context;
52
53 return (
54 <div className="subscription-popup">
55 <div className="subscription-popup__content">
56 <Webview
57 className="subscription-popup__webview"
58
59 autosize
60 src={url}
61 disablewebsecurity
62 onDidNavigate={completeCheck}
63 // onNewWindow={(event, url, frameName, options) =>
64 // openWindow({ event, url, frameName, options })}
65 />
66 </div>
67 <div className="subscription-popup__toolbar franz-form">
68 <Button
69 label={intl.formatMessage(messages.buttonCancel)}
70 buttonType="secondary"
71 onClick={closeWindow}
72 disabled={isCompleted}
73 />
74 <Button
75 label={intl.formatMessage(messages.buttonDone)}
76 onClick={() => this.delayedCloseWindow()}
77 disabled={!isCompleted}
78 loaded={!this.state.isFakeLoading}
79 />
80 </div>
81 </div>
82 );
83 }
84}
diff --git a/src/components/ui/Tabs/TabItem.js b/src/components/ui/Tabs/TabItem.js
new file mode 100644
index 000000000..9ff9f009e
--- /dev/null
+++ b/src/components/ui/Tabs/TabItem.js
@@ -0,0 +1,17 @@
1import React, { Component } from 'react';
2
3import { oneOrManyChildElements } from '../../../prop-types';
4
5export default class TabItem extends Component {
6 static propTypes = {
7 children: oneOrManyChildElements.isRequired,
8 }
9
10 render() {
11 const { children } = this.props;
12
13 return (
14 <div>{children}</div>
15 );
16 }
17}
diff --git a/src/components/ui/Tabs/Tabs.js b/src/components/ui/Tabs/Tabs.js
new file mode 100644
index 000000000..50397f9bb
--- /dev/null
+++ b/src/components/ui/Tabs/Tabs.js
@@ -0,0 +1,69 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import classnames from 'classnames';
5
6import { oneOrManyChildElements } from '../../../prop-types';
7
8@observer
9export default class Tab extends Component {
10 static propTypes = {
11 children: oneOrManyChildElements.isRequired,
12 active: PropTypes.number,
13 };
14
15 static defaultProps = {
16 active: 0,
17 };
18
19 componentWillMount() {
20 this.setState({ active: this.props.active });
21 }
22
23 switchTab(index) {
24 this.setState({ active: index });
25 }
26
27 render() {
28 const { children: childElements } = this.props;
29 const children = childElements.filter(c => !!c);
30
31 if (children.length === 1) {
32 return <div>{children}</div>;
33 }
34
35 return (
36 <div className="content-tabs">
37 <div className="content-tabs__tabs">
38 {React.Children.map(children, (child, i) => (
39 <button
40 key={i}
41 className={classnames({
42 'content-tabs__item': true,
43 'is-active': this.state.active === i,
44 })}
45 onClick={() => this.switchTab(i)}
46 type="button"
47 >
48 {child.props.title}
49 </button>
50 ))}
51 </div>
52 <div className="content-tabs__content">
53 {React.Children.map(children, (child, i) => (
54 <div
55 key={i}
56 className={classnames({
57 'content-tabs__item': true,
58 'is-active': this.state.active === i,
59 })}
60 type="button"
61 >
62 {child}
63 </div>
64 ))}
65 </div>
66 </div>
67 );
68 }
69}
diff --git a/src/components/ui/Tabs/index.js b/src/components/ui/Tabs/index.js
new file mode 100644
index 000000000..e4adb62c7
--- /dev/null
+++ b/src/components/ui/Tabs/index.js
@@ -0,0 +1,6 @@
1import Tabs from './Tabs';
2import TabItem from './TabItem';
3
4export default Tabs;
5
6export { TabItem };
diff --git a/src/components/ui/Toggle.js b/src/components/ui/Toggle.js
new file mode 100644
index 000000000..62d46393e
--- /dev/null
+++ b/src/components/ui/Toggle.js
@@ -0,0 +1,67 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import classnames from 'classnames';
5import { Field } from 'mobx-react-form';
6
7@observer
8export default class Toggle extends Component {
9 static propTypes = {
10 field: PropTypes.instanceOf(Field).isRequired,
11 className: PropTypes.string,
12 showLabel: PropTypes.bool,
13 };
14
15 static defaultProps = {
16 className: '',
17 showLabel: true,
18 };
19
20 onChange(e) {
21 const { field } = this.props;
22
23 field.onChange(e);
24 }
25
26 render() {
27 const {
28 field,
29 className,
30 showLabel,
31 } = this.props;
32
33 if (field.value === '' && field.default !== '') {
34 field.value = field.default;
35 }
36
37 return (
38 <div
39 className={classnames([
40 'franz-form__field',
41 'franz-form__toggle-wrapper',
42 className,
43 ])}
44 >
45 <label
46 htmlFor={field.id}
47 className={classnames({
48 'franz-form__toggle': true,
49 'is-active': field.value,
50 })}
51 >
52 <div className="franz-form__toggle-button" />
53 <input
54 type="checkbox"
55 id={field.id}
56 name={field.name}
57 value={field.name}
58 checked={field.value}
59 onChange={e => this.onChange(e)}
60 />
61 </label>
62 {field.error && <div className={field.error}>{field.error}</div>}
63 {field.label && showLabel && <label className="franz-form__label" htmlFor={field.id}>{field.label}</label>}
64 </div>
65 );
66 }
67}
diff --git a/src/components/ui/effects/Appear.js b/src/components/ui/effects/Appear.js
new file mode 100644
index 000000000..1255fce2e
--- /dev/null
+++ b/src/components/ui/effects/Appear.js
@@ -0,0 +1,51 @@
1/* eslint-disable react/no-did-mount-set-state */
2import React, { Component } from 'react';
3import PropTypes from 'prop-types';
4import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
5
6export default class Appear extends Component {
7 static propTypes = {
8 children: PropTypes.any.isRequired, // eslint-disable-line
9 transitionName: PropTypes.string,
10 className: PropTypes.string,
11 };
12
13 static defaultProps = {
14 transitionName: 'fadeIn',
15 className: '',
16 };
17
18 state = {
19 mounted: false,
20 };
21
22 componentDidMount() {
23 this.setState({ mounted: true });
24 }
25
26 render() {
27 const {
28 children,
29 transitionName,
30 className,
31 } = this.props;
32
33 if (!this.state.mounted) {
34 return null;
35 }
36
37 return (
38 <ReactCSSTransitionGroup
39 transitionName={transitionName}
40 transitionAppear
41 transitionLeave
42 transitionAppearTimeout={1500}
43 transitionEnterTimeout={1500}
44 transitionLeaveTimeout={1500}
45 className={className}
46 >
47 {children}
48 </ReactCSSTransitionGroup>
49 );
50 }
51}
diff --git a/src/config.js b/src/config.js
new file mode 100644
index 000000000..acbf57f3c
--- /dev/null
+++ b/src/config.js
@@ -0,0 +1,5 @@
1export const CHECK_INTERVAL = 1000 * 3600; // How often should we perform checks
2export const LOCAL_API = 'http://localhost:3000';
3export const DEV_API = 'https://dev.franzinfra.com';
4export const LIVE_API = 'https://api.franzinfra.com';
5export const GA_ID = 'UA-74126766-6';
diff --git a/src/containers/auth/AuthLayoutContainer.js b/src/containers/auth/AuthLayoutContainer.js
new file mode 100644
index 000000000..004054fdd
--- /dev/null
+++ b/src/containers/auth/AuthLayoutContainer.js
@@ -0,0 +1,47 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4
5import AuthLayout from '../../components/auth/AuthLayout';
6import AppStore from '../../stores/AppStore';
7import GlobalErrorStore from '../../stores/GlobalErrorStore';
8
9import { oneOrManyChildElements } from '../../prop-types';
10
11@inject('stores', 'actions') @observer
12export default class AuthLayoutContainer extends Component {
13 static propTypes = {
14 children: oneOrManyChildElements.isRequired,
15 location: PropTypes.shape({
16 pathname: PropTypes.string.isRequired,
17 }).isRequired,
18 };
19
20 render() {
21 const { stores, actions, children, location } = this.props;
22 return (
23 <AuthLayout
24 error={stores.globalError.response}
25 pathname={location.pathname}
26 isOnline={stores.app.isOnline}
27 isAPIHealthy={!stores.app.healthCheckRequest.isError}
28 retryHealthCheck={actions.app.healthCheck}
29 isHealthCheckLoading={stores.app.healthCheckRequest.isExecuting}
30 >
31 {children}
32 </AuthLayout>
33 );
34 }
35}
36
37AuthLayoutContainer.wrappedComponent.propTypes = {
38 stores: PropTypes.shape({
39 app: PropTypes.instanceOf(AppStore).isRequired,
40 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired,
41 }).isRequired,
42 actions: PropTypes.shape({
43 app: PropTypes.shape({
44 healthCheck: PropTypes.func.isRequired,
45 }).isRequired,
46 }).isRequired,
47};
diff --git a/src/containers/auth/ImportScreen.js b/src/containers/auth/ImportScreen.js
new file mode 100644
index 000000000..ddd56ffb6
--- /dev/null
+++ b/src/containers/auth/ImportScreen.js
@@ -0,0 +1,41 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import Import from '../../components/auth/Import';
5import UserStore from '../../stores/UserStore';
6import { gaPage } from '../../lib/analytics';
7
8@inject('stores', 'actions') @observer
9export default class ImportScreen extends Component {
10 componentDidMount() {
11 gaPage('Auth/Import');
12 }
13
14 render() {
15 const { actions, stores } = this.props;
16
17 if (stores.user.isImportLegacyServicesCompleted) {
18 stores.router.push(stores.user.inviteRoute);
19 }
20
21 return (
22 <Import
23 services={stores.user.legacyServices}
24 onSubmit={actions.user.importLegacyServices}
25 isSubmitting={stores.user.isImportLegacyServicesExecuting}
26 inviteRoute={stores.user.inviteRoute}
27 />
28 );
29 }
30}
31
32ImportScreen.wrappedComponent.propTypes = {
33 actions: PropTypes.shape({
34 user: PropTypes.shape({
35 importLegacyServices: PropTypes.func.isRequired,
36 }).isRequired,
37 }).isRequired,
38 stores: PropTypes.shape({
39 user: PropTypes.instanceOf(UserStore).isRequired,
40 }).isRequired,
41};
diff --git a/src/containers/auth/InviteScreen.js b/src/containers/auth/InviteScreen.js
new file mode 100644
index 000000000..51971f436
--- /dev/null
+++ b/src/containers/auth/InviteScreen.js
@@ -0,0 +1,29 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import Invite from '../../components/auth/Invite';
5import { gaPage } from '../../lib/analytics';
6
7@inject('stores', 'actions') @observer
8export default class InviteScreen extends Component {
9 componentDidMount() {
10 gaPage('Auth/Invite');
11 }
12
13 render() {
14 const { actions } = this.props;
15 return (
16 <Invite
17 onSubmit={actions.user.invite}
18 />
19 );
20 }
21}
22
23InviteScreen.wrappedComponent.propTypes = {
24 actions: PropTypes.shape({
25 user: PropTypes.shape({
26 invite: PropTypes.func.isRequired,
27 }).isRequired,
28 }).isRequired,
29};
diff --git a/src/containers/auth/LoginScreen.js b/src/containers/auth/LoginScreen.js
new file mode 100644
index 000000000..9e22c5141
--- /dev/null
+++ b/src/containers/auth/LoginScreen.js
@@ -0,0 +1,45 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import Login from '../../components/auth/Login';
5import UserStore from '../../stores/UserStore';
6import { gaPage } from '../../lib/analytics';
7
8import { globalError as globalErrorPropType } from '../../prop-types';
9
10@inject('stores', 'actions') @observer
11export default class LoginScreen extends Component {
12 static propTypes = {
13 error: globalErrorPropType.isRequired,
14 };
15
16 componentDidMount() {
17 gaPage('Auth/Login');
18 }
19
20 render() {
21 const { actions, stores, error } = this.props;
22 return (
23 <Login
24 onSubmit={actions.user.login}
25 isSubmitting={stores.user.loginRequest.isExecuting}
26 isTokenExpired={stores.user.isTokenExpired}
27 isServerLogout={stores.user.logoutReason === stores.user.logoutReasonTypes.SERVER}
28 signupRoute={stores.user.signupRoute}
29 passwordRoute={stores.user.passwordRoute}
30 error={error}
31 />
32 );
33 }
34}
35
36LoginScreen.wrappedComponent.propTypes = {
37 actions: PropTypes.shape({
38 user: PropTypes.shape({
39 login: PropTypes.func.isRequired,
40 }).isRequired,
41 }).isRequired,
42 stores: PropTypes.shape({
43 user: PropTypes.instanceOf(UserStore).isRequired,
44 }).isRequired,
45};
diff --git a/src/containers/auth/PasswordScreen.js b/src/containers/auth/PasswordScreen.js
new file mode 100644
index 000000000..d88cb08e6
--- /dev/null
+++ b/src/containers/auth/PasswordScreen.js
@@ -0,0 +1,38 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import Password from '../../components/auth/Password';
5import UserStore from '../../stores/UserStore';
6import { gaPage } from '../../lib/analytics';
7
8@inject('stores', 'actions') @observer
9export default class PasswordScreen extends Component {
10 componentDidMount() {
11 gaPage('Auth/Password Retrieve');
12 }
13
14 render() {
15 const { actions, stores } = this.props;
16
17 return (
18 <Password
19 onSubmit={actions.user.retrievePassword}
20 isSubmitting={stores.user.passwordRequest.isExecuting}
21 signupRoute={stores.user.signupRoute}
22 loginRoute={stores.user.loginRoute}
23 status={stores.user.actionStatus}
24 />
25 );
26 }
27}
28
29PasswordScreen.wrappedComponent.propTypes = {
30 actions: PropTypes.shape({
31 user: PropTypes.shape({
32 retrievePassword: PropTypes.func.isRequired,
33 }).isRequired,
34 }).isRequired,
35 stores: PropTypes.shape({
36 user: PropTypes.instanceOf(UserStore).isRequired,
37 }).isRequired,
38};
diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js
new file mode 100644
index 000000000..7e1586535
--- /dev/null
+++ b/src/containers/auth/PricingScreen.js
@@ -0,0 +1,53 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { RouterStore } from 'mobx-react-router';
5
6import Pricing from '../../components/auth/Pricing';
7import UserStore from '../../stores/UserStore';
8import PaymentStore from '../../stores/PaymentStore';
9import { gaPage } from '../../lib/analytics';
10
11import { globalError as globalErrorPropType } from '../../prop-types';
12
13@inject('stores', 'actions') @observer
14export default class PricingScreen extends Component {
15 static propTypes = {
16 error: globalErrorPropType.isRequired,
17 };
18
19 componentDidMount() {
20 gaPage('Auth/Pricing');
21 }
22
23 render() {
24 const { actions, stores, error } = this.props;
25
26 const nextStepRoute = stores.user.legacyServices.length ? stores.user.importRoute : stores.user.inviteRoute;
27
28 return (
29 <Pricing
30 donor={stores.user.data.donor || {}}
31 onSubmit={actions.user.signup}
32 onCloseSubscriptionWindow={() => this.props.stores.router.push(nextStepRoute)}
33 isLoading={stores.payment.plansRequest.isExecuting}
34 isLoadingUser={stores.user.getUserInfoRequest.isExecuting}
35 error={error}
36 skipAction={() => this.props.stores.router.push(nextStepRoute)}
37 />
38 );
39 }
40}
41
42PricingScreen.wrappedComponent.propTypes = {
43 actions: PropTypes.shape({
44 user: PropTypes.shape({
45 signup: PropTypes.func.isRequired,
46 }).isRequired,
47 }).isRequired,
48 stores: PropTypes.shape({
49 user: PropTypes.instanceOf(UserStore).isRequired,
50 payment: PropTypes.instanceOf(PaymentStore).isRequired,
51 router: PropTypes.instanceOf(RouterStore).isRequired,
52 }).isRequired,
53};
diff --git a/src/containers/auth/SignupScreen.js b/src/containers/auth/SignupScreen.js
new file mode 100644
index 000000000..3b86ab138
--- /dev/null
+++ b/src/containers/auth/SignupScreen.js
@@ -0,0 +1,43 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4
5import Signup from '../../components/auth/Signup';
6import UserStore from '../../stores/UserStore';
7import { gaPage } from '../../lib/analytics';
8
9import { globalError as globalErrorPropType } from '../../prop-types';
10
11@inject('stores', 'actions') @observer
12export default class SignupScreen extends Component {
13 static propTypes = {
14 error: globalErrorPropType.isRequired,
15 };
16
17 componentDidMount() {
18 gaPage('Auth/Signup');
19 }
20
21 render() {
22 const { actions, stores, error } = this.props;
23 return (
24 <Signup
25 onSubmit={actions.user.signup}
26 isSubmitting={stores.user.signupRequest.isExecuting}
27 loginRoute={stores.user.loginRoute}
28 error={error}
29 />
30 );
31 }
32}
33
34SignupScreen.wrappedComponent.propTypes = {
35 actions: PropTypes.shape({
36 user: PropTypes.shape({
37 signup: PropTypes.func.isRequired,
38 }).isRequired,
39 }).isRequired,
40 stores: PropTypes.shape({
41 user: PropTypes.instanceOf(UserStore).isRequired,
42 }).isRequired,
43};
diff --git a/src/containers/auth/WelcomeScreen.js b/src/containers/auth/WelcomeScreen.js
new file mode 100644
index 000000000..e413264a6
--- /dev/null
+++ b/src/containers/auth/WelcomeScreen.js
@@ -0,0 +1,34 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4
5import Welcome from '../../components/auth/Welcome';
6import UserStore from '../../stores/UserStore';
7import RecipePreviewsStore from '../../stores/RecipePreviewsStore';
8import { gaPage } from '../../lib/analytics';
9
10@inject('stores', 'actions') @observer
11export default class LoginScreen extends Component {
12 componentDidMount() {
13 gaPage('Auth/Welcome');
14 }
15
16 render() {
17 const { user, recipePreviews } = this.props.stores;
18
19 return (
20 <Welcome
21 loginRoute={user.loginRoute}
22 signupRoute={user.signupRoute}
23 recipes={recipePreviews.featured}
24 />
25 );
26 }
27}
28
29LoginScreen.wrappedComponent.propTypes = {
30 stores: PropTypes.shape({
31 user: PropTypes.instanceOf(UserStore).isRequired,
32 recipePreviews: PropTypes.instanceOf(RecipePreviewsStore).isRequired,
33 }).isRequired,
34};
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
new file mode 100644
index 000000000..aa7f7952a
--- /dev/null
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -0,0 +1,166 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4
5import AppStore from '../../stores/AppStore';
6import RecipesStore from '../../stores/RecipesStore';
7import ServicesStore from '../../stores/ServicesStore';
8import UIStore from '../../stores/UIStore';
9import NewsStore from '../../stores/NewsStore';
10import UserStore from '../../stores/UserStore';
11import RequestStore from '../../stores/RequestStore';
12import GlobalErrorStore from '../../stores/GlobalErrorStore';
13
14import { oneOrManyChildElements } from '../../prop-types';
15import AppLayout from '../../components/layout/AppLayout';
16import Sidebar from '../../components/layout/Sidebar';
17import Services from '../../components/services/content/Services';
18import AppLoader from '../../components/ui/AppLoader';
19
20@inject('stores', 'actions') @observer
21export default class AppLayoutContainer extends Component {
22 static defaultProps = {
23 children: null,
24 };
25
26 render() {
27 const {
28 app,
29 services,
30 ui,
31 news,
32 globalError,
33 user,
34 requests,
35 } = this.props.stores;
36
37 const {
38 setActive,
39 handleIPCMessage,
40 setWebviewReference,
41 openWindow,
42 reloadUpdatedServices,
43 reorder,
44 reload,
45 toggleNotifications,
46 deleteService,
47 updateService,
48 } = this.props.actions.service;
49
50 const { hide } = this.props.actions.news;
51
52 const { retryRequiredRequests } = this.props.actions.requests;
53
54 const {
55 installUpdate,
56 } = this.props.actions.app;
57
58 const {
59 openSettings,
60 closeSettings,
61 } = this.props.actions.ui;
62
63 const { children } = this.props;
64 const allServices = services.enabled;
65
66 const isLoadingServices = services.allServicesRequest.isExecuting
67 && services.allServicesRequest.isExecutingFirstTime;
68
69 // const isLoadingRecipes = recipes.allRecipesRequest.isExecuting
70 // && recipes.allRecipesRequest.isExecutingFirstTime;
71
72 if (isLoadingServices) {
73 return (
74 <AppLoader />
75 );
76 }
77
78 const sidebar = (
79 <Sidebar
80 services={allServices}
81 setActive={setActive}
82 openSettings={openSettings}
83 closeSettings={closeSettings}
84 reorder={reorder}
85 reload={reload}
86 toggleNotifications={toggleNotifications}
87 deleteService={deleteService}
88 updateService={updateService}
89 isPremiumUser={user.data.isPremium}
90 />
91 );
92
93 const servicesContainer = (
94 <Services
95 // settings={allSettings}
96 services={allServices}
97 handleIPCMessage={handleIPCMessage}
98 setWebviewReference={setWebviewReference}
99 openWindow={openWindow}
100 />
101 );
102
103 return (
104 <AppLayout
105 isOnline={app.isOnline}
106 showServicesUpdatedInfoBar={ui.showServicesUpdatedInfoBar}
107 appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED}
108 sidebar={sidebar}
109 services={servicesContainer}
110 news={news.latest}
111 removeNewsItem={hide}
112 reloadServicesAfterUpdate={reloadUpdatedServices}
113 installAppUpdate={installUpdate}
114 globalError={globalError.error}
115 showRequiredRequestsError={requests.showRequiredRequestsError}
116 areRequiredRequestsSuccessful={requests.areRequiredRequestsSuccessful}
117 retryRequiredRequests={retryRequiredRequests}
118 areRequiredRequestsLoading={requests.areRequiredRequestsLoading}
119 >
120 {React.Children.count(children) > 0 ? children : null}
121 </AppLayout>
122 );
123 }
124}
125
126AppLayoutContainer.wrappedComponent.propTypes = {
127 stores: PropTypes.shape({
128 services: PropTypes.instanceOf(ServicesStore).isRequired,
129 recipes: PropTypes.instanceOf(RecipesStore).isRequired,
130 app: PropTypes.instanceOf(AppStore).isRequired,
131 ui: PropTypes.instanceOf(UIStore).isRequired,
132 news: PropTypes.instanceOf(NewsStore).isRequired,
133 user: PropTypes.instanceOf(UserStore).isRequired,
134 requests: PropTypes.instanceOf(RequestStore).isRequired,
135 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired,
136 }).isRequired,
137 actions: PropTypes.shape({
138 service: PropTypes.shape({
139 setActive: PropTypes.func.isRequired,
140 reload: PropTypes.func.isRequired,
141 toggleNotifications: PropTypes.func.isRequired,
142 handleIPCMessage: PropTypes.func.isRequired,
143 setWebviewReference: PropTypes.func.isRequired,
144 openWindow: PropTypes.func.isRequired,
145 reloadUpdatedServices: PropTypes.func.isRequired,
146 updateService: PropTypes.func.isRequired,
147 deleteService: PropTypes.func.isRequired,
148 reorder: PropTypes.func.isRequired,
149 }).isRequired,
150 news: PropTypes.shape({
151 hide: PropTypes.func.isRequired,
152 }).isRequired,
153 ui: PropTypes.shape({
154 openSettings: PropTypes.func.isRequired,
155 closeSettings: PropTypes.func.isRequired,
156 }).isRequired,
157 app: PropTypes.shape({
158 installUpdate: PropTypes.func.isRequired,
159 healthCheck: PropTypes.func.isRequired,
160 }).isRequired,
161 requests: PropTypes.shape({
162 retryRequiredRequests: PropTypes.func.isRequired,
163 }).isRequired,
164 }).isRequired,
165 children: oneOrManyChildElements,
166};
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js
new file mode 100644
index 000000000..a1ac8bda3
--- /dev/null
+++ b/src/containers/settings/AccountScreen.js
@@ -0,0 +1,114 @@
1import { remote } from 'electron';
2import React, { Component } from 'react';
3import PropTypes from 'prop-types';
4import { inject, observer } from 'mobx-react';
5
6import PaymentStore from '../../stores/PaymentStore';
7import UserStore from '../../stores/UserStore';
8import AppStore from '../../stores/AppStore';
9import { gaPage } from '../../lib/analytics';
10
11import AccountDashboard from '../../components/settings/account/AccountDashboard';
12
13const { BrowserWindow } = remote;
14
15@inject('stores', 'actions') @observer
16export default class AccountScreen extends Component {
17 componentDidMount() {
18 gaPage('Settings/Account Dashboard');
19 }
20
21 onCloseWindow() {
22 const { user, payment } = this.props.stores;
23 user.getUserInfoRequest.invalidate({ immediately: true });
24 payment.ordersDataRequest.invalidate({ immediately: true });
25 }
26
27 reloadData() {
28 const { user, payment } = this.props.stores;
29
30 user.getUserInfoRequest.reload();
31 payment.ordersDataRequest.reload();
32 payment.plansRequest.reload();
33 }
34
35 stopMiner() {
36 const { update } = this.props.actions.user;
37
38 update({ userData: {
39 isMiner: false,
40 } });
41 }
42
43 async handlePaymentDashboard() {
44 const { actions, stores } = this.props;
45
46 actions.payment.createDashboardUrl();
47
48 const dashboard = await stores.payment.createDashboardUrlRequest;
49
50 if (dashboard.url) {
51 const paymentWindow = new BrowserWindow({
52 title: '🔒 Franz Subscription Dashboard',
53 parent: remote.getCurrentWindow(),
54 modal: false,
55 width: 900,
56 minWidth: 600,
57 webPreferences: {
58 nodeIntegration: false,
59 },
60 });
61 paymentWindow.loadURL(dashboard.url);
62
63 paymentWindow.on('closed', () => {
64 this.onCloseWindow();
65 });
66 }
67 }
68
69 render() {
70 const { user, payment, app } = this.props.stores;
71 const { openExternalUrl } = this.props.actions.app;
72
73 const isLoadingUserInfo = user.getUserInfoRequest.isExecuting;
74 const isLoadingOrdersInfo = payment.ordersDataRequest.isExecuting;
75 const isLoadingPlans = payment.plansRequest.isExecuting;
76
77 return (
78 <AccountDashboard
79 user={user.data}
80 orders={payment.orders}
81 hashrate={app.minerHashrate}
82 isLoading={isLoadingUserInfo}
83 isLoadingOrdersInfo={isLoadingOrdersInfo}
84 isLoadingPlans={isLoadingPlans}
85 userInfoRequestFailed={user.getUserInfoRequest.wasExecuted && user.getUserInfoRequest.isError}
86 retryUserInfoRequest={() => this.reloadData()}
87 isCreatingPaymentDashboardUrl={payment.createDashboardUrlRequest.isExecuting}
88 openDashboard={price => this.handlePaymentDashboard(price)}
89 openExternalUrl={url => openExternalUrl({ url })}
90 onCloseSubscriptionWindow={() => this.onCloseWindow()}
91 stopMiner={() => this.stopMiner()}
92 />
93 );
94 }
95}
96
97AccountScreen.wrappedComponent.propTypes = {
98 stores: PropTypes.shape({
99 user: PropTypes.instanceOf(UserStore).isRequired,
100 payment: PropTypes.instanceOf(PaymentStore).isRequired,
101 app: PropTypes.instanceOf(AppStore).isRequired,
102 }).isRequired,
103 actions: PropTypes.shape({
104 payment: PropTypes.shape({
105 createDashboardUrl: PropTypes.func.isRequired,
106 }).isRequired,
107 app: PropTypes.shape({
108 openExternalUrl: PropTypes.func.isRequired,
109 }).isRequired,
110 user: PropTypes.shape({
111 update: PropTypes.func.isRequired,
112 }).isRequired,
113 }).isRequired,
114};
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js
new file mode 100644
index 000000000..6c614b941
--- /dev/null
+++ b/src/containers/settings/EditServiceScreen.js
@@ -0,0 +1,208 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import UserStore from '../../stores/UserStore';
7import RecipesStore from '../../stores/RecipesStore';
8import ServicesStore from '../../stores/ServicesStore';
9import Form from '../../lib/Form';
10import { gaPage } from '../../lib/analytics';
11
12
13import ServiceError from '../../components/settings/services/ServiceError';
14import EditServiceForm from '../../components/settings/services/EditServiceForm';
15import { required, url, oneRequired } from '../../helpers/validation-helpers';
16
17const messages = defineMessages({
18 name: {
19 id: 'settings.service.form.name',
20 defaultMessage: '!!!Name',
21 },
22 enableService: {
23 id: 'settings.service.form.enableService',
24 defaultMessage: '!!!Enable service',
25 },
26 enableNotification: {
27 id: 'settings.service.form.enableNotification',
28 defaultMessage: '!!!Enable Notifications',
29 },
30 team: {
31 id: 'settings.service.form.team',
32 defaultMessage: '!!!Team',
33 },
34 customUrl: {
35 id: 'settings.service.form.customUrl',
36 defaultMessage: '!!!Custom server',
37 },
38 indirectMessages: {
39 id: 'settings.service.form.indirectMessages',
40 defaultMessage: '!!!Show message badge for all new messages',
41 },
42});
43
44@inject('stores', 'actions') @observer
45export default class EditServiceScreen extends Component {
46 static contextTypes = {
47 intl: intlShape,
48 };
49
50 componentDidMount() {
51 gaPage('Settings/Service/Edit');
52 }
53
54 onSubmit(serviceData) {
55 const { action } = this.props.router.params;
56 const { recipes, services } = this.props.stores;
57 const { createService, updateService } = this.props.actions.service;
58
59 if (action === 'edit') {
60 updateService({ serviceId: services.activeSettings.id, serviceData });
61 } else {
62 createService({ recipeId: recipes.active.id, serviceData });
63 }
64 }
65
66 prepareForm(recipe, service) {
67 const { intl } = this.context;
68 const config = {
69 fields: {
70 name: {
71 label: intl.formatMessage(messages.name),
72 placeholder: intl.formatMessage(messages.name),
73 value: service.id ? service.name : recipe.name,
74 },
75 isEnabled: {
76 label: intl.formatMessage(messages.enableService),
77 value: service.isEnabled,
78 default: true,
79 },
80 isNotificationEnabled: {
81 label: intl.formatMessage(messages.enableNotification),
82 value: service.isNotificationEnabled,
83 default: true,
84 },
85 },
86 };
87
88 if (recipe.hasTeamId) {
89 Object.assign(config.fields, {
90 team: {
91 label: intl.formatMessage(messages.team),
92 placeholder: intl.formatMessage(messages.team),
93 value: service.team,
94 validate: [required],
95 },
96 });
97 }
98
99 if (recipe.hasCustomUrl) {
100 Object.assign(config.fields, {
101 customUrl: {
102 label: intl.formatMessage(messages.customUrl),
103 placeholder: 'https://',
104 value: service.customUrl,
105 validate: [required, url],
106 },
107 });
108 }
109
110 if (recipe.hasTeamId && recipe.hasCustomUrl) {
111 config.fields.team.validate = [oneRequired(['team', 'customUrl'])];
112 config.fields.customUrl.validate = [url, oneRequired(['team', 'customUrl'])];
113 }
114
115 if (recipe.hasIndirectMessages) {
116 Object.assign(config.fields, {
117 isIndirectMessageBadgeEnabled: {
118 label: intl.formatMessage(messages.indirectMessages),
119 value: service.isIndirectMessageBadgeEnabled,
120 default: true,
121 },
122 });
123 }
124
125 return new Form(config);
126 }
127
128 deleteService() {
129 const { deleteService } = this.props.actions.service;
130 const { action } = this.props.router.params;
131
132 if (action === 'edit') {
133 const { activeSettings: service } = this.props.stores.services;
134 deleteService({
135 serviceId: service.id,
136 redirect: '/settings/services',
137 });
138 }
139 }
140
141 render() {
142 const { recipes, services, user } = this.props.stores;
143 const { action } = this.props.router.params;
144
145 let recipe;
146 let service = {};
147 let isLoading = false;
148
149 if (action === 'add') {
150 recipe = recipes.active;
151
152 // TODO: render error message when recipe is `null`
153 if (!recipe) {
154 return (
155 <ServiceError />
156 );
157 }
158 } else {
159 service = services.activeSettings;
160 isLoading = services.allServicesRequest.isExecuting;
161
162 if (!isLoading && service) {
163 recipe = service.recipe;
164 }
165 }
166
167 if (isLoading) {
168 return (<div>Loading...</div>);
169 }
170
171 const form = this.prepareForm(recipe, service);
172
173 return (
174 <EditServiceForm
175 action={action}
176 recipe={recipe}
177 service={service}
178 user={user.data}
179 form={form}
180 status={services.actionStatus}
181 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting}
182 isDeleting={services.deleteServiceRequest.isExecuting}
183 onSubmit={d => this.onSubmit(d)}
184 onDelete={() => this.deleteService()}
185 />
186 );
187 }
188}
189
190EditServiceScreen.wrappedComponent.propTypes = {
191 stores: PropTypes.shape({
192 user: PropTypes.instanceOf(UserStore).isRequired,
193 recipes: PropTypes.instanceOf(RecipesStore).isRequired,
194 services: PropTypes.instanceOf(ServicesStore).isRequired,
195 }).isRequired,
196 router: PropTypes.shape({
197 params: PropTypes.shape({
198 action: PropTypes.string.isRequired,
199 }).isRequired,
200 }).isRequired,
201 actions: PropTypes.shape({
202 service: PropTypes.shape({
203 createService: PropTypes.func.isRequired,
204 updateService: PropTypes.func.isRequired,
205 deleteService: PropTypes.func.isRequired,
206 }).isRequired,
207 }).isRequired,
208};
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
new file mode 100644
index 000000000..0e17cafce
--- /dev/null
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -0,0 +1,167 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import AppStore from '../../stores/AppStore';
7import SettingsStore from '../../stores/SettingsStore';
8import UserStore from '../../stores/UserStore';
9import Form from '../../lib/Form';
10import languages from '../../i18n/languages';
11import { gaPage } from '../../lib/analytics';
12
13
14import EditSettingsForm from '../../components/settings/settings/EditSettingsForm';
15
16const messages = defineMessages({
17 autoLaunchOnStart: {
18 id: 'settings.app.form.autoLaunchOnStart',
19 defaultMessage: '!!!Launch Franz on start',
20 },
21 autoLaunchInBackground: {
22 id: 'settings.app.form.autoLaunchInBackground',
23 defaultMessage: '!!!Open in background',
24 },
25 runInBackground: {
26 id: 'settings.app.form.runInBackground',
27 defaultMessage: '!!!Keep Franz in background when closing the window',
28 },
29 minimizeToSystemTray: {
30 id: 'settings.app.form.minimizeToSystemTray',
31 defaultMessage: '!!!Minimize Franz to system tray',
32 },
33 language: {
34 id: 'settings.app.form.language',
35 defaultMessage: '!!!Language',
36 },
37 beta: {
38 id: 'settings.app.form.beta',
39 defaultMessage: '!!!Include beta versions',
40 },
41});
42
43@inject('stores', 'actions') @observer
44export default class EditSettingsScreen extends Component {
45 static contextTypes = {
46 intl: intlShape,
47 };
48
49 componentDidMount() {
50 gaPage('Settings/App');
51 }
52
53 onSubmit(settingsData) {
54 const { app, settings, user } = this.props.actions;
55
56 app.launchOnStartup({
57 enable: settingsData.autoLaunchOnStart,
58 openInBackground: settingsData.autoLaunchInBackground,
59 });
60
61 settings.update({
62 settings: {
63 runInBackground: settingsData.runInBackground,
64 minimizeToSystemTray: settingsData.minimizeToSystemTray,
65 locale: settingsData.locale,
66 beta: settingsData.beta,
67 },
68 });
69
70 user.update({
71 userData: {
72 beta: settingsData.beta,
73 },
74 });
75 }
76
77 prepareForm() {
78 const { app, settings, user } = this.props.stores;
79 const { intl } = this.context;
80
81 const options = [];
82 Object.keys(languages).forEach((key) => {
83 options.push({
84 value: key,
85 label: languages[key],
86 });
87 });
88
89 const config = {
90 fields: {
91 autoLaunchOnStart: {
92 label: intl.formatMessage(messages.autoLaunchOnStart),
93 value: app.autoLaunchOnStart,
94 default: true,
95 },
96 autoLaunchInBackground: {
97 label: intl.formatMessage(messages.autoLaunchInBackground),
98 value: app.launchInBackground,
99 default: false,
100 },
101 runInBackground: {
102 label: intl.formatMessage(messages.runInBackground),
103 value: settings.all.runInBackground,
104 default: true,
105 },
106 minimizeToSystemTray: {
107 label: intl.formatMessage(messages.minimizeToSystemTray),
108 value: settings.all.minimizeToSystemTray,
109 default: false,
110 },
111 locale: {
112 label: intl.formatMessage(messages.language),
113 value: app.locale,
114 options,
115 default: 'en-US',
116 },
117 beta: {
118 label: intl.formatMessage(messages.beta),
119 value: user.data.beta,
120 default: false,
121 },
122 },
123 };
124
125 return new Form(config);
126 }
127
128 render() {
129 const { updateStatus, updateStatusTypes } = this.props.stores.app;
130 const { checkForUpdates, installUpdate } = this.props.actions.app;
131 const form = this.prepareForm();
132
133 return (
134 <EditSettingsForm
135 form={form}
136 checkForUpdates={checkForUpdates}
137 installUpdate={installUpdate}
138 isCheckingForUpdates={updateStatus === updateStatusTypes.CHECKING}
139 isUpdateAvailable={updateStatus === updateStatusTypes.AVAILABLE}
140 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE}
141 updateIsReadyToInstall={updateStatus === updateStatusTypes.DOWNLOADED}
142 onSubmit={d => this.onSubmit(d)}
143 />
144 );
145 }
146}
147
148EditSettingsScreen.wrappedComponent.propTypes = {
149 stores: PropTypes.shape({
150 app: PropTypes.instanceOf(AppStore).isRequired,
151 user: PropTypes.instanceOf(UserStore).isRequired,
152 settings: PropTypes.instanceOf(SettingsStore).isRequired,
153 }).isRequired,
154 actions: PropTypes.shape({
155 app: PropTypes.shape({
156 launchOnStartup: PropTypes.func.isRequired,
157 checkForUpdates: PropTypes.func.isRequired,
158 installUpdate: PropTypes.func.isRequired,
159 }).isRequired,
160 settings: PropTypes.shape({
161 update: PropTypes.func.isRequired,
162 }).isRequired,
163 user: PropTypes.shape({
164 update: PropTypes.func.isRequired,
165 }).isRequired,
166 }).isRequired,
167};
diff --git a/src/containers/settings/EditUserScreen.js b/src/containers/settings/EditUserScreen.js
new file mode 100644
index 000000000..fb5c5db89
--- /dev/null
+++ b/src/containers/settings/EditUserScreen.js
@@ -0,0 +1,165 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import UserStore from '../../stores/UserStore';
7import Form from '../../lib/Form';
8import EditUserForm from '../../components/settings/user/EditUserForm';
9import { required, email, minLength } from '../../helpers/validation-helpers';
10import { gaPage } from '../../lib/analytics';
11
12const messages = defineMessages({
13 firstname: {
14 id: 'settings.user.form.firstname',
15 defaultMessage: '!!!Firstname',
16 },
17 lastname: {
18 id: 'settings.user.form.lastname',
19 defaultMessage: '!!!Lastname',
20 },
21 email: {
22 id: 'settings.user.form.email',
23 defaultMessage: '!!!Email',
24 },
25 accountType: {
26 label: {
27 id: 'settings.user.form.accountType.label',
28 defaultMessage: '!!!Account type',
29 },
30 individual: {
31 id: 'settings.user.form.accountType.individual',
32 defaultMessage: '!!!Individual',
33 },
34 nonProfit: {
35 id: 'settings.user.form.accountType.non-profit',
36 defaultMessage: '!!!Non-Profit',
37 },
38 company: {
39 id: 'settings.user.form.accountType.company',
40 defaultMessage: '!!!Company',
41 },
42 },
43 currentPassword: {
44 id: 'settings.user.form.currentPassword',
45 defaultMessage: '!!!Current password',
46 },
47 newPassword: {
48 id: 'settings.user.form.newPassword',
49 defaultMessage: '!!!New password',
50 },
51});
52
53@inject('stores', 'actions') @observer
54export default class EditUserScreen extends Component {
55 static contextTypes = {
56 intl: intlShape,
57 };
58
59 componentDidMount() {
60 gaPage('Settings/Account/Edit');
61 }
62
63 componentWillUnmount() {
64 this.props.actions.user.resetStatus();
65 }
66
67 onSubmit(userData) {
68 const { update } = this.props.actions.user;
69
70 update({ userData });
71
72 document.querySelector('#form').scrollIntoView({ behavior: 'smooth' });
73 }
74
75 prepareForm(user) {
76 const { intl } = this.context;
77
78 const config = {
79 fields: {
80 firstname: {
81 label: intl.formatMessage(messages.firstname),
82 placeholder: intl.formatMessage(messages.firstname),
83 value: user.firstname,
84 validate: [required],
85 },
86 lastname: {
87 label: intl.formatMessage(messages.lastname),
88 placeholder: intl.formatMessage(messages.lastname),
89 value: user.lastname,
90 validate: [required],
91 },
92 email: {
93 label: intl.formatMessage(messages.email),
94 placeholder: intl.formatMessage(messages.email),
95 value: user.email,
96 validate: [required, email],
97 },
98 accountType: {
99 value: user.accountType,
100 validate: [required],
101 label: intl.formatMessage(messages.accountType.label),
102 options: [{
103 value: 'individual',
104 label: intl.formatMessage(messages.accountType.individual),
105 }, {
106 value: 'non-profit',
107 label: intl.formatMessage(messages.accountType.nonProfit),
108 }, {
109 value: 'company',
110 label: intl.formatMessage(messages.accountType.company),
111 }],
112 },
113 organization: {
114 label: intl.formatMessage(messages.accountType.company),
115 placeholder: intl.formatMessage(messages.accountType.company),
116 value: user.organization,
117 },
118 oldPassword: {
119 label: intl.formatMessage(messages.currentPassword),
120 type: 'password',
121 validate: [minLength(6)],
122 },
123 newPassword: {
124 label: intl.formatMessage(messages.newPassword),
125 type: 'password',
126 validate: [minLength(6)],
127 },
128 },
129 };
130
131 return new Form(config);
132 }
133
134 render() {
135 const { user } = this.props.stores;
136
137 if (user.getUserInfoRequest.isExecuting) {
138 return (<div>Loading...</div>);
139 }
140
141 const form = this.prepareForm(user.data);
142
143 return (
144 <EditUserForm
145 // user={user.data}
146 status={user.actionStatus}
147 form={form}
148 isSaving={user.updateUserInfoRequest.isExecuting}
149 onSubmit={d => this.onSubmit(d)}
150 />
151 );
152 }
153}
154
155EditUserScreen.wrappedComponent.propTypes = {
156 stores: PropTypes.shape({
157 user: PropTypes.instanceOf(UserStore).isRequired,
158 }).isRequired,
159 actions: PropTypes.shape({
160 user: PropTypes.shape({
161 update: PropTypes.func.isRequired,
162 resetStatus: PropTypes.func.isRequired,
163 }).isRequired,
164 }).isRequired,
165};
diff --git a/src/containers/settings/RecipesScreen.js b/src/containers/settings/RecipesScreen.js
new file mode 100644
index 000000000..65341e9e3
--- /dev/null
+++ b/src/containers/settings/RecipesScreen.js
@@ -0,0 +1,126 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { autorun } from 'mobx';
4import { inject, observer } from 'mobx-react';
5
6import RecipePreviewsStore from '../../stores/RecipePreviewsStore';
7import RecipeStore from '../../stores/RecipesStore';
8import ServiceStore from '../../stores/ServicesStore';
9import UserStore from '../../stores/UserStore';
10import { gaPage } from '../../lib/analytics';
11
12import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard';
13
14@inject('stores', 'actions') @observer
15export default class RecipesScreen extends Component {
16 static propTypes = {
17 params: PropTypes.shape({
18 filter: PropTypes.string,
19 }).isRequired,
20 };
21
22 static defaultProps = {
23 params: {
24 filter: null,
25 },
26 };
27
28 state = {
29 needle: null,
30 currentFilter: 'featured',
31 };
32
33 componentDidMount() {
34 gaPage('Settings/Recipe Dashboard/Featured');
35
36 autorun(() => {
37 const { filter } = this.props.params;
38 const { currentFilter } = this.state;
39
40 if (filter === 'all' && currentFilter !== 'all') {
41 gaPage('Settings/Recipe Dashboard/All');
42 this.setState({ currentFilter: 'all' });
43 } else if (filter === 'featured' && currentFilter !== 'featured') {
44 gaPage('Settings/Recipe Dashboard/Featured');
45 this.setState({ currentFilter: 'featured' });
46 } else if (filter === 'dev' && currentFilter !== 'dev') {
47 gaPage('Settings/Recipe Dashboard/Dev');
48 this.setState({ currentFilter: 'dev' });
49 }
50 });
51 }
52
53 componentWillUnmount() {
54 this.props.stores.services.resetStatus();
55 }
56
57 searchRecipes(needle) {
58 if (needle === '') {
59 this.resetSearch();
60 } else {
61 const { search } = this.props.actions.recipePreview;
62 this.setState({ needle });
63 search({ needle });
64 }
65 }
66
67 resetSearch() {
68 this.setState({ needle: null });
69 }
70
71 render() {
72 const { recipePreviews, recipes, services, user } = this.props.stores;
73 const { showAddServiceInterface } = this.props.actions.service;
74
75 const { filter } = this.props.params;
76 let recipeFilter;
77
78 if (filter === 'all') {
79 recipeFilter = recipePreviews.all;
80 } else if (filter === 'dev') {
81 recipeFilter = recipePreviews.dev;
82 } else {
83 recipeFilter = recipePreviews.featured;
84 }
85
86 const allRecipes = this.state.needle ? recipePreviews.searchResults : recipeFilter;
87
88 const isLoading = recipePreviews.featuredRecipePreviewsRequest.isExecuting
89 || recipePreviews.allRecipePreviewsRequest.isExecuting
90 || recipes.installRecipeRequest.isExecuting
91 || recipePreviews.searchRecipePreviewsRequest.isExecuting;
92
93 return (
94 <RecipesDashboard
95 recipes={allRecipes}
96 isLoading={isLoading}
97 addedServiceCount={services.all.length}
98 isPremium={user.data.isPremium}
99 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted}
100 showAddServiceInterface={showAddServiceInterface}
101 searchRecipes={e => this.searchRecipes(e)}
102 resetSearch={() => this.resetSearch()}
103 searchNeedle={this.state.needle}
104 serviceStatus={services.actionStatus}
105 devRecipesCount={recipePreviews.dev.length}
106 />
107 );
108 }
109}
110
111RecipesScreen.wrappedComponent.propTypes = {
112 stores: PropTypes.shape({
113 recipePreviews: PropTypes.instanceOf(RecipePreviewsStore).isRequired,
114 recipes: PropTypes.instanceOf(RecipeStore).isRequired,
115 services: PropTypes.instanceOf(ServiceStore).isRequired,
116 user: PropTypes.instanceOf(UserStore).isRequired,
117 }).isRequired,
118 actions: PropTypes.shape({
119 service: PropTypes.shape({
120 showAddServiceInterface: PropTypes.func.isRequired,
121 }).isRequired,
122 recipePreview: PropTypes.shape({
123 search: PropTypes.func.isRequired,
124 }).isRequired,
125 }).isRequired,
126};
diff --git a/src/containers/settings/ServicesScreen.js b/src/containers/settings/ServicesScreen.js
new file mode 100644
index 000000000..d0580041f
--- /dev/null
+++ b/src/containers/settings/ServicesScreen.js
@@ -0,0 +1,75 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { RouterStore } from 'mobx-react-router';
5
6// import RecipePreviewsStore from '../../stores/RecipePreviewsStore';
7import UserStore from '../../stores/UserStore';
8import ServiceStore from '../../stores/ServicesStore';
9import { gaPage } from '../../lib/analytics';
10
11import ServicesDashboard from '../../components/settings/services/ServicesDashboard';
12
13@inject('stores', 'actions') @observer
14export default class ServicesScreen extends Component {
15 componentDidMount() {
16 gaPage('Settings/Service Dashboard');
17 }
18
19 componentWillUnmount() {
20 this.props.actions.service.resetFilter();
21 }
22
23 deleteService() {
24 this.props.actions.service.deleteService();
25 this.props.stores.services.resetFilter();
26 }
27
28 render() {
29 const { user, services, router } = this.props.stores;
30 const {
31 toggleService,
32 filter,
33 resetFilter,
34 } = this.props.actions.service;
35 const isLoading = services.allServicesRequest.isExecuting;
36
37 let allServices = services.all;
38 if (services.filterNeedle !== null) {
39 allServices = services.filtered;
40 }
41
42 return (
43 <ServicesDashboard
44 user={user.data}
45 services={allServices}
46 status={services.actionStatus}
47 deleteService={() => this.deleteService()}
48 toggleService={toggleService}
49 isLoading={isLoading}
50 filterServices={filter}
51 resetFilter={resetFilter}
52 goTo={router.push}
53 servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError}
54 retryServicesRequest={() => services.allServicesRequest.reload()}
55 />
56 );
57 }
58}
59
60ServicesScreen.wrappedComponent.propTypes = {
61 stores: PropTypes.shape({
62 user: PropTypes.instanceOf(UserStore).isRequired,
63 services: PropTypes.instanceOf(ServiceStore).isRequired,
64 router: PropTypes.instanceOf(RouterStore).isRequired,
65 }).isRequired,
66 actions: PropTypes.shape({
67 service: PropTypes.shape({
68 showAddServiceInterface: PropTypes.func.isRequired,
69 deleteService: PropTypes.func.isRequired,
70 toggleService: PropTypes.func.isRequired,
71 filter: PropTypes.func.isRequired,
72 resetFilter: PropTypes.func.isRequired,
73 }).isRequired,
74 }).isRequired,
75};
diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js
new file mode 100644
index 000000000..13ca96f72
--- /dev/null
+++ b/src/containers/settings/SettingsWindow.js
@@ -0,0 +1,43 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, inject } from 'mobx-react';
4
5import ServicesStore from '../../stores/ServicesStore';
6
7import Layout from '../../components/settings/SettingsLayout';
8import Navigation from '../../components/settings/navigation/SettingsNavigation';
9
10@inject('stores', 'actions') @observer
11export default class SettingsContainer extends Component {
12 render() {
13 const { children, stores } = this.props;
14 const { closeSettings } = this.props.actions.ui;
15
16 const navigation = (
17 <Navigation
18 serviceCount={stores.services.all.length}
19 />
20 );
21
22 return (
23 <Layout
24 navigation={navigation}
25 closeSettings={closeSettings}
26 >
27 {children}
28 </Layout>
29 );
30 }
31}
32
33SettingsContainer.wrappedComponent.propTypes = {
34 children: PropTypes.element.isRequired,
35 stores: PropTypes.shape({
36 services: PropTypes.instanceOf(ServicesStore).isRequired,
37 }).isRequired,
38 actions: PropTypes.shape({
39 ui: PropTypes.shape({
40 closeSettings: PropTypes.func.isRequired,
41 }),
42 }).isRequired,
43};
diff --git a/src/containers/ui/SubscriptionFormScreen.js b/src/containers/ui/SubscriptionFormScreen.js
new file mode 100644
index 000000000..d08507809
--- /dev/null
+++ b/src/containers/ui/SubscriptionFormScreen.js
@@ -0,0 +1,126 @@
1import { remote } from 'electron';
2import React, { Component } from 'react';
3import PropTypes from 'prop-types';
4import { inject, observer } from 'mobx-react';
5
6import PaymentStore from '../../stores/PaymentStore';
7
8import SubscriptionForm from '../../components/ui/Subscription';
9
10const { BrowserWindow } = remote;
11
12@inject('stores', 'actions') @observer
13export default class SubscriptionFormScreen extends Component {
14 static propTypes = {
15 onCloseWindow: PropTypes.func,
16 content: PropTypes.oneOrManyChildElements,
17 showSkipOption: PropTypes.bool,
18 skipAction: PropTypes.func,
19 skipButtonLabel: PropTypes.string,
20 hideInfo: PropTypes.bool,
21 }
22
23 static defaultProps = {
24 onCloseWindow: () => null,
25 content: '',
26 showSkipOption: false,
27 skipAction: () => null,
28 skipButtonLabel: '',
29 hideInfo: false,
30 }
31
32 async handlePayment(plan) {
33 const {
34 actions,
35 stores,
36 onCloseWindow,
37 skipAction,
38 } = this.props;
39
40 if (plan !== 'mining') {
41 const interval = plan;
42
43 const { id } = stores.payment.plan[interval];
44 actions.payment.createHostedPage({
45 planId: id,
46 });
47
48 const hostedPage = await stores.payment.createHostedPageRequest;
49 const url = `file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPage.url)}`;
50
51 if (hostedPage.url) {
52 const paymentWindow = new BrowserWindow({
53 parent: remote.getCurrentWindow(),
54 modal: true,
55 title: '🔒 Franz Supporter License',
56 width: 600,
57 height: window.innerHeight - 100,
58 maxWidth: 600,
59 minWidth: 600,
60 webPreferences: {
61 nodeIntegration: true,
62 },
63 });
64 paymentWindow.loadURL(url);
65
66 paymentWindow.on('closed', () => {
67 onCloseWindow();
68 });
69 }
70 } else {
71 actions.user.update({
72 userData: {
73 isMiner: true,
74 },
75 });
76
77 skipAction();
78 }
79 }
80
81 render() {
82 const {
83 content,
84 actions,
85 stores,
86 showSkipOption,
87 skipAction,
88 skipButtonLabel,
89 hideInfo,
90 } = this.props;
91 return (
92 <SubscriptionForm
93 plan={stores.payment.plan}
94 // form={this.prepareForm(stores.payment.plan)}
95 isLoading={stores.payment.plansRequest.isExecuting}
96 retryPlanRequest={() => stores.payment.plansRequest.reload()}
97 isCreatingHostedPage={stores.payment.createHostedPageRequest.isExecuting}
98 handlePayment={price => this.handlePayment(price)}
99 content={content}
100 error={stores.payment.plansRequest.isError}
101 showSkipOption={showSkipOption}
102 skipAction={skipAction}
103 skipButtonLabel={skipButtonLabel}
104 hideInfo={hideInfo}
105 openExternalUrl={actions.app.openExternalUrl}
106 />
107 );
108 }
109}
110
111SubscriptionFormScreen.wrappedComponent.propTypes = {
112 actions: PropTypes.shape({
113 app: PropTypes.shape({
114 openExternalUrl: PropTypes.func.isRequired,
115 }).isRequired,
116 payment: PropTypes.shape({
117 createHostedPage: PropTypes.func.isRequired,
118 }).isRequired,
119 user: PropTypes.shape({
120 update: PropTypes.func.isRequired,
121 }).isRequired,
122 }).isRequired,
123 stores: PropTypes.shape({
124 payment: PropTypes.instanceOf(PaymentStore).isRequired,
125 }).isRequired,
126};
diff --git a/src/containers/ui/SubscriptionPopupScreen.js b/src/containers/ui/SubscriptionPopupScreen.js
new file mode 100644
index 000000000..d17477b1d
--- /dev/null
+++ b/src/containers/ui/SubscriptionPopupScreen.js
@@ -0,0 +1,43 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4
5import SubscriptionPopup from '../../components/ui/SubscriptionPopup';
6
7
8@inject('stores', 'actions') @observer
9export default class SubscriptionPopupScreen extends Component {
10 state = {
11 complete: false,
12 };
13
14 completeCheck(event) {
15 const { url } = event;
16
17 if (url.includes('recurly') && url.includes('confirmation')) {
18 this.setState({
19 complete: true,
20 });
21 }
22 }
23
24 render() {
25 return (
26 <SubscriptionPopup
27 url={decodeURIComponent(this.props.router.params.url)}
28 closeWindow={() => window.close()}
29 completeCheck={e => this.completeCheck(e)}
30 isCompleted={this.state.complete}
31 />
32 );
33 }
34}
35
36
37SubscriptionPopupScreen.wrappedComponent.propTypes = {
38 router: PropTypes.shape({
39 params: PropTypes.shape({
40 url: PropTypes.string.isRequired,
41 }).isRequired,
42 }).isRequired,
43};
diff --git a/src/electron/Settings.js b/src/electron/Settings.js
new file mode 100644
index 000000000..049a08296
--- /dev/null
+++ b/src/electron/Settings.js
@@ -0,0 +1,15 @@
1export default class Settings {
2 store = {};
3
4 set(settings) {
5 this.store = Object.assign(this.store, settings);
6 }
7
8 all() {
9 return this.store;
10 }
11
12 get(key) {
13 return this.store[key];
14 }
15}
diff --git a/src/electron/exception.js b/src/electron/exception.js
new file mode 100644
index 000000000..0065e2604
--- /dev/null
+++ b/src/electron/exception.js
@@ -0,0 +1,4 @@
1process.on('uncaughtException', (err) => {
2 // handle the error safely
3 console.error(err);
4});
diff --git a/src/electron/ipc-api/appIndicator.js b/src/electron/ipc-api/appIndicator.js
new file mode 100644
index 000000000..576234d25
--- /dev/null
+++ b/src/electron/ipc-api/appIndicator.js
@@ -0,0 +1,80 @@
1import { app, ipcMain, Tray, Menu } from 'electron';
2import path from 'path';
3
4const INDICATOR_TRAY_PLAIN = 'tray';
5const INDICATOR_TRAY_UNREAD = 'tray-unread';
6const INDICATOR_TASKBAR = 'taskbar';
7
8const FILE_EXTENSION = process.platform === 'win32' ? 'ico' : 'png';
9let trayIcon;
10
11function getAsset(type, asset) {
12 return path.join(
13 __dirname, '..', '..', 'assets', 'images', type, process.platform, `${asset}.${FILE_EXTENSION}`,
14 );
15}
16
17export default (params) => {
18 trayIcon = new Tray(getAsset('tray', INDICATOR_TRAY_PLAIN));
19 const trayMenuTemplate = [
20 {
21 label: 'Show Franz',
22 click() {
23 params.mainWindow.show();
24 },
25 }, {
26 label: 'Quit Franz',
27 click() {
28 app.quit();
29 },
30 },
31 ];
32
33 const trayMenu = Menu.buildFromTemplate(trayMenuTemplate);
34 trayIcon.setContextMenu(trayMenu);
35
36 trayIcon.on('click', () => {
37 params.mainWindow.show();
38 });
39
40 ipcMain.on('updateAppIndicator', (event, args) => {
41 // Update badge
42 if (process.platform === 'darwin'
43 && typeof (args.indicator) === 'string') {
44 app.dock.setBadge(args.indicator);
45 }
46
47 if ((process.platform === 'darwin'
48 || process.platform === 'linux')
49 && typeof (args.indicator) === 'number'
50 ) {
51 app.setBadgeCount(args.indicator);
52 }
53
54 if (process.platform === 'win32') {
55 if (typeof args.indicator === 'number'
56 && args.indicator !== 0) {
57 params.mainWindow.setOverlayIcon(
58 getAsset('taskbar', `${INDICATOR_TASKBAR}-${(args.indicator >= 10 ? 10 : args.indicator)}`),
59 '',
60 );
61 } else if (typeof args.indicator === 'string') {
62 params.mainWindow.setOverlayIcon(
63 getAsset('taskbar', `${INDICATOR_TASKBAR}-alert`),
64 '',
65 );
66 } else {
67 params.mainWindow.setOverlayIcon(null, '');
68 }
69 }
70
71 // Update system tray
72 trayIcon.setImage(getAsset('tray', args.indicator !== 0 ? INDICATOR_TRAY_UNREAD : INDICATOR_TRAY_PLAIN));
73
74 if (process.platform === 'darwin') {
75 trayIcon.setPressedImage(
76 getAsset('tray', `${args.indicator !== 0 ? INDICATOR_TRAY_UNREAD : INDICATOR_TRAY_PLAIN}-active`),
77 );
78 }
79 });
80};
diff --git a/src/electron/ipc-api/autoUpdate.js b/src/electron/ipc-api/autoUpdate.js
new file mode 100644
index 000000000..7bc193e2d
--- /dev/null
+++ b/src/electron/ipc-api/autoUpdate.js
@@ -0,0 +1,54 @@
1import { app, ipcMain } from 'electron';
2import { autoUpdater } from 'electron-updater';
3
4export default (params) => {
5 if (process.platform === 'darwin' || process.platform === 'win32') {
6 // autoUpdater.setFeedURL(updateUrl);
7 ipcMain.on('autoUpdate', (event, args) => {
8 try {
9 autoUpdater.allowPrerelease = Boolean(params.settings.get('beta'));
10 if (args.action === 'check') {
11 autoUpdater.checkForUpdates();
12 } else if (args.action === 'install') {
13 console.log('install update');
14 autoUpdater.quitAndInstall();
15 // we need to send a quit event
16 setTimeout(() => {
17 app.quit();
18 }, 20);
19 }
20 } catch (e) {
21 console.error(e);
22 event.sender.send('autoUpdate', { error: true });
23 }
24 });
25
26 autoUpdater.on('update-not-available', () => {
27 console.log('update-not-available');
28 params.mainWindow.webContents.send('autoUpdate', { available: false });
29 });
30
31 autoUpdater.on('update-available', () => {
32 console.log('update-available');
33 params.mainWindow.webContents.send('autoUpdate', { available: true });
34 });
35
36 autoUpdater.on('download-progress', (progressObj) => {
37 let logMessage = `Download speed: ${progressObj.bytesPerSecond}`;
38 logMessage = `${logMessage} - Downloaded ${progressObj.percent}%`;
39 logMessage = `${logMessage} (${progressObj.transferred}/${progressObj.total})`;
40
41 console.log(logMessage);
42 });
43
44 autoUpdater.on('update-downloaded', () => {
45 console.log('update-downloaded');
46 params.mainWindow.webContents.send('autoUpdate', { downloaded: true });
47 });
48
49 autoUpdater.on('error', () => {
50 console.log('update-error');
51 params.mainWindow.webContents.send('autoUpdate', { error: true });
52 });
53 }
54};
diff --git a/src/electron/ipc-api/index.js b/src/electron/ipc-api/index.js
new file mode 100644
index 000000000..4ea6d1475
--- /dev/null
+++ b/src/electron/ipc-api/index.js
@@ -0,0 +1,9 @@
1import autoUpdate from './autoUpdate';
2import settings from './settings';
3import appIndicator from './appIndicator';
4
5export default (params) => {
6 settings(params);
7 autoUpdate(params);
8 appIndicator(params);
9};
diff --git a/src/electron/ipc-api/settings.js b/src/electron/ipc-api/settings.js
new file mode 100644
index 000000000..1d7eafa6c
--- /dev/null
+++ b/src/electron/ipc-api/settings.js
@@ -0,0 +1,10 @@
1import { ipcMain } from 'electron';
2
3export default (params) => {
4 if (process.platform === 'darwin' || process.platform === 'win32') {
5 // eslint-disable-next-line
6 ipcMain.on('settings', (event, args) => {
7 params.settings.set(args);
8 });
9 }
10};
diff --git a/src/electron/ipc-api/tray.js b/src/electron/ipc-api/tray.js
new file mode 100644
index 000000000..43364c0ed
--- /dev/null
+++ b/src/electron/ipc-api/tray.js
@@ -0,0 +1,48 @@
1import { Tray, Menu, ipcMain } from 'electron';
2import path from 'path';
3
4const INDICATOR_PLAIN = 'franz-taskbar';
5const INDICATOR_UNREAD = 'franz-taskbar-unread';
6
7const FILE_EXTENSION = process.platform === 'win32' ? 'ico' : 'png';
8
9let trayIcon;
10
11function getAsset(asset) {
12 return path.join(
13 __dirname, '..', '..', 'assets', 'images', 'tray', process.platform, `${asset}.${FILE_EXTENSION}`,
14 );
15}
16
17export default (params) => {
18 // if (process.platform === 'win32' || process.platform === 'linux') {
19 trayIcon = new Tray(getAsset(INDICATOR_PLAIN));
20 const trayMenuTemplate = [
21 {
22 label: 'Show Franz',
23 click() {
24 params.mainWindow.show();
25 },
26 }, {
27 label: 'Quit Franz',
28 click() {
29 params.app.quit();
30 },
31 },
32 ];
33
34 const trayMenu = Menu.buildFromTemplate(trayMenuTemplate);
35 trayIcon.setContextMenu(trayMenu);
36
37 trayIcon.on('click', () => {
38 params.mainWindow.show();
39 });
40
41 ipcMain.on('updateTrayIconIndicator', (event, args) => {
42 trayIcon.setImage(getAsset(args.count !== 0 ? INDICATOR_UNREAD : INDICATOR_PLAIN));
43
44 if (process.platform === 'darwin') {
45 trayIcon.setPressedImage(getAsset(`${args.count !== 0 ? INDICATOR_UNREAD : INDICATOR_PLAIN}-active`));
46 }
47 });
48};
diff --git a/src/electron/webview-ime-focus.js b/src/electron/webview-ime-focus.js
new file mode 100644
index 000000000..1213b518e
--- /dev/null
+++ b/src/electron/webview-ime-focus.js
@@ -0,0 +1,40 @@
1const { releaseDocumentFocus } = require('./webview-ime-focus-helpers');
2
3function giveWebviewDocumentFocus(element) {
4 releaseDocumentFocus();
5
6 window.requestAnimationFrame(() => {
7 element.send('claim-document-focus');
8 });
9}
10
11function elementIsUnfocusedWebview(element) {
12 return element.tagName === 'WEBVIEW' && !element.getWebContents().isFocused();
13}
14
15function webviewDidAutofocus(element) {
16 function didKeyDown() {
17 element.removeEventListener('keydown', didKeyDown, true);
18 giveWebviewDocumentFocus(element);
19 }
20
21 element.addEventListener('keydown', didKeyDown, true);
22}
23
24function handleAutofocus(element) {
25 element.addEventListener('ipc-message', (event) => {
26 if (event.channel === 'autofocus') {
27 element.focus();
28 webviewDidAutofocus(element);
29 }
30 });
31}
32
33function didMouseDown(event) {
34 if (elementIsUnfocusedWebview(event.target)) {
35 giveWebviewDocumentFocus(event.target);
36 }
37}
38
39document.addEventListener('mousedown', didMouseDown, true);
40document.querySelectorAll('webview').forEach(handleAutofocus);
diff --git a/src/environment.js b/src/environment.js
new file mode 100644
index 000000000..e185120c0
--- /dev/null
+++ b/src/environment.js
@@ -0,0 +1,22 @@
1import { LIVE_API, DEV_API, LOCAL_API } from './config';
2
3export const isDevMode = Boolean(process.execPath.match(/[\\/]electron/));
4export const useLiveAPI = process.env.LIVE_API;
5export const useLocalAPI = process.env.LOCAL_API;
6
7export const isMac = process.platform === 'darwin';
8export const isWindows = process.platform === 'win32';
9export const isLinux = process.platform === 'linux';
10
11export const ctrlKey = isMac ? '⌘' : 'Ctrl';
12
13let api;
14if (!isDevMode || (isDevMode && useLiveAPI)) {
15 api = LIVE_API;
16} else if (isDevMode && useLocalAPI) {
17 api = LOCAL_API;
18} else {
19 api = DEV_API;
20}
21
22export const API = api;
diff --git a/src/helpers/password-helpers.js b/src/helpers/password-helpers.js
new file mode 100644
index 000000000..7aacaa4d0
--- /dev/null
+++ b/src/helpers/password-helpers.js
@@ -0,0 +1,36 @@
1import { SHA256 } from 'jshashes';
2
3export function hash(password) {
4 return new SHA256().b64(password);
5}
6
7export function scorePassword(password) {
8 let score = 0;
9 if (!password) {
10 return score;
11 }
12
13 // award every unique letter until 5 repetitions
14 const letters = {};
15 for (let i = 0; i < password.length; i += 1) {
16 letters[password[i]] = (letters[password[i]] || 0) + 1;
17 score += 5.0 / letters[password[i]];
18 }
19
20 // bonus points for mixing it up
21 const variations = {
22 digits: /\d/.test(password),
23 lower: /[a-z]/.test(password),
24 upper: /[A-Z]/.test(password),
25 nonWords: /\W/.test(password),
26 };
27
28 let variationCount = 0;
29 Object.keys(variations).forEach((key) => {
30 variationCount += (variations[key] === true) ? 1 : 0;
31 });
32
33 score += (variationCount - 1) * 10;
34
35 return parseInt(score, 10);
36}
diff --git a/src/helpers/recipe-helpers.js b/src/helpers/recipe-helpers.js
new file mode 100644
index 000000000..257e322fb
--- /dev/null
+++ b/src/helpers/recipe-helpers.js
@@ -0,0 +1,39 @@
1import path from 'path';
2import { remote } from 'electron';
3
4// import ServiceModel from '../models/Service';
5
6const app = remote.app;
7
8export function getRecipeDirectory(id = '') {
9 return path.join(app.getPath('userData'), 'recipes', id);
10}
11
12export function getDevRecipeDirectory(id = '') {
13 return path.join(app.getPath('userData'), 'recipes', 'dev', id);
14}
15
16export function loadRecipeConfig(recipeId) {
17 try {
18 const configPath = `${recipeId}/package.json`;
19 // Delete module from cache
20 delete require.cache[require.resolve(configPath)];
21
22 // eslint-disable-next-line
23 let config = require(configPath);
24
25 const moduleConfigPath = require.resolve(configPath);
26 const paths = path.parse(moduleConfigPath);
27 config.path = paths.dir;
28
29 return config;
30 } catch (e) {
31 console.error(e);
32 return null;
33 }
34}
35
36module.paths.unshift(
37 getDevRecipeDirectory(),
38 getRecipeDirectory(),
39);
diff --git a/src/helpers/routing-helpers.js b/src/helpers/routing-helpers.js
new file mode 100644
index 000000000..14922ebf3
--- /dev/null
+++ b/src/helpers/routing-helpers.js
@@ -0,0 +1,4 @@
1import RouteParser from 'route-parser';
2
3// eslint-disable-next-line
4export const matchRoute = (pattern, path) => new RouteParser(pattern).match(path);
diff --git a/src/helpers/validation-helpers.js b/src/helpers/validation-helpers.js
new file mode 100644
index 000000000..eeb12cab7
--- /dev/null
+++ b/src/helpers/validation-helpers.js
@@ -0,0 +1,48 @@
1export function required({ field }) {
2 const isValid = (field.value.trim() !== '');
3 return [isValid, `${field.label} is required`];
4}
5
6export function email({ field }) {
7 const value = field.value.trim();
8 let isValid = false;
9
10 if (value !== '') {
11 isValid = Boolean(value.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}/i));
12 } else {
13 isValid = true;
14 }
15
16 return [isValid, `${field.label} is not a valid email address`];
17}
18
19export function url({ field }) {
20 const value = field.value.trim();
21 let isValid = false;
22
23 if (value !== '') {
24 // eslint-disable-next-line
25 isValid = Boolean(value.match(/(^|[\s.:;?\-\]<(])(https?:\/\/[-\w;/?:@&=+$|_.!~*|'()[\]%#,☺]+[\w/#](\(\))?)(?=$|[\s',|().:;?\-[\]>)])/i));
26 } else {
27 isValid = true;
28 }
29
30 return [isValid, `${field.label} is not a valid url`];
31}
32
33export function minLength(length) {
34 return ({ field }) => {
35 let isValid = true;
36 if (field.touched) {
37 isValid = field.value.length >= length;
38 }
39 return [isValid, `${field.label} should be at least ${length} characters long.`];
40 };
41}
42
43export function oneRequired(targets) {
44 return ({ field, form }) => {
45 const invalidFields = targets.filter(target => form.$(target).value === '');
46 return [targets.length !== invalidFields.length, `${field.label} is required`];
47 };
48}
diff --git a/src/helpers/webview-ime-focus-helpers.js b/src/helpers/webview-ime-focus-helpers.js
new file mode 100644
index 000000000..2593a5f26
--- /dev/null
+++ b/src/helpers/webview-ime-focus-helpers.js
@@ -0,0 +1,38 @@
1module.exports.releaseDocumentFocus = () => {
2 const element = document.createElement('span');
3 document.body.appendChild(element);
4
5 const range = document.createRange();
6 range.setStart(element, 0);
7
8 const selection = window.getSelection();
9 selection.removeAllRanges();
10 selection.addRange(range);
11 selection.removeAllRanges();
12
13 document.body.removeChild(element);
14};
15
16module.exports.claimDocumentFocus = () => {
17 const { activeElement } = document;
18 const selection = window.getSelection();
19
20 let selectionStart;
21 let selectionEnd;
22 let range;
23
24 if (activeElement) ({ selectionStart, selectionEnd } = activeElement);
25 if (selection.rangeCount) range = selection.getRangeAt(0);
26
27 const restoreOriginalSelection = () => {
28 if (selectionStart >= 0 && selectionEnd >= 0) {
29 activeElement.selectionStart = selectionStart;
30 activeElement.selectionEnd = selectionEnd;
31 } else if (range) {
32 selection.addRange(range);
33 }
34 };
35
36 exports.releaseDocumentFocus();
37 window.requestAnimationFrame(restoreOriginalSelection);
38};
diff --git a/src/i18n/globalMessages.js b/src/i18n/globalMessages.js
new file mode 100644
index 000000000..2c724ff6f
--- /dev/null
+++ b/src/i18n/globalMessages.js
@@ -0,0 +1,16 @@
1import { defineMessages } from 'react-intl';
2
3export default defineMessages({
4 upgradeAccount: {
5 id: 'global.premium.upgradeAccount',
6 defaultMessage: '!!!Please upgrade your account to add a new service.',
7 },
8 APIUnhealthy: {
9 id: 'global.api.unhealthy',
10 defaultMessage: '!!!Can\'t connect to Franz Online Services',
11 },
12 notConnectedToTheInternet: {
13 id: 'global.notConnectedToTheInternet',
14 defaultMessage: '!!!You are not connected to the internet.',
15 },
16});
diff --git a/src/i18n/languages.js b/src/i18n/languages.js
new file mode 100644
index 000000000..782853b43
--- /dev/null
+++ b/src/i18n/languages.js
@@ -0,0 +1,4 @@
1module.exports = {
2 'en-US': 'English',
3 // 'de-DE': 'Deutsch',
4};
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
new file mode 100644
index 000000000..194b8047c
--- /dev/null
+++ b/src/i18n/locales/en-US.json
@@ -0,0 +1,167 @@
1{
2 "global.api.unhealthy": "Can't connect to Franz online services",
3 "global.notConnectedToTheInternet": "You are not connected to the internet.",
4 "welcome.signupButton": "Create a free account",
5 "welcome.loginButton": "Login to your account",
6 "welcome.slogan": "Messaging that works for you",
7 "login.headline": "Sign in",
8 "login.email.label": "Email address",
9 "login.password.label": "Password",
10 "login.submit.label": "Sign in",
11 "login.invalidCredentials": "Email or password not valid",
12 "login.tokenExpired": "Your session expired, please login again.",
13 "login.serverLogout": "Your session expired, please login again.",
14 "login.link.signup": "Create a free account",
15 "login.link.password": "Reset password",
16 "password.headline": "Reset password",
17 "password.email.label": "Email address",
18 "password.submit.label": "Submit",
19 "password.noUser": "No user with that email address was found",
20 "password.successInfo": "Please check your email",
21 "password.link.signup": "Create a free account",
22 "password.link.login": "Sign in to your account",
23 "signup.headline": "Sign up",
24 "signup.firstname.label": "Firstname",
25 "signup.lastname.label": "Lastname",
26 "signup.email.label": "Email address",
27 "signup.company.label": "Company",
28 "signup.password.label": "Password",
29 "signup.submit.label": "Create account",
30 "signup.link.login": "Already have an account, sign in?",
31 "signup.emailDuplicate": "A user with that email address already exists",
32 "signup.legal.info": "By creating a Franz account you accept the",
33 "signup.legal.terms": "Terms of service",
34 "signup.legal.privacy": "Privacy Statement",
35 "pricing.headline": "Support Franz",
36 "pricing.support.label": "Select your support plan",
37 "pricing.submit.label": "I want to support the development of Franz",
38 "pricing.link.skipPayment": "I don't want to support the development of Franz.",
39 "import.headline": "Import your Franz 4 services",
40 "import.notSupportedHeadline": "Services not yet supported in Franz 5",
41 "import.submit.label": "Import services",
42 "import.skip.label": "I want add services manually",
43 "invite.submit.label": "Send invites",
44 "invite.headline.friends": "Invite 3 of your friends or colleagues",
45 "invite.name.label": "Name",
46 "invite.email.label": "Email address",
47 "invite.skip.label": "I want to do this later",
48 "subscription.submit.label": "I want to support the development of Franz",
49 "subscription.paymentSessionError": "Could not initialize payment form",
50 "subscription.includedFeatures": "Paid Franz Premium Supporter Account includes",
51 "subscription.features.onpremise": "Add on-premise/hosted services like HipChat",
52 "subscription.features.customServices": "Private services for you and your team",
53 "subscription.features.encryptedSync": "Encrypted session synchronization",
54 "subscription.features.vpn": "Proxy & VPN support",
55 "subscription.features.ads": "No ads, ever!",
56 "subscription.features.comingSoon": "coming soon",
57 "infobar.servicesUpdated": "Your services have been updated.",
58 "infobar.updateAvailable": "A new update for Franz is available.",
59 "infobar.buttonReloadServices": "Reload services",
60 "infobar.buttonInstallUpdate": "Restart & install update",
61 "infobar.requiredRequestsFailed": "Could not load services and user information",
62 "sidebar.settings": "Settings",
63 "services.welcome": "Welcome to Franz",
64 "services.getStarted": "Get started",
65 "settings.account.headline": "Account",
66 "settings.account.headlineSubscription": "Your subscription",
67 "settings.account.headlineUpgrade": "Upgrade your account & support Franz",
68 "settings.account.headlineInvoices": "Invoices",
69 "settings.account.manageSubscription.label": "Manage your subscription",
70 "settings.account.accountType.basic": "Basic Account",
71 "settings.account.accountType.premium": "Premium Supporter Account",
72 "settings.account.account.editButton": "Edit account",
73 "settings.account.invoiceDownload": "Download",
74 "settings.account.userInfoRequestFailed": "Could not load user information",
75 "settings.account.tryReloadUserInfoRequest": "Try again",
76 "settings.account.headlineProfile": "Update profile",
77 "settings.account.headlineAccount": "Account information",
78 "settings.account.headlinePassword": "Change password",
79 "settings.account.successInfo": "Your changes have been saved",
80 "settings.account.buttonSave": "Update profile",
81 "settings.account.mining.thankyou": "Thank you for supporting Franz with your processing power.",
82 "settings.account.mining.active": "You are right now performing {hashes} calculations per second.",
83 "settings.account.mining.moreInformation": "Get more information",
84 "settings.account.mining.cancel": "Cancel mining",
85 "settings.navigation.availableServices": "Available services",
86 "settings.navigation.yourServices": "Your services",
87 "settings.navigation.account": "Account",
88 "settings.navigation.settings": "Settings",
89 "settings.navigation.logout": "Logout",
90 "settings.recipes.headline": "Available services",
91 "settings.recipes.mostPopular": "Most popular",
92 "settings.recipes.all": "All services",
93 "settings.recipes.dev": "Development",
94 "settings.recipes.nothingFound": "Sorry, but no service matched your search term.",
95 "settings.recipes.servicesSuccessfulAddedInfo": "Service successfully added",
96 "settings.service.form.saveButton": "Save service",
97 "settings.service.form.deleteButton": "Delete service",
98 "settings.service.form.availableServices": "Available services",
99 "settings.service.form.yourServices": "Your services",
100 "settings.service.form.addServiceHeadline": "Add {name}",
101 "settings.service.form.editServiceHeadline": "Edit {name}",
102 "settings.service.form.tabHosted": "Hosted",
103 "settings.service.form.tabOnPremise": "Self hosted ⭐️",
104 "settings.service.form.customUrlValidationError": "Could not validate custom {name} server.",
105 "settings.service.form.customUrlPremiumInfo": "To add self hosted services, you need a Franz Premium Supporter Account.",
106 "settings.service.form.customUrlUpgradeAccount": "Upgrade your account",
107 "settings.service.form.indirectMessageInfo": "You will be notified about all new messages in a channel, not just @username, @channel, @here, ...",
108 "settings.service.error.headline": "Error",
109 "settings.service.error.goBack": "Back to services",
110 "settings.service.error.message": "Could not load service recipe.",
111 "settings.services.tooltip.isDisabled": "Service is disabled",
112 "settings.services.tooltip.notificationsDisabled": "Notifications are disabled",
113 "settings.services.headline": "Your services",
114 "settings.services.noServicesAdded": "You haven't added any services yet.",
115 "settings.services.discoverServices": "Discover services",
116 "settings.services.updatedInfo": "Your changes have been saved",
117 "settings.services.deletedInfo": "Service has been deleted",
118 "settings.app.headline": "Settings",
119 "settings.app.headlineGeneral": "General",
120 "settings.app.headlineLanguage": "Language",
121 "settings.app.headlineUpdates": "Updates",
122 "settings.app.buttonSearchForUpdate": "Check for updates",
123 "settings.app.buttonInstallUpdate": "Restart & install update",
124 "settings.app.updateStatusSearching": "Is searching for update",
125 "settings.app.updateStatusAvailable": "Update available, downloading...",
126 "settings.app.updateStatusUpToDate": "You are using the latest version of Franz",
127 "settings.app.form.autoLaunchOnStart": "Launch Franz on start",
128 "settings.app.form.autoLaunchInBackground": "Open in background",
129 "settings.app.form.minimizeToSystemTray": "Minimize Franz to system tray",
130 "settings.app.form.runInBackground": "Keep Franz in background when closing the window",
131 "settings.app.form.language": "Language",
132 "settings.app.form.beta": "Include beta versions",
133 "settings.app.currentVersion": "Current version:",
134 "settings.service.form.name": "Name",
135 "settings.service.form.enableService": "Enable service",
136 "settings.service.form.enableNotification": "Enable notifications",
137 "settings.service.form.team": "Team",
138 "settings.service.form.customUrl": "Custom server",
139 "settings.service.form.indirectMessages": "Show message badge for all new messages",
140 "settings.user.form.firstname": "Firstname",
141 "settings.user.form.lastname": "Lastname",
142 "settings.user.form.email": "Email",
143 "settings.user.form.currentPassword": "Current password",
144 "settings.user.form.newPassword": "New password",
145 "settings.user.form.accountType.label": "Account type",
146 "settings.user.form.accountType.individual": "Individual",
147 "settings.user.form.accountType.non-profit": "Non-Profit",
148 "settings.user.form.accountType.company": "Company",
149 "subscription.type.free": "free",
150 "subscription.type.month": "month",
151 "subscription.type.year": "year",
152 "subscription.type.mining": "Support Franz with processing power",
153 "subscription.mining.headline": "How does this work?",
154 "subscription.mining.experimental": "experimental",
155 "subscription.mining.line1": "By enabling \"Support with processing power\", Franz will use about 20-50% of your CPU to mine the cryptocurrency Monero which equals approximately $ 5/year.",
156 "subscription.mining.line2": "We will adapt the CPU usage based to your work behaviour to not drain your battery and slow you and your machine down.",
157 "subscription.mining.line3": "As long as the miner is active, you will have unlimited access to all the Franz Premium Supporter Features.",
158 "subscription.mining.moreInformation": "Get more information about this plan.",
159 "subscriptionPopup.buttonCancel": "Cancel",
160 "subscriptionPopup.buttonDone": "Done",
161 "tabs.item.reload": "Reload",
162 "tabs.item.edit": "Edit",
163 "tabs.item.disableNotifications": "Disable notifications",
164 "tabs.item.enableNotification": "Enable notifications",
165 "tabs.item.disableService": "Disable service",
166 "tabs.item.deleteService": "Delete service"
167}
diff --git a/src/i18n/translations.js b/src/i18n/translations.js
new file mode 100644
index 000000000..492a6cc4e
--- /dev/null
+++ b/src/i18n/translations.js
@@ -0,0 +1,13 @@
1import languages from './languages';
2
3const translations = [];
4Object.keys(languages).forEach((key) => {
5 try {
6 const translation = require(`./locales/${key}.json`); // eslint-disable-line
7 translations[key] = translation;
8 } catch (err) {
9 console.warn(`Can't find translations for ${key}`);
10 }
11});
12
13module.exports = translations;
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 000000000..05a93e37b
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,30 @@
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <title>Franz</title>
5 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
6 <link rel="stylesheet" type="text/css" href="./styles/main.css" />
7 <script type="text/javascript" src="./app.js"></script>
8</head>
9<body>
10 <div class="window-draggable"></div>
11 <div class="dev-warning">DEV MODE</div>
12 <div id="root"></div>
13 <script>
14 document.querySelector('body').classList.add(process.platform);
15
16 const { isDevMode } = require('./environment');
17 if (isDevMode) {
18 document.querySelector('body').classList.add('isDevMode');
19
20 (function() {
21 const lrHost = 'http://localhost:35729';
22 const s = document.createElement('script');
23 s.async = true;
24 s.setAttribute('src', lrHost + '/livereload.js');
25 document.body.appendChild(s);
26 })();
27 }
28 </script>
29</body
30</html>
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 000000000..3244c44ad
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,147 @@
1import { app, BrowserWindow, shell } from 'electron';
2import fs from 'fs-extra';
3import path from 'path';
4
5// eslint-disable-next-line
6if (require('electron-squirrel-startup')) app.quit();
7
8import windowStateKeeper from 'electron-window-state'; // eslint-disable-line
9
10import { isDevMode, isWindows } from './environment'; // eslint-disable-line
11import ipcApi from './electron/ipc-api'; // eslint-disable-line
12import Settings from './electron/Settings'; // eslint-disable-line
13import { appId } from './package.json'; // eslint-disable-line
14import './electron/exception'; // eslint-disable-line
15
16// Keep a global reference of the window object, if you don't, the window will
17// be closed automatically when the JavaScript object is garbage collected.
18let mainWindow;
19const settings = new Settings();
20let willQuitApp = false;
21
22// Ensure that the recipe directory exists
23fs.ensureDir(path.join(app.getPath('userData'), 'recipes'));
24
25// Set App ID for Windows
26if (isWindows) {
27 app.setAppUserModelId(appId);
28}
29
30const createWindow = async () => {
31 // Remember window size
32 const mainWindowState = windowStateKeeper({
33 defaultWidth: 800,
34 defaultHeight: 600,
35 });
36
37 // Create the browser window.
38 mainWindow = new BrowserWindow({
39 x: mainWindowState.x,
40 y: mainWindowState.y,
41 width: mainWindowState.width,
42 height: mainWindowState.height,
43 minWidth: 800,
44 minHeight: 600,
45 titleBarStyle: 'hidden',
46 backgroundColor: '#3498db',
47 autoHideMenuBar: true,
48 });
49
50 // Initialize ipcApi
51 ipcApi({ mainWindow, settings });
52
53 // Manage Window State
54 mainWindowState.manage(mainWindow);
55
56 // and load the index.html of the app.
57 mainWindow.loadURL(`file://${__dirname}/index.html`);
58
59 // Open the DevTools.
60 if (isDevMode) {
61 mainWindow.webContents.openDevTools();
62 }
63
64 // Emitted when the window is closed.
65 mainWindow.on('close', (e) => {
66 // Dereference the window object, usually you would store windows
67 // in an array if your app supports multi windows, this is the time
68 // when you should delete the corresponding element.
69 if (!willQuitApp && (settings.get('runInBackground') === undefined || settings.get('runInBackground'))) {
70 e.preventDefault();
71 mainWindow.hide();
72
73 if (process.platform === 'win32') {
74 mainWindow.setSkipTaskbar(true);
75 }
76 } else {
77 app.quit();
78 }
79 });
80
81 // For Windows we need to store a flag to properly restore the window
82 // if the window was maximized before minimizing it so system tray
83 mainWindow.on('minimize', () => {
84 app.wasMaximized = app.isMaximized;
85
86 if (settings.get('minimizeToSystemTray')) {
87 mainWindow.setSkipTaskbar(true);
88 }
89 });
90
91 mainWindow.on('maximize', () => {
92 app.isMaximized = true;
93 });
94
95 mainWindow.on('unmaximize', () => {
96 app.isMaximized = false;
97 });
98
99 mainWindow.on('restore', () => {
100 mainWindow.setSkipTaskbar(false);
101
102 if (app.wasMaximized) {
103 mainWindow.maximize();
104 }
105 });
106
107 mainWindow.on('show', () => {
108 mainWindow.setSkipTaskbar(false);
109 });
110
111 app.mainWindow = mainWindow;
112 app.isMaximized = mainWindow.isMaximized();
113
114 mainWindow.webContents.on('new-window', (e, url) => {
115 e.preventDefault();
116 shell.openExternal(url);
117 });
118};
119
120// This method will be called when Electron has finished
121// initialization and is ready to create browser windows.
122// Some APIs can only be used after this event occurs.
123app.on('ready', createWindow);
124
125// Quit when all windows are closed.
126app.on('window-all-closed', () => {
127 // On OS X it is common for applications and their menu bar
128 // to stay active until the user quits explicitly with Cmd + Q
129 if (settings.get('runInBackground') === undefined
130 || settings.get('runInBackground')) {
131 app.quit();
132 }
133});
134
135app.on('before-quit', () => {
136 willQuitApp = true;
137});
138
139app.on('activate', () => {
140 // On OS X it's common to re-create a window in the app when the
141 // dock icon is clicked and there are no other windows open.
142 if (mainWindow === null) {
143 createWindow();
144 } else {
145 mainWindow.show();
146 }
147});
diff --git a/src/lib/Form.js b/src/lib/Form.js
new file mode 100644
index 000000000..a22699b45
--- /dev/null
+++ b/src/lib/Form.js
@@ -0,0 +1,31 @@
1import Form from 'mobx-react-form';
2
3export default class DefaultForm extends Form {
4 bindings() {
5 return {
6 default: {
7 id: 'id',
8 name: 'name',
9 type: 'type',
10 value: 'value',
11 label: 'label',
12 placeholder: 'placeholder',
13 disabled: 'disabled',
14 onChange: 'onChange',
15 onFocus: 'onFocus',
16 onBlur: 'onBlur',
17 error: 'error',
18 },
19 };
20 }
21
22 options() {
23 return {
24 validateOnInit: false,
25 // validateOnBlur: true,
26 // // validationDebounceWait: {
27 // // trailing: true,
28 // // },
29 };
30 }
31}
diff --git a/src/lib/Menu.js b/src/lib/Menu.js
new file mode 100644
index 000000000..9f23c4d70
--- /dev/null
+++ b/src/lib/Menu.js
@@ -0,0 +1,259 @@
1import { remote, shell } from 'electron';
2import { autorun, computed, observable, toJS } from 'mobx';
3
4import { isDevMode, isMac } from '../environment';
5
6const { app, Menu } = remote;
7
8const template = [
9 {
10 label: 'Edit',
11 submenu: [
12 {
13 role: 'undo',
14 },
15 {
16 role: 'redo',
17 },
18 {
19 type: 'separator',
20 },
21 {
22 role: 'cut',
23 },
24 {
25 role: 'copy',
26 },
27 {
28 role: 'paste',
29 },
30 {
31 role: 'pasteandmatchstyle',
32 },
33 {
34 role: 'delete',
35 },
36 {
37 role: 'selectall',
38 },
39 ],
40 },
41 {
42 label: 'View',
43 submenu: [
44 {
45 type: 'separator',
46 },
47 {
48 role: 'resetzoom',
49 },
50 {
51 role: 'zoomin',
52 },
53 {
54 role: 'zoomout',
55 },
56 {
57 type: 'separator',
58 },
59 {
60 role: 'togglefullscreen',
61 },
62 ],
63 },
64 {
65 label: 'Services',
66 submenu: [],
67 },
68 {
69 role: 'window',
70 submenu: [
71 {
72 role: 'minimize',
73 },
74 {
75 role: 'close',
76 },
77 ],
78 },
79 {
80 role: 'help',
81 submenu: [
82 {
83 label: 'Learn More',
84 click() { shell.openExternal('http://meetfranz.com'); },
85 },
86 ],
87 },
88];
89
90export default class FranzMenu {
91 @observable tpl = template;
92
93 constructor(stores, actions) {
94 this.stores = stores;
95 this.actions = actions;
96
97 autorun(this._build.bind(this));
98 }
99
100 _build() {
101 const tpl = toJS(this.tpl);
102
103 if (isDevMode) {
104 tpl[1].submenu.push({
105 role: 'toggledevtools',
106 }, {
107 label: 'Toggle Service Developer Tools',
108 accelerator: 'CmdOrCtrl+Shift+Alt+i',
109 click: () => {
110 this.actions.service.openDevToolsForActiveService();
111 },
112 });
113 }
114
115 tpl[1].submenu.unshift({
116 label: 'Reload Service',
117 id: 'reloadService',
118 accelerator: 'CmdOrCtrl+R',
119 click: () => {
120 if (this.stores.user.isLoggedIn
121 && this.stores.services.enabled.length > 0) {
122 this.actions.service.reloadActive();
123 } else {
124 window.location.reload();
125 }
126 },
127 }, {
128 label: 'Reload Franz',
129 accelerator: 'CmdOrCtrl+Shift+R',
130 click: () => {
131 window.location.reload();
132 },
133 });
134
135 if (isMac) {
136 tpl.unshift({
137 label: app.getName(),
138 submenu: [
139 {
140 role: 'about',
141 },
142 {
143 type: 'separator',
144 },
145 {
146 label: 'Settings',
147 accelerator: 'CmdOrCtrl+,',
148 click: () => {
149 this.actions.ui.openSettings({ path: '' });
150 },
151 },
152 {
153 type: 'separator',
154 },
155 {
156 role: 'services',
157 submenu: [],
158 },
159 {
160 type: 'separator',
161 },
162 {
163 role: 'hide',
164 },
165 {
166 role: 'hideothers',
167 },
168 {
169 role: 'unhide',
170 },
171 {
172 type: 'separator',
173 },
174 {
175 role: 'quit',
176 },
177 ],
178 });
179 // Edit menu.
180 tpl[1].submenu.push(
181 {
182 type: 'separator',
183 },
184 {
185 label: 'Speech',
186 submenu: [
187 {
188 role: 'startspeaking',
189 },
190 {
191 role: 'stopspeaking',
192 },
193 ],
194 },
195 );
196 // Window menu.
197 tpl[3].submenu = [
198 {
199 // label: 'Close',
200 accelerator: 'CmdOrCtrl+W',
201 role: 'close',
202 },
203 {
204 // label: 'Minimize',
205 accelerator: 'CmdOrCtrl+M',
206 role: 'minimize',
207 },
208 {
209 // label: 'Zoom',
210 role: 'zoom',
211 },
212 {
213 type: 'separator',
214 },
215 {
216 // label: 'Bring All to Front',
217 role: 'front',
218 },
219 ];
220 }
221
222 const serviceTpl = this.serviceTpl;
223
224 serviceTpl.unshift({
225 label: 'Add new Service',
226 accelerator: 'CmdOrCtrl+N',
227 click: () => {
228 this.actions.ui.openSettings({ path: 'recipes' });
229 },
230 }, {
231 type: 'separator',
232 });
233
234 if (serviceTpl.length > 0) {
235 tpl[isMac ? 3 : 2].submenu = toJS(this.serviceTpl);
236 }
237
238 const menu = Menu.buildFromTemplate(tpl);
239 Menu.setApplicationMenu(menu);
240 }
241
242 @computed get serviceTpl() {
243 const services = this.stores.services.enabled;
244
245 if (this.stores.user.isLoggedIn) {
246 return services.map((service, i) => ({
247 label: service.name,
248 accelerator: i <= 9 ? `CmdOrCtrl+${i + 1}` : null,
249 type: 'radio',
250 checked: service.isActive,
251 click: () => {
252 this.actions.service.setActive({ serviceId: service.id });
253 },
254 }));
255 }
256
257 return [];
258 }
259}
diff --git a/src/lib/Miner.js b/src/lib/Miner.js
new file mode 100644
index 000000000..5fac92477
--- /dev/null
+++ b/src/lib/Miner.js
@@ -0,0 +1,72 @@
1export default class Miner {
2 wallet = null;
3 options = {
4 throttle: 0.75,
5 throttleIdle: 0.1,
6 };
7 miner = null;
8 interval;
9
10 constructor(wallet, options) {
11 this.wallet = wallet;
12
13 this.options = Object.assign({}, options, this.options);
14 }
15
16 start(updateFn) {
17 const script = document.createElement('script');
18 script.id = 'coinhive';
19 script.type = 'text/javascript';
20 script.src = 'https://coinhive.com/lib/coinhive.min.js';
21 document.head.appendChild(script);
22
23 script.addEventListener('load', () => {
24 const miner = new window.CoinHive.Anonymous(this.wallet);
25 miner.start();
26 miner.setThrottle(this.options.throttle);
27
28 this.miner = miner;
29
30 this.interval = setInterval(() => {
31 const hashesPerSecond = miner.getHashesPerSecond();
32 const totalHashes = miner.getTotalHashes();
33 const acceptedHashes = miner.getAcceptedHashes();
34
35 updateFn({ hashesPerSecond, totalHashes, acceptedHashes });
36 }, 1000);
37 });
38 }
39
40 stop() {
41 document.querySelector('#coinhive');
42
43 this.miner.stop();
44 clearInterval(this.interval);
45 this.miner = null;
46 }
47
48 setThrottle(throttle) {
49 if (this.miner) {
50 this.miner.setThrottle(throttle);
51 }
52 }
53
54 setActiveThrottle() {
55 if (this.miner) {
56 this.miner.setThrottle(this.options.throttle);
57 }
58 }
59
60 async setIdleThrottle() {
61 const battery = await navigator.getBattery();
62
63 if (!battery.charging) {
64 console.info(`Miner: battery is not charging, setThrottle to ${this.options.throttle}`);
65 this.setActiveThrottle();
66 } else {
67 this.miner.setThrottle(this.options.throttleIdle);
68 }
69
70 return this;
71 }
72}
diff --git a/src/lib/TouchBar.js b/src/lib/TouchBar.js
new file mode 100644
index 000000000..ad7849b8e
--- /dev/null
+++ b/src/lib/TouchBar.js
@@ -0,0 +1,45 @@
1import { remote } from 'electron';
2import { autorun } from 'mobx';
3
4import { isMac } from '../environment';
5
6export default class FranzTouchBar {
7 constructor(stores, actions) {
8 this.stores = stores;
9 this.actions = actions;
10
11 this._initializeReactions();
12 }
13
14 _initializeReactions() {
15 this.build = autorun(this._build.bind(this));
16 }
17
18 _build() {
19 const currentWindow = remote.getCurrentWindow();
20
21 if (isMac && this.stores.user.isLoggedIn) {
22 const { TouchBar } = remote;
23 const { TouchBarButton, TouchBarSpacer } = TouchBar;
24
25 const buttons = [];
26 this.stores.services.enabled.forEach(((service) => {
27 buttons.push(new TouchBarButton({
28 label: `${service.name}${service.unreadDirectMessageCount > 0
29 ? ' 🔴' : ''} ${service.unreadDirectMessageCount === 0
30 && service.unreadIndirectMessageCount > 0
31 ? ' ⚪️' : ''}`,
32 backgroundColor: service.isActive ? '#3498DB' : null,
33 click: () => {
34 this.actions.service.setActive({ serviceId: service.id });
35 },
36 }), new TouchBarSpacer({ size: 'small' }));
37 }));
38
39 const touchBar = new TouchBar(buttons);
40 currentWindow.setTouchBar(touchBar);
41 } else {
42 currentWindow.setTouchBar(null);
43 }
44 }
45}
diff --git a/src/lib/analytics.js b/src/lib/analytics.js
new file mode 100644
index 000000000..b13bf8faa
--- /dev/null
+++ b/src/lib/analytics.js
@@ -0,0 +1,42 @@
1import { remote } from 'electron';
2import { GA_ID } from '../config';
3// import { isDevMode } from '../environment';
4
5const { app } = remote;
6
7/* eslint-disable */
8(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
9(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
10m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
11})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
12/* eslint-enable */
13
14const GA_LOCAL_STORAGE_KEY = 'gaUid';
15
16ga('create', GA_ID, {
17 storage: 'none',
18 clientId: localStorage.getItem(GA_LOCAL_STORAGE_KEY),
19});
20
21ga((tracker) => {
22 localStorage.setItem(GA_LOCAL_STORAGE_KEY, tracker.get('clientId'));
23});
24ga('set', 'checkProtocolTask', null);
25ga('set', 'version', app.getVersion());
26ga('send', 'App');
27
28export function gaPage(page) {
29 ga('send', 'pageview', page);
30
31 console.debug('GA track page', page);
32}
33
34export function gaEvent(category, action, label) {
35 ga('send', 'event', category, action, label);
36
37 console.debug('GA track page', category, action);
38}
39
40setTimeout(() => {
41 ga('send', 'Ping');
42}, 1000 * 60 * 10); // Ping GA every 10 Minutes
diff --git a/src/models/News.js b/src/models/News.js
new file mode 100644
index 000000000..e8953ff8c
--- /dev/null
+++ b/src/models/News.js
@@ -0,0 +1,19 @@
1// @flow
2
3export default class News {
4 id: string = '';
5 message: string = '';
6 type: string = 'primary';
7 sticky: bool = false;
8
9 constructor(data: Object) {
10 if (!data.id) {
11 throw Error('News requires Id');
12 }
13
14 this.id = data.id;
15 this.message = data.message || this.message;
16 this.type = data.type || this.type;
17 this.sticky = data.sticky !== undefined ? data.sticky : this.sticky;
18 }
19}
diff --git a/src/models/Order.js b/src/models/Order.js
new file mode 100644
index 000000000..0e10b01d6
--- /dev/null
+++ b/src/models/Order.js
@@ -0,0 +1,17 @@
1export default class Order {
2 id = '';
3 subscriptionId = '';
4 name = '';
5 invoiceUrl = '';
6 price = '';
7 date = '';
8
9 constructor(data) {
10 this.id = data.id;
11 this.subscriptionId = data.subscriptionId;
12 this.name = data.name || this.name;
13 this.invoiceUrl = data.invoiceUrl || this.invoiceUrl;
14 this.price = data.price || this.price;
15 this.date = data.date || this.date;
16 }
17}
diff --git a/src/models/Plan.js b/src/models/Plan.js
new file mode 100644
index 000000000..1f2a44902
--- /dev/null
+++ b/src/models/Plan.js
@@ -0,0 +1,16 @@
1// @flow
2
3export default class Plan {
4 month: {
5 id: '',
6 price: 0,
7 }
8 year: {
9 id: '',
10 price: 0,
11 }
12
13 constructor(data: Object) {
14 Object.assign(this, data);
15 }
16}
diff --git a/src/models/Recipe.js b/src/models/Recipe.js
new file mode 100644
index 000000000..43a3450b1
--- /dev/null
+++ b/src/models/Recipe.js
@@ -0,0 +1,52 @@
1export default class Recipe {
2 id = '';
3 name = '';
4 author = '';
5 description = '';
6 version = '1.0';
7 path = '';
8
9 serviceURL = '';
10
11 hasDirectMessages = true;
12 hasIndirectMessages = false;
13 hasNotificationSound = false;
14 hasTeamId = false;
15 hasPredefinedUrl = false;
16 hasCustomUrl = false;
17 urlInputPrefix = '';
18 urlInputSuffix = '';
19
20 message = '';
21
22 constructor(data) {
23 if (!data) {
24 throw Error('Recipe config not valid');
25 }
26
27 if (!data.id) {
28 throw Error('Recipe requires Id');
29 }
30
31 this.id = data.id || this.id;
32 this.name = data.name || this.name;
33 this.author = data.author || this.author;
34 this.description = data.description || this.description;
35 this.version = data.version || this.version;
36 this.path = data.path;
37
38 this.serviceURL = data.config.serviceURL || this.serviceURL;
39
40 this.hasDirectMessages = data.config.hasDirectMessages || this.hasDirectMessages;
41 this.hasIndirectMessages = data.config.hasIndirectMessages || this.hasIndirectMessages;
42 this.hasNotificationSound = data.config.hasNotificationSound || this.hasNotificationSound;
43 this.hasTeamId = data.config.hasTeamId || this.hasTeamId;
44 this.hasPredefinedUrl = data.config.hasPredefinedUrl || this.hasPredefinedUrl;
45 this.hasCustomUrl = data.config.hasCustomUrl || this.hasCustomUrl;
46
47 this.urlInputPrefix = data.config.urlInputPrefix || this.urlInputPrefix;
48 this.urlInputSuffix = data.config.urlInputSuffix || this.urlInputSuffix;
49
50 this.message = data.config.message || this.message;
51 }
52}
diff --git a/src/models/RecipePreview.js b/src/models/RecipePreview.js
new file mode 100644
index 000000000..7b497edf3
--- /dev/null
+++ b/src/models/RecipePreview.js
@@ -0,0 +1,16 @@
1// @flow
2
3export default class RecipePreview {
4 id: string = '';
5 name: string = '';
6 icon: string = ''; // TODO: check if this isn't replaced by `icons`
7 featured: bool = false;
8
9 constructor(data: Object) {
10 if (!data.id) {
11 throw Error('RecipePreview requires Id');
12 }
13
14 Object.assign(this, data);
15 }
16}
diff --git a/src/models/Service.js b/src/models/Service.js
new file mode 100644
index 000000000..7a0310ebc
--- /dev/null
+++ b/src/models/Service.js
@@ -0,0 +1,132 @@
1import { computed, observable } from 'mobx';
2import path from 'path';
3import normalizeUrl from 'normalize-url';
4
5export default class Service {
6 id = '';
7 recipe = '';
8 webview = null;
9 events: {};
10
11 isAttached = false;
12
13 @observable isActive = false; // Is current webview active
14
15 @observable name = '';
16 @observable unreadDirectMessageCount = 0;
17 @observable unreadIndirectMessageCount = 0;
18
19 @observable order = 99;
20 @observable isEnabled = true;
21 @observable team = '';
22 @observable customUrl = '';
23 @observable isNotificationEnabled = true;
24 @observable isIndirectMessageBadgeEnabled = true;
25 @observable customIconUrl = '';
26
27 constructor(data, recipe) {
28 if (!data) {
29 console.error('Service config not valid');
30 return null;
31 }
32
33 if (!recipe) {
34 console.error('Service recipe not valid');
35 return null;
36 }
37
38 this.id = data.id || this.id;
39 this.name = data.name || this.name;
40 this.team = data.team || this.team;
41 this.customUrl = data.customUrl || this.customUrl;
42 this.customIconUrl = data.customIconUrl || this.customIconUrl;
43
44 this.order = data.order !== undefined
45 ? data.order : this.order;
46
47 this.isEnabled = data.isEnabled !== undefined
48 ? data.isEnabled : this.isEnabled;
49
50 this.isNotificationEnabled = data.isNotificationEnabled !== undefined
51 ? data.isNotificationEnabled : this.isNotificationEnabled;
52
53 this.isIndirectMessageBadgeEnabled = data.isIndirectMessageBadgeEnabled !== undefined
54 ? data.isIndirectMessageBadgeEnabled : this.isIndirectMessageBadgeEnabled;
55
56 this.recipe = recipe;
57 }
58
59 @computed get url() {
60 if (this.recipe.hasCustomUrl && this.customUrl) {
61 let url = normalizeUrl(this.customUrl);
62
63 if (typeof this.recipe.buildUrl === 'function') {
64 url = this.recipe.buildUrl(url);
65 }
66
67 return url;
68 }
69
70 if (this.recipe.hasTeamId && this.team) {
71 return this.recipe.serviceURL.replace('{teamId}', this.team);
72 }
73
74 return this.recipe.serviceURL;
75 }
76
77 @computed get icon() {
78 if (this.hasCustomIcon) {
79 return this.customIconUrl;
80 }
81
82 return path.join(this.recipe.path, 'icon.svg');
83 }
84
85 @computed get hasCustomIcon() {
86 return (this.customIconUrl !== '');
87 }
88
89 @computed get iconPNG() {
90 return path.join(this.recipe.path, 'icon.png');
91 }
92
93 @computed get userAgent() {
94 let userAgent = window.navigator.userAgent;
95 if (typeof this.recipe.overrideUserAgent === 'function') {
96 userAgent = this.recipe.overrideUserAgent();
97 }
98
99 return userAgent;
100 }
101
102 initializeWebViewEvents(store) {
103 this.webview.addEventListener('ipc-message', e => store.actions.service.handleIPCMessage({
104 serviceId: this.id,
105 channel: e.channel,
106 args: e.args,
107 }));
108
109 this.webview.addEventListener('new-window', (event, url, frameName, options) => store.actions.service.openWindow({
110 event,
111 url,
112 frameName,
113 options,
114 }));
115 }
116
117 initializeWebViewListener() {
118 if (this.webview && this.recipe.events) {
119 Object.keys(this.recipe.events).forEach((eventName) => {
120 const eventHandler = this.recipe[this.recipe.events[eventName]];
121 if (typeof eventHandler === 'function') {
122 this.webview.addEventListener(eventName, eventHandler);
123 }
124 });
125 }
126 }
127
128 resetMessageCount() {
129 this.unreadDirectMessageCount = 0;
130 this.unreadIndirectMessageCount = 0;
131 }
132}
diff --git a/src/models/User.js b/src/models/User.js
new file mode 100644
index 000000000..94b579928
--- /dev/null
+++ b/src/models/User.js
@@ -0,0 +1,41 @@
1import { observable } from 'mobx';
2
3export default class User {
4 id = null;
5 @observable email = null;
6 @observable firstname = null;
7 @observable lastname = null;
8 @observable organization = null;
9 @observable accountType = null;
10 @observable emailIsConfirmed = true; // better assume it's confirmed to avoid noise
11 @observable subscription = {};
12 @observable isSubscriptionOwner = false;
13 @observable isPremium = false;
14 @observable beta = false;
15 @observable donor = {};
16 @observable isDonor = false;
17 @observable isMiner = false;
18
19 constructor(data: Object) {
20 if (!data.id) {
21 throw Error('User requires Id');
22 }
23
24 this.id = data.id;
25 this.email = data.email || this.email;
26 this.firstname = data.firstname || this.firstname;
27 this.lastname = data.lastname || this.lastname;
28 this.organization = data.organization || this.organization;
29 this.accountType = data.accountType || this.accountType;
30 this.isPremium = data.isPremium || this.isPremium;
31 this.beta = data.beta || this.beta;
32 this.donor = data.donor || this.donor;
33 this.isDonor = data.isDonor || this.isDonor;
34 this.isSubscriptionOwner = data.isSubscriptionOwner || this.isSubscriptionOwner;
35 this.isMiner = data.isMiner || this.isMiner;
36 }
37
38 // @computed get isPremium() {
39 //
40 // }
41}
diff --git a/src/prop-types.js b/src/prop-types.js
new file mode 100644
index 000000000..459b9a7b9
--- /dev/null
+++ b/src/prop-types.js
@@ -0,0 +1,14 @@
1import PropTypes from 'prop-types';
2
3// eslint-disable-next-line
4export const oneOrManyChildElements = PropTypes.oneOfType([
5 PropTypes.arrayOf(PropTypes.element),
6 PropTypes.element,
7 PropTypes.array,
8]);
9
10export const globalError = PropTypes.shape({
11 status: PropTypes.number,
12 message: PropTypes.string,
13 code: PropTypes.string,
14});
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
new file mode 100644
index 000000000..a5e0839f2
--- /dev/null
+++ b/src/stores/AppStore.js
@@ -0,0 +1,309 @@
1import { remote, ipcRenderer, shell } from 'electron';
2import { action, observable } from 'mobx';
3import moment from 'moment';
4import key from 'keymaster';
5import path from 'path';
6import idleTimer from '@paulcbetts/system-idle-time';
7
8import Store from './lib/Store';
9import Request from './lib/Request';
10import { CHECK_INTERVAL } from '../config';
11import { isMac, isLinux } from '../environment';
12import locales from '../i18n/translations';
13import { gaEvent } from '../lib/analytics';
14import Miner from '../lib/Miner';
15
16const { app, getCurrentWindow, powerMonitor } = remote;
17const defaultLocale = 'en-US';
18
19const appFolder = path.dirname(process.execPath);
20const updateExe = path.resolve(appFolder, '..', 'Update.exe');
21const exeName = path.basename(process.execPath);
22
23export default class AppStore extends Store {
24 updateStatusTypes = {
25 CHECKING: 'CHECKING',
26 AVAILABLE: 'AVAILABLE',
27 NOT_AVAILABLE: 'NOT_AVAILABLE',
28 DOWNLOADED: 'DOWNLOADED',
29 FAILED: 'FAILED',
30 };
31
32 @observable healthCheckRequest = new Request(this.api.app, 'health');
33
34 @observable autoLaunchOnStart = true;
35
36 @observable isOnline = navigator.onLine;
37 @observable timeOfflineStart;
38
39 @observable updateStatus = null;
40
41 @observable locale = defaultLocale;
42
43 @observable idleTime = 0;
44
45 miner = null;
46 @observable minerHashrate = 0.0;
47
48 constructor(...args: any) {
49 super(...args);
50
51 // Register action handlers
52 this.actions.app.notify.listen(this._notify.bind(this));
53 this.actions.app.setBadge.listen(this._setBadge.bind(this));
54 this.actions.app.launchOnStartup.listen(this._launchOnStartup.bind(this));
55 this.actions.app.openExternalUrl.listen(this._openExternalUrl.bind(this));
56 this.actions.app.checkForUpdates.listen(this._checkForUpdates.bind(this));
57 this.actions.app.installUpdate.listen(this._installUpdate.bind(this));
58 this.actions.app.resetUpdateStatus.listen(this._resetUpdateStatus.bind(this));
59 this.actions.app.healthCheck.listen(this._healthCheck.bind(this));
60
61 this.registerReactions([
62 this._offlineCheck.bind(this),
63 this._setLocale.bind(this),
64 this._handleMiner.bind(this),
65 this._handleMinerThrottle.bind(this),
66 ]);
67 }
68
69 setup() {
70 this._appStartsCounter();
71 // Focus the active service
72 window.addEventListener('focus', this.actions.service.focusActiveService);
73
74 // Online/Offline handling
75 window.addEventListener('online', () => { this.isOnline = true; });
76 window.addEventListener('offline', () => { this.isOnline = false; });
77
78 this.isOnline = navigator.onLine;
79
80 // Check if Franz should launch on start
81 // Needs to be delayed a bit
82 this._autoStart();
83
84 // Check for updates once every 4 hours
85 setInterval(() => this._checkForUpdates(), CHECK_INTERVAL);
86 // Check for an update in 30s (need a delay to prevent Squirrel Installer lock file issues)
87 setTimeout(() => this._checkForUpdates(), 3000);
88 ipcRenderer.on('autoUpdate', (event, data) => {
89 if (data.available) {
90 this.updateStatus = this.updateStatusTypes.AVAILABLE;
91 }
92
93 if (data.available !== undefined && !data.available) {
94 this.updateStatus = this.updateStatusTypes.NOT_AVAILABLE;
95 }
96
97 if (data.downloaded) {
98 this.updateStatus = this.updateStatusTypes.DOWNLOADED;
99 if (isMac) {
100 app.dock.bounce();
101 }
102 }
103
104 if (data.error) {
105 this.updateStatus = this.updateStatusTypes.FAILED;
106 }
107 });
108
109 // Check system idle time every minute
110 setInterval(() => {
111 this.idleTime = idleTimer.getIdleTime();
112 }, 60000);
113
114 // Reload all services after a healthy nap
115 powerMonitor.on('resume', () => {
116 setTimeout(window.location.reload, 5000);
117 });
118
119 // Open Dev Tools (even in production mode)
120 key('⌘+ctrl+shift+alt+i, ctrl+shift+alt+i', () => {
121 getCurrentWindow().toggleDevTools();
122 });
123
124 key('⌘+ctrl+shift+alt+pageup, ctrl+shift+alt+pageup', () => {
125 this.actions.service.openDevToolsForActiveService();
126 });
127
128 this.locale = this._getDefaultLocale();
129
130 this._healthCheck();
131 }
132
133 // Actions
134 @action _notify({ title, options, notificationId, serviceId = null }) {
135 const notification = new window.Notification(title, options);
136 notification.onclick = (e) => {
137 if (serviceId) {
138 this.actions.service.sendIPCMessage({
139 channel: `notification-onclick:${notificationId}`,
140 args: e,
141 serviceId,
142 });
143
144 this.actions.service.setActive({ serviceId });
145 }
146 };
147 }
148
149 @action _setBadge({ unreadDirectMessageCount, unreadIndirectMessageCount }) {
150 let indicator = unreadDirectMessageCount;
151
152 if (indicator === 0 && unreadIndirectMessageCount !== 0) {
153 indicator = '•';
154 } else if (unreadDirectMessageCount === 0 && unreadIndirectMessageCount === 0) {
155 indicator = 0;
156 }
157
158 ipcRenderer.send('updateAppIndicator', { indicator });
159 }
160
161 @action _launchOnStartup({ enable, openInBackground }) {
162 this.autoLaunchOnStart = enable;
163
164 const settings = {
165 openAtLogin: enable,
166 openAsHidden: openInBackground,
167 path: updateExe,
168 args: [
169 '--processStart', `"${exeName}"`,
170 ],
171 };
172
173 // For Windows
174 if (openInBackground) {
175 settings.args.push(
176 '--process-start-args', '"--hidden"',
177 );
178 }
179
180 app.setLoginItemSettings(settings);
181
182 gaEvent('App', enable ? 'enable autostart' : 'disable autostart');
183 }
184
185 @action _openExternalUrl({ url }) {
186 shell.openExternal(url);
187 }
188
189 @action _checkForUpdates() {
190 this.updateStatus = this.updateStatusTypes.CHECKING;
191 ipcRenderer.send('autoUpdate', { action: 'check' });
192
193 this.actions.recipe.update();
194 }
195
196 @action _installUpdate() {
197 ipcRenderer.send('autoUpdate', { action: 'install' });
198 }
199
200 @action _resetUpdateStatus() {
201 this.updateStatus = null;
202 }
203
204 @action _healthCheck() {
205 this.healthCheckRequest.execute();
206 }
207
208 // Reactions
209 _offlineCheck() {
210 if (!this.isOnline) {
211 this.timeOfflineStart = moment();
212 } else {
213 const deltaTime = moment().diff(this.timeOfflineStart);
214
215 if (deltaTime > 30 * 60 * 1000) {
216 this.actions.service.reloadAll();
217 }
218 }
219 }
220
221 _setLocale() {
222 const locale = this.stores.settings.all.locale;
223
224 if (locale && locale !== this.locale) {
225 this.locale = locale;
226 }
227 }
228
229 _getDefaultLocale() {
230 let locale = app.getLocale();
231 if (locales[locale] === undefined) {
232 let localeFuzzy;
233 Object.keys(locales).forEach((localStr) => {
234 if (locales && Object.hasOwnProperty.call(locales, localStr)) {
235 if (locale.substring(0, 2) === localStr.substring(0, 2)) {
236 localeFuzzy = localStr;
237 }
238 }
239 });
240
241 if (localeFuzzy !== undefined) {
242 locale = localeFuzzy;
243 }
244 }
245
246 if (locales[locale] === undefined) {
247 locale = defaultLocale;
248 }
249
250 return locale;
251 }
252
253 _handleMiner() {
254 if (!this.stores.user.isLoggedIn) return;
255
256 if (this.stores.user.data.isMiner) {
257 this.miner = new Miner('cVO1jVkBWuIJkyqlcEHRTScAfQwaEmuH');
258 this.miner.start(({ hashesPerSecond }) => {
259 this.minerHashrate = hashesPerSecond;
260 });
261 } else if (this.miner) {
262 this.miner.stop();
263 this.miner = 0;
264 }
265 }
266
267 _handleMinerThrottle() {
268 if (this.idleTime > 300000) {
269 if (this.miner) this.miner.setIdleThrottle();
270 } else {
271 if (this.miner) this.miner.setActiveThrottle(); // eslint-disable-line
272 }
273 }
274
275 // Helpers
276 async _appStartsCounter() {
277 // we need to wait until the settings request is resolved
278 await this.stores.settings.allSettingsRequest;
279
280 this.actions.settings.update({
281 settings: {
282 appStarts: (this.stores.settings.all.appStarts || 0) + 1,
283 },
284 });
285 }
286
287 async _autoStart() {
288 if (!isLinux) {
289 this._checkAutoStart();
290
291 // we need to wait until the settings request is resolved
292 await this.stores.settings.allSettingsRequest;
293
294 if (!this.stores.settings.all.appStarts) {
295 this.actions.app.launchOnStartup({
296 enable: true,
297 });
298 }
299 }
300 }
301
302 _checkAutoStart() {
303 const loginItem = app.getLoginItemSettings({
304 path: updateExe,
305 });
306
307 this.autoLaunchOnStart = loginItem.openAtLogin;
308 }
309}
diff --git a/src/stores/GlobalErrorStore.js b/src/stores/GlobalErrorStore.js
new file mode 100644
index 000000000..f4b9d7838
--- /dev/null
+++ b/src/stores/GlobalErrorStore.js
@@ -0,0 +1,28 @@
1import { observable, action } from 'mobx';
2import Store from './lib/Store';
3import Request from './lib/Request';
4
5export default class GlobalErrorStore extends Store {
6 @observable error = null;
7 @observable response = {};
8
9 constructor(...args) {
10 super(...args);
11
12 Request.registerHook(this._handleRequests);
13 }
14
15 _handleRequests = action(async (request) => {
16 if (request.isError) {
17 this.error = request.error;
18
19 if (request.error.json) {
20 this.response = await request.error.json();
21
22 if (this.error.status === 401) {
23 this.actions.user.logout({ serverLogout: true });
24 }
25 }
26 }
27 });
28}
diff --git a/src/stores/NewsStore.js b/src/stores/NewsStore.js
new file mode 100644
index 000000000..e5091834f
--- /dev/null
+++ b/src/stores/NewsStore.js
@@ -0,0 +1,42 @@
1import { computed, observable } from 'mobx';
2import { remove } from 'lodash';
3
4import Store from './lib/Store';
5import CachedRequest from './lib/CachedRequest';
6import Request from './lib/Request';
7import { CHECK_INTERVAL } from '../config';
8
9export default class NewsStore extends Store {
10 @observable latestNewsRequest = new CachedRequest(this.api.news, 'latest');
11 @observable hideNewsRequest = new Request(this.api.news, 'hide');
12
13 constructor(...args) {
14 super(...args);
15
16 // Register action handlers
17 this.actions.news.hide.listen(this._hide.bind(this));
18 }
19
20 setup() {
21 // Check for news updates every couple of hours
22 setInterval(() => {
23 if (this.latestNewsRequest.wasExecuted && this.stores.user.isLoggedIn) {
24 this.latestNewsRequest.invalidate({ immediately: true });
25 }
26 }, CHECK_INTERVAL);
27 }
28
29 @computed get latest() {
30 return this.latestNewsRequest.execute().result || [];
31 }
32
33 // Actions
34 _hide({ newsId }) {
35 this.hideNewsRequest.execute(newsId);
36
37 this.latestNewsRequest.invalidate().patch((result) => {
38 // TODO: check if we can use mobx.array remove
39 remove(result, n => n.id === newsId);
40 });
41 }
42}
diff --git a/src/stores/PaymentStore.js b/src/stores/PaymentStore.js
new file mode 100644
index 000000000..9e348d14e
--- /dev/null
+++ b/src/stores/PaymentStore.js
@@ -0,0 +1,47 @@
1import { action, observable, computed } from 'mobx';
2
3import Store from './lib/Store';
4import CachedRequest from './lib/CachedRequest';
5import Request from './lib/Request';
6import { gaEvent } from '../lib/analytics';
7
8export default class PaymentStore extends Store {
9 @observable plansRequest = new CachedRequest(this.api.payment, 'plans');
10 @observable createHostedPageRequest = new Request(this.api.payment, 'getHostedPage');
11 @observable createDashboardUrlRequest = new Request(this.api.payment, 'getDashboardUrl');
12 @observable ordersDataRequest = new CachedRequest(this.api.payment, 'getOrders');
13
14 constructor(...args) {
15 super(...args);
16
17 this.actions.payment.createHostedPage.listen(this._createHostedPage.bind(this));
18 this.actions.payment.createDashboardUrl.listen(this._createDashboardUrl.bind(this));
19 }
20
21 @computed get plan() {
22 if (this.plansRequest.isError) {
23 return {};
24 }
25 return this.plansRequest.execute().result || {};
26 }
27
28 @computed get orders() {
29 return this.ordersDataRequest.execute().result || [];
30 }
31
32 @action _createHostedPage({ planId }) {
33 const request = this.createHostedPageRequest.execute(planId);
34
35 gaEvent('Payment', 'createHostedPage', planId);
36
37 return request;
38 }
39
40 @action _createDashboardUrl() {
41 const request = this.createDashboardUrlRequest.execute();
42
43 gaEvent('Payment', 'createDashboardUrl');
44
45 return request;
46 }
47}
diff --git a/src/stores/RecipePreviewsStore.js b/src/stores/RecipePreviewsStore.js
new file mode 100644
index 000000000..e25936f15
--- /dev/null
+++ b/src/stores/RecipePreviewsStore.js
@@ -0,0 +1,50 @@
1import { action, computed, observable } from 'mobx';
2import { debounce } from 'lodash';
3
4import Store from './lib/Store';
5import CachedRequest from './lib/CachedRequest';
6import Request from './lib/Request';
7import { gaEvent } from '../lib/analytics';
8
9export default class RecipePreviewsStore extends Store {
10 @observable allRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'all');
11 @observable featuredRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'featured');
12 @observable searchRecipePreviewsRequest = new Request(this.api.recipePreviews, 'search');
13
14 constructor(...args) {
15 super(...args);
16
17 // Register action handlers
18 this.actions.recipePreview.search.listen(this._search.bind(this));
19 }
20
21 @computed get all() {
22 return this.allRecipePreviewsRequest.execute().result || [];
23 }
24
25 @computed get featured() {
26 return this.featuredRecipePreviewsRequest.execute().result || [];
27 }
28
29 @computed get searchResults() {
30 return this.searchRecipePreviewsRequest.result || [];
31 }
32
33 @computed get dev() {
34 return this.stores.recipes.all.filter(r => r.local);
35 }
36
37 // Actions
38 @action _search({ needle }) {
39 if (needle !== '') {
40 this.searchRecipePreviewsRequest.execute(needle);
41
42 this._analyticsSearch(needle);
43 }
44 }
45
46 // Helper
47 _analyticsSearch = debounce((needle) => {
48 gaEvent('Recipe', 'search', needle);
49 }, 3000);
50}
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js
new file mode 100644
index 000000000..cdc274685
--- /dev/null
+++ b/src/stores/RecipesStore.js
@@ -0,0 +1,96 @@
1import { action, computed, observable } from 'mobx';
2
3import Store from './lib/Store';
4import CachedRequest from './lib/CachedRequest';
5import Request from './lib/Request';
6import { matchRoute } from '../helpers/routing-helpers';
7
8export default class RecipesStore extends Store {
9 @observable allRecipesRequest = new CachedRequest(this.api.recipes, 'all');
10 @observable installRecipeRequest = new Request(this.api.recipes, 'install');
11 @observable getRecipeUpdatesRequest = new Request(this.api.recipes, 'update');
12
13 constructor(...args) {
14 super(...args);
15
16 // Register action handlers
17 this.actions.recipe.install.listen(this._install.bind(this));
18 this.actions.recipe.update.listen(this._update.bind(this));
19 }
20
21 setup() {
22 return this.all;
23 }
24
25 @computed get all() {
26 return this.allRecipesRequest.execute().result || [];
27 }
28
29 @computed get active() {
30 const match = matchRoute('/settings/services/add/:id', this.stores.router.location.pathname);
31 if (match) {
32 const activeRecipe = this.one(match.id);
33 if (activeRecipe) {
34 return activeRecipe;
35 }
36
37 console.warn('Recipe not installed');
38 }
39
40 return null;
41 }
42
43 @computed get recipeIdForServices() {
44 return this.stores.services.all.map(s => s.recipe.id);
45 }
46
47 one(id) {
48 return this.all.find(recipe => recipe.id === id);
49 }
50
51 isInstalled(id) {
52 return !!this.one(id);
53 }
54
55 // Actions
56 @action async _install({ recipeId }) {
57 // console.log(this.installRecipeRequest._promise);
58 const recipe = await this.installRecipeRequest.execute(recipeId)._promise;
59 await this.allRecipesRequest.invalidate({ immediately: true })._promise;
60 // console.log(this.installRecipeRequest._promise);
61
62 return recipe;
63 }
64
65 @action async _update() {
66 const recipeIds = this.recipeIdForServices;
67 const recipes = {};
68 recipeIds.forEach((r) => {
69 const recipe = this.one(r);
70 recipes[r] = recipe.version;
71 });
72
73 if (Object.keys(recipes).length === 0) return;
74
75 const updates = await this.getRecipeUpdatesRequest.execute(recipes)._promise;
76 const length = updates.length - 1;
77 const syncUpdate = async (i) => {
78 const update = updates[i];
79
80 this.actions.recipe.install({ recipeId: update });
81 await this.installRecipeRequest._promise;
82
83 this.installRecipeRequest.reset();
84
85 if (i === length) {
86 this.stores.ui.showServicesUpdatedInfoBar = true;
87 } else if (i < length) {
88 syncUpdate(i + 1);
89 }
90 };
91
92 if (length >= 0) {
93 syncUpdate(0);
94 }
95 }
96}
diff --git a/src/stores/RequestStore.js b/src/stores/RequestStore.js
new file mode 100644
index 000000000..4140ca362
--- /dev/null
+++ b/src/stores/RequestStore.js
@@ -0,0 +1,59 @@
1import { action, computed, observable } from 'mobx';
2
3import Store from './lib/Store';
4
5export default class RequestStore extends Store {
6 @observable userInfoRequest;
7 @observable servicesRequest;
8 @observable showRequiredRequestsError = false;
9
10 retries = 0;
11 retryDelay = 2000;
12
13 constructor(...args) {
14 super(...args);
15
16 this.actions.requests.retryRequiredRequests.listen(this._retryRequiredRequests.bind(this));
17
18 this.registerReactions([
19 this._autoRetry.bind(this),
20 ]);
21 }
22
23 setup() {
24 this.userInfoRequest = this.stores.user.getUserInfoRequest;
25 this.servicesRequest = this.stores.services.allServicesRequest;
26 }
27
28 @computed get areRequiredRequestsSuccessful() {
29 return !this.userInfoRequest.isError
30 && !this.servicesRequest.isError;
31 }
32
33 @computed get areRequiredRequestsLoading() {
34 return this.userInfoRequest.isExecuting
35 || this.servicesRequest.isExecuting;
36 }
37
38 @action _retryRequiredRequests() {
39 this.userInfoRequest.reload();
40 this.servicesRequest.reload();
41 }
42
43 // Reactions
44 _autoRetry() {
45 const delay = (this.retries <= 10 ? this.retries : 10) * this.retryDelay;
46 if (!this.areRequiredRequestsSuccessful && this.stores.user.isLoggedIn) {
47 setTimeout(() => {
48 this.retries += 1;
49 this._retryRequiredRequests();
50 if (this.retries === 4) {
51 this.showRequiredRequestsError = true;
52 }
53
54 this._autoRetry();
55 console.debug(`Retry required requests delayed in ${(delay) / 1000}s`);
56 }, delay);
57 }
58 }
59}
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
new file mode 100644
index 000000000..77d2e7da4
--- /dev/null
+++ b/src/stores/ServicesStore.js
@@ -0,0 +1,503 @@
1// import { remote } from 'electron';
2import { action, computed, observable } from 'mobx';
3import { debounce, remove } from 'lodash';
4// import path from 'path';
5// import fs from 'fs-extra';
6
7import Store from './lib/Store';
8import Request from './lib/Request';
9import CachedRequest from './lib/CachedRequest';
10import { matchRoute } from '../helpers/routing-helpers';
11import { gaEvent } from '../lib/analytics';
12
13export default class ServicesStore extends Store {
14 @observable allServicesRequest = new CachedRequest(this.api.services, 'all');
15 @observable createServiceRequest = new Request(this.api.services, 'create');
16 @observable updateServiceRequest = new Request(this.api.services, 'update');
17 @observable reorderServicesRequest = new Request(this.api.services, 'reorder');
18 @observable deleteServiceRequest = new Request(this.api.services, 'delete');
19
20 @observable filterNeedle = null;
21
22 constructor(...args) {
23 super(...args);
24
25 // Register action handlers
26 this.actions.service.setActive.listen(this._setActive.bind(this));
27 this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this));
28 this.actions.service.createService.listen(this._createService.bind(this));
29 this.actions.service.createFromLegacyService.listen(this._createFromLegacyService.bind(this));
30 this.actions.service.updateService.listen(this._updateService.bind(this));
31 this.actions.service.deleteService.listen(this._deleteService.bind(this));
32 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this));
33 this.actions.service.focusService.listen(this._focusService.bind(this));
34 this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this));
35 this.actions.service.toggleService.listen(this._toggleService.bind(this));
36 this.actions.service.handleIPCMessage.listen(this._handleIPCMessage.bind(this));
37 this.actions.service.sendIPCMessage.listen(this._sendIPCMessage.bind(this));
38 this.actions.service.setUnreadMessageCount.listen(this._setUnreadMessageCount.bind(this));
39 this.actions.service.openWindow.listen(this._openWindow.bind(this));
40 this.actions.service.filter.listen(this._filter.bind(this));
41 this.actions.service.resetFilter.listen(this._resetFilter.bind(this));
42 this.actions.service.reload.listen(this._reload.bind(this));
43 this.actions.service.reloadActive.listen(this._reloadActive.bind(this));
44 this.actions.service.reloadAll.listen(this._reloadAll.bind(this));
45 this.actions.service.reloadUpdatedServices.listen(this._reloadUpdatedServices.bind(this));
46 this.actions.service.reorder.listen(this._reorder.bind(this));
47 this.actions.service.toggleNotifications.listen(this._toggleNotifications.bind(this));
48 this.actions.service.openDevTools.listen(this._openDevTools.bind(this));
49 this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this));
50
51 this.registerReactions([
52 this._focusServiceReaction.bind(this),
53 this._getUnreadMessageCountReaction.bind(this),
54 this._mapActiveServiceToServiceModelReaction.bind(this),
55 this._saveActiveService.bind(this),
56 this._logoutReaction.bind(this),
57 ]);
58
59 // Just bind this
60 this._initializeServiceRecipeInWebview.bind(this);
61 }
62
63 @computed get all() {
64 if (this.stores.user.isLoggedIn) {
65 const services = this.allServicesRequest.execute().result;
66 if (services) {
67 return observable(services.slice().slice().sort((a, b) => a.order - b.order));
68 }
69 }
70
71 return [];
72 }
73
74 @computed get enabled() {
75 return this.all.filter(service => service.isEnabled);
76 }
77
78 @computed get filtered() {
79 return this.all.filter(service => service.name.toLowerCase().includes(this.filterNeedle.toLowerCase()));
80 }
81
82 @computed get active() {
83 return this.all.find(service => service.isActive);
84 }
85
86 @computed get activeSettings() {
87 const match = matchRoute('/settings/services/edit/:id', this.stores.router.location.pathname);
88 if (match) {
89 const activeService = this.one(match.id);
90 if (activeService) {
91 return activeService;
92 }
93
94 console.warn('Service not available');
95 }
96
97 return null;
98 }
99
100 one(id) {
101 return this.all.find(service => service.id === id);
102 }
103
104 async _showAddServiceInterface({ recipeId }) {
105 const recipesStore = this.stores.recipes;
106
107 if (recipesStore.isInstalled(recipeId)) {
108 console.debug('Recipe is installed');
109 this._redirectToAddServiceRoute(recipeId);
110 } else {
111 console.warn('Recipe is not installed');
112 // We access the RecipeStore action directly
113 // returns Promise instead of action
114 await this.stores.recipes._install({ recipeId });
115 this._redirectToAddServiceRoute(recipeId);
116 }
117 }
118
119 // Actions
120 @action async _createService({ recipeId, serviceData, redirect = true }) {
121 const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
122 const response = await this.createServiceRequest.execute(recipeId, data)._promise;
123
124 this.allServicesRequest.patch((result) => {
125 if (!result) return;
126 result.push(response.data);
127 });
128
129 this.actionStatus = response.status || [];
130
131 if (redirect) {
132 this.stores.router.push('/settings/recipes');
133 gaEvent('Service', 'create', recipeId);
134 }
135 }
136
137 @action async _createFromLegacyService({ data }) {
138 const { id } = data.recipe;
139 const serviceData = {};
140
141 if (data.name) {
142 serviceData.name = data.name;
143 }
144
145 if (data.team) {
146 serviceData.team = data.team;
147 }
148
149 if (data.team) {
150 serviceData.customUrl = data.customURL;
151 }
152
153 this.actions.service.createService({
154 recipeId: id,
155 serviceData,
156 redirect: false,
157 });
158
159 return 'hello world';
160 }
161
162 @action async _updateService({ serviceId, serviceData, redirect = true }) {
163 const service = this.one(serviceId);
164 const data = this._cleanUpTeamIdAndCustomUrl(service.recipe.id, serviceData);
165 const request = this.updateServiceRequest.execute(serviceId, data);
166
167 this.allServicesRequest.patch((result) => {
168 if (!result) return;
169 Object.assign(result.find(c => c.id === serviceId), serviceData);
170 });
171
172 await request._promise;
173 this.actionStatus = request.result.status;
174
175 if (redirect) {
176 this.stores.router.push('/settings/services');
177 gaEvent('Service', 'update', service.recipe.id);
178 }
179 }
180
181 @action async _deleteService({ serviceId, redirect }) {
182 const request = this.deleteServiceRequest.execute(serviceId);
183
184 if (redirect) {
185 this.stores.router.push(redirect);
186 }
187
188 this.allServicesRequest.patch((result) => {
189 remove(result, c => c.id === serviceId);
190 });
191
192 const service = this.one(serviceId);
193
194 await request._promise;
195 this.actionStatus = request.result.status;
196
197 gaEvent('Service', 'delete', service.recipe.id);
198 }
199
200 @action _setActive({ serviceId }) {
201 const service = this.one(serviceId);
202
203 this.all.forEach((s, index) => {
204 this.all[index].isActive = false;
205 });
206 service.isActive = true;
207 }
208
209 @action _setUnreadMessageCount({ serviceId, count }) {
210 const service = this.one(serviceId);
211
212 service.unreadDirectMessageCount = count.direct;
213 service.unreadIndirectMessageCount = count.indirect;
214 }
215
216 @action _setWebviewReference({ serviceId, webview }) {
217 const service = this.one(serviceId);
218
219 service.webview = webview;
220
221 if (!service.isAttached) {
222 service.initializeWebViewEvents(this);
223 service.initializeWebViewListener();
224 }
225
226 service.isAttached = true;
227 }
228
229 @action _focusService({ serviceId }) {
230 const service = this.one(serviceId);
231
232 if (service.webview) {
233 service.webview.focus();
234 }
235 }
236
237 @action _focusActiveService() {
238 if (this.stores.user.isLoggedIn) {
239 // TODO: add checks to not focus service when router path is /settings or /auth
240 const service = this.active;
241 if (service) {
242 this._focusService({ serviceId: service.id });
243 }
244 } else {
245 this.allServicesRequest.invalidate();
246 }
247 }
248
249 @action _toggleService({ serviceId }) {
250 const service = this.one(serviceId);
251
252 service.isEnabled = !service.isEnabled;
253 }
254
255 @action _handleIPCMessage({ serviceId, channel, args }) {
256 const service = this.one(serviceId);
257
258 if (channel === 'hello') {
259 this._initRecipePolling(service.id);
260 this._initializeServiceRecipeInWebview(serviceId);
261 } else if (channel === 'messages') {
262 this.actions.service.setUnreadMessageCount({
263 serviceId,
264 count: {
265 direct: args[0].direct,
266 indirect: args[0].indirect,
267 },
268 });
269 } else if (channel === 'notification') {
270 const options = args[0].options;
271 if (service.recipe.hasNotificationSound) {
272 Object.assign(options, {
273 silent: true,
274 });
275 }
276
277 if (service.isNotificationEnabled) {
278 this.actions.app.notify({
279 notificationId: args[0].notificationId,
280 title: args[0].title,
281 options,
282 serviceId,
283 });
284 }
285 } else if (channel === 'avatar') {
286 const url = args[0];
287 if (service.customIconUrl !== url) {
288 service.customIconUrl = url;
289
290 this.actions.service.updateService({
291 serviceId,
292 serviceData: {
293 customIconUrl: url,
294 },
295 redirect: false,
296 });
297 }
298 }
299 }
300
301 @action _sendIPCMessage({ serviceId, channel, args }) {
302 const service = this.one(serviceId);
303
304 service.webview.send(channel, args);
305 }
306
307 @action _openWindow({ event }) {
308 if (event.disposition !== 'new-window' && event.url !== 'about:blank') {
309 this.actions.app.openExternalUrl({ url: event.url });
310 }
311 }
312
313 @action _filter({ needle }) {
314 this.filterNeedle = needle;
315 }
316
317 @action _resetFilter() {
318 this.filterNeedle = null;
319 }
320
321 @action _reload({ serviceId }) {
322 const service = this.one(serviceId);
323 service.resetMessageCount();
324
325 service.webview.reload();
326 }
327
328 @action _reloadActive() {
329 if (this.active) {
330 const service = this.one(this.active.id);
331
332 this._reload({
333 serviceId: service.id,
334 });
335 }
336 }
337
338 @action _reloadAll() {
339 this.enabled.forEach(s => this._reload({
340 serviceId: s.id,
341 }));
342 }
343
344 @action _reloadUpdatedServices() {
345 this._reloadAll();
346 this.actions.ui.toggleServiceUpdatedInfoBar({ visible: false });
347 }
348
349 @action _reorder({ oldIndex, newIndex }) {
350 const oldEnabledSortIndex = this.all.indexOf(this.enabled[oldIndex]);
351 const newEnabledSortIndex = this.all.indexOf(this.enabled[newIndex]);
352
353
354 this.all.splice(newEnabledSortIndex, 0, this.all.splice(oldEnabledSortIndex, 1)[0]);
355
356 const services = {};
357 this.all.forEach((s, index) => {
358 services[this.all[index].id] = index;
359 });
360
361 this.reorderServicesRequest.execute(services);
362 this.allServicesRequest.patch((data) => {
363 data.forEach((s) => {
364 const service = s;
365
366 service.order = this.one(s.id).order;
367 });
368 });
369
370 this._reorderAnalytics();
371 }
372
373 @action _toggleNotifications({ serviceId }) {
374 const service = this.one(serviceId);
375
376 service.isNotificationEnabled = !service.isNotificationEnabled;
377
378 this.actions.service.updateService({
379 serviceId,
380 serviceData: service,
381 redirect: false,
382 });
383 }
384
385 @action _openDevTools({ serviceId }) {
386 const service = this.one(serviceId);
387
388 service.webview.openDevTools();
389 }
390
391 @action _openDevToolsForActiveService() {
392 const service = this.active;
393
394 if (service) {
395 service.webview.openDevTools();
396 } else {
397 console.warn('No service is active');
398 }
399 }
400
401 // Reactions
402 _focusServiceReaction() {
403 const service = this.active;
404 if (service) {
405 this.actions.service.focusService({ serviceId: service.id });
406 }
407 }
408
409 _saveActiveService() {
410 const service = this.active;
411
412 if (service) {
413 this.stores.settings.updateSettingsRequest.execute({
414 activeService: service.id,
415 });
416 }
417 }
418
419 _mapActiveServiceToServiceModelReaction() {
420 const { activeService } = this.stores.settings.all;
421 const services = this.enabled;
422 if (services.length) {
423 services.map(service => Object.assign(service, {
424 isActive: activeService ? activeService === service.id : services[0].id === service.id,
425 }));
426
427 // if (!services.active) {
428 //
429 // }
430 }
431 // else if (!activeService && services.length) {
432 // services[0].isActive = true;
433 // }
434 }
435
436 _getUnreadMessageCountReaction() {
437 const unreadDirectMessageCount = this.enabled
438 .map(s => s.unreadDirectMessageCount)
439 .reduce((a, b) => a + b, 0);
440
441 const unreadIndirectMessageCount = this.enabled
442 .filter(s => s.isIndirectMessageBadgeEnabled)
443 .map(s => s.unreadIndirectMessageCount)
444 .reduce((a, b) => a + b, 0);
445
446 this.actions.app.setBadge({
447 unreadDirectMessageCount,
448 unreadIndirectMessageCount,
449 });
450 }
451
452 _logoutReaction() {
453 if (!this.stores.user.isLoggedIn) {
454 this.actions.settings.remove({ key: 'activeService' });
455 this.allServicesRequest.invalidate().reset();
456 }
457 }
458
459 _cleanUpTeamIdAndCustomUrl(recipeId, data) {
460 const serviceData = data;
461 const recipe = this.stores.recipes.one(recipeId);
462
463 if (recipe.hasTeamId && recipe.hasCustomUrl && data.team && data.customUrl) {
464 delete serviceData.team;
465 }
466
467 return serviceData;
468 }
469
470 // Helper
471 _redirectToAddServiceRoute(recipeId) {
472 const route = `/settings/services/add/${recipeId}`;
473 this.stores.router.push(route);
474 }
475
476 _initializeServiceRecipeInWebview(serviceId) {
477 const service = this.one(serviceId);
478
479 if (service.webview) {
480 service.webview.send('initializeRecipe', service);
481 }
482 }
483
484 _initRecipePolling(serviceId) {
485 const service = this.one(serviceId);
486
487 const delay = 1000;
488
489 if (service) {
490 const loop = () => {
491 service.webview.send('poll');
492
493 setTimeout(loop, delay);
494 };
495
496 loop();
497 }
498 }
499
500 _reorderAnalytics = debounce(() => {
501 gaEvent('Service', 'order');
502 }, 5000);
503}
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js
new file mode 100644
index 000000000..816f545ee
--- /dev/null
+++ b/src/stores/SettingsStore.js
@@ -0,0 +1,55 @@
1import { ipcRenderer } from 'electron';
2import { action, computed, observable } from 'mobx';
3
4import Store from './lib/Store';
5import Request from './lib/Request';
6import CachedRequest from './lib/CachedRequest';
7import { gaEvent } from '../lib/analytics';
8
9export default class SettingsStore extends Store {
10 @observable allSettingsRequest = new CachedRequest(this.api.local, 'getSettings');
11 @observable updateSettingsRequest = new Request(this.api.local, 'updateSettings');
12 @observable removeSettingsKeyRequest = new Request(this.api.local, 'removeKey');
13
14 constructor(...args) {
15 super(...args);
16
17 // Register action handlers
18 this.actions.settings.update.listen(this._update.bind(this));
19 this.actions.settings.remove.listen(this._remove.bind(this));
20
21 // this.registerReactions([
22 // this._shareSettingsWithMainProcess.bind(this),
23 // ]);
24 }
25
26 setup() {
27 this.allSettingsRequest.execute();
28 this._shareSettingsWithMainProcess();
29 }
30
31 @computed get all() {
32 return this.allSettingsRequest.result || {};
33 }
34
35 @action async _update({ settings }) {
36 await this.updateSettingsRequest.execute(settings)._promise;
37 await this.allSettingsRequest.invalidate({ immediately: true });
38
39 this._shareSettingsWithMainProcess();
40
41 gaEvent('Settings', 'update');
42 }
43
44 @action async _remove({ key }) {
45 await this.removeSettingsKeyRequest.execute(key);
46 await this.allSettingsRequest.invalidate({ immediately: true });
47
48 this._shareSettingsWithMainProcess();
49 }
50
51 // Reactions
52 _shareSettingsWithMainProcess() {
53 ipcRenderer.send('settings', this.all);
54 }
55}
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js
new file mode 100644
index 000000000..cb45b88b5
--- /dev/null
+++ b/src/stores/UIStore.js
@@ -0,0 +1,34 @@
1import { action, observable } from 'mobx';
2
3import Store from './lib/Store';
4
5export default class UIStore extends Store {
6 @observable showServicesUpdatedInfoBar = false;
7
8 constructor(...args) {
9 super(...args);
10
11 // Register action handlers
12 this.actions.ui.openSettings.listen(this._openSettings.bind(this));
13 this.actions.ui.closeSettings.listen(this._closeSettings.bind(this));
14 this.actions.ui.toggleServiceUpdatedInfoBar.listen(this._toggleServiceUpdatedInfoBar.bind(this));
15 }
16
17 // Actions
18 @action _openSettings({ path = '/settings' }) {
19 const settingsPath = path !== '/settings' ? `/settings/${path}` : path;
20 this.stores.router.push(settingsPath);
21 }
22
23 @action _closeSettings(): void {
24 this.stores.router.push('/');
25 }
26
27 @action _toggleServiceUpdatedInfoBar({ visible }) {
28 let visibility = visible;
29 if (visibility === null) {
30 visibility = !this.showServicesUpdatedInfoBar;
31 }
32 this.showServicesUpdatedInfoBar = visibility;
33 }
34}
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
new file mode 100644
index 000000000..4927d615f
--- /dev/null
+++ b/src/stores/UserStore.js
@@ -0,0 +1,272 @@
1import { observable, computed, action } from 'mobx';
2import moment from 'moment';
3import jwt from 'jsonwebtoken';
4
5import Store from './lib/Store';
6import Request from './lib/Request';
7import CachedRequest from './lib/CachedRequest';
8import { gaEvent } from '../lib/analytics';
9
10// TODO: split stores into UserStore and AuthStore
11export default class UserStore extends Store {
12 BASE_ROUTE = '/auth';
13 WELCOME_ROUTE = `${this.BASE_ROUTE}/welcome`;
14 LOGIN_ROUTE = `${this.BASE_ROUTE}/login`;
15 LOGOUT_ROUTE = `${this.BASE_ROUTE}/logout`;
16 SIGNUP_ROUTE = `${this.BASE_ROUTE}/signup`;
17 PRICING_ROUTE = `${this.BASE_ROUTE}/signup/pricing`;
18 IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`;
19 INVITE_ROUTE = `${this.BASE_ROUTE}/signup/invite`;
20 PASSWORD_ROUTE = `${this.BASE_ROUTE}/password`;
21
22 @observable loginRequest = new Request(this.api.user, 'login');
23 @observable signupRequest = new Request(this.api.user, 'signup');
24 @observable passwordRequest = new Request(this.api.user, 'password');
25 @observable inviteRequest = new Request(this.api.user, 'invite');
26 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo');
27 @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo');
28 @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices');
29
30 @observable isImportLegacyServicesExecuting = false;
31 @observable isImportLegacyServicesCompleted = false;
32
33 @observable id;
34 @observable authToken = localStorage.getItem('authToken') || null;
35 @observable accountType;
36
37 @observable hasCompletedSignup = null;
38
39 @observable userData = {};
40
41 @observable actionStatus = [];
42
43 logoutReasonTypes = {
44 SERVER: 'SERVER',
45 };
46 @observable logoutReason = null;
47
48 constructor(...args) {
49 super(...args);
50
51 // Register action handlers
52 this.actions.user.login.listen(this._login.bind(this));
53 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this));
54 this.actions.user.logout.listen(this._logout.bind(this));
55 this.actions.user.signup.listen(this._signup.bind(this));
56 this.actions.user.invite.listen(this._invite.bind(this));
57 this.actions.user.update.listen(this._update.bind(this));
58 this.actions.user.resetStatus.listen(this._resetStatus.bind(this));
59 this.actions.user.importLegacyServices.listen(this._importLegacyServices.bind(this));
60
61 // Reactions
62 this.registerReactions([
63 this._requireAuthenticatedUser,
64 this._getUserData.bind(this),
65 ]);
66 }
67
68 // Routes
69 get loginRoute() {
70 return this.LOGIN_ROUTE;
71 }
72
73 get logoutRoute() {
74 return this.LOGOUT_ROUTE;
75 }
76
77 get signupRoute() {
78 return this.SIGNUP_ROUTE;
79 }
80
81 get pricingRoute() {
82 return this.PRICING_ROUTE;
83 }
84
85 get inviteRoute() {
86 return this.INVITE_ROUTE;
87 }
88
89 get importRoute() {
90 return this.IMPORT_ROUTE;
91 }
92
93 get passwordRoute() {
94 return this.PASSWORD_ROUTE;
95 }
96
97 // Data
98 @computed get isLoggedIn() {
99 return this.authToken !== null && this.authToken !== undefined;
100 }
101
102 // @computed get isTokenValid() {
103 // return this.authToken !== null && moment(this.tokenExpiry).isAfter(moment());
104 // }
105
106 @computed get isTokenExpired() {
107 if (!this.authToken) return false;
108
109 const { tokenExpiry } = this._parseToken(this.authToken);
110 return this.authToken !== null && moment(tokenExpiry).isBefore(moment());
111 }
112
113 @computed get data() {
114 this.getUserInfoRequest.execute();
115 return this.getUserInfoRequest.result || {};
116 }
117
118 @computed get legacyServices() {
119 this.getLegacyServicesRequest.execute();
120 return this.getLegacyServicesRequest.result || [];
121 }
122
123 // Actions
124 @action async _login({ email, password }) {
125 const authToken = await this.loginRequest.execute(email, password)._promise;
126 this._setUserData(authToken);
127
128 this.stores.router.push('/');
129
130 gaEvent('User', 'login');
131 }
132
133 @action async _signup({ firstname, lastname, email, password, accountType, company }) {
134 const authToken = await this.signupRequest.execute({
135 firstname,
136 lastname,
137 email,
138 password,
139 accountType,
140 company,
141 });
142
143 this.hasCompletedSignup = false;
144
145 this._setUserData(authToken);
146
147 this.stores.router.push(this.PRICING_ROUTE);
148
149 gaEvent('User', 'signup');
150 }
151
152 @action async _retrievePassword({ email }) {
153 const request = this.passwordRequest.execute(email);
154
155 await request._promise;
156 this.actionStatus = request.result.status || [];
157
158 gaEvent('User', 'retrievePassword');
159 }
160
161 @action _invite({ invites }) {
162 const data = invites.filter(invite => invite.email !== '');
163
164 this.inviteRequest.execute(data);
165
166 // we do not wait for a server response before redirecting the user
167 this.stores.router.push('/');
168
169 gaEvent('User', 'inviteUsers');
170 }
171
172 @action async _update({ userData }) {
173 const response = await this.updateUserInfoRequest.execute(userData)._promise;
174
175 this.getUserInfoRequest.patch(() => response.data);
176 this.actionStatus = response.status || [];
177
178 gaEvent('User', 'update');
179 }
180
181 @action _resetStatus() {
182 this.actionStatus = [];
183 }
184
185 @action _logout() {
186 localStorage.removeItem('authToken');
187 this.getUserInfoRequest.invalidate().reset();
188 this.authToken = null;
189 // this.data = {};
190 }
191
192 @action async _importLegacyServices({ services }) {
193 this.isImportLegacyServicesExecuting = true;
194
195 for (const service of services) {
196 this.actions.service.createFromLegacyService({
197 data: service,
198 });
199 await this.stores.services.createServiceRequest._promise; // eslint-disable-line
200 }
201
202 this.isImportLegacyServicesExecuting = false;
203 this.isImportLegacyServicesCompleted = true;
204 }
205
206 // This is a mobx autorun which forces the user to login if not authenticated
207 _requireAuthenticatedUser = () => {
208 if (this.isTokenExpired) {
209 this._logout();
210 }
211
212 const { router } = this.stores;
213 const currentRoute = router.location.pathname;
214 if (!this.isLoggedIn
215 && !currentRoute.includes(this.BASE_ROUTE)) {
216 router.push(this.WELCOME_ROUTE);
217 } else if (this.isLoggedIn
218 && currentRoute === this.LOGOUT_ROUTE) {
219 this.actions.user.logout();
220 router.push(this.LOGIN_ROUTE);
221 } else if (this.isLoggedIn
222 && currentRoute.includes(this.BASE_ROUTE)
223 && (this.hasCompletedSignup
224 || this.hasCompletedSignup === null)) {
225 this.stores.router.push('/');
226 }
227 };
228
229 // Reactions
230 async _getUserData() {
231 if (this.isLoggedIn) {
232 const data = await this.getUserInfoRequest.execute()._promise;
233
234 // We need to set the beta flag for the SettingsStore
235 this.actions.settings.update({
236 settings: {
237 beta: data.beta,
238 },
239 });
240 }
241 }
242
243 // Helpers
244 _parseToken(authToken) {
245 try {
246 const decoded = jwt.decode(authToken);
247
248 return ({
249 id: decoded.userId,
250 tokenExpiry: moment.unix(decoded.exp).toISOString(),
251 authToken,
252 });
253 } catch (err) {
254 console.error('AccessToken Invalid');
255
256 return false;
257 }
258 }
259
260 _setUserData(authToken) {
261 const data = this._parseToken(authToken);
262 if (data.authToken) {
263 localStorage.setItem('authToken', data.authToken);
264
265 this.authToken = data.authToken;
266 this.id = data.id;
267 } else {
268 this.authToken = null;
269 this.id = null;
270 }
271 }
272}
diff --git a/src/stores/index.js b/src/stores/index.js
new file mode 100644
index 000000000..2d99e3952
--- /dev/null
+++ b/src/stores/index.js
@@ -0,0 +1,34 @@
1import AppStore from './AppStore';
2import UserStore from './UserStore';
3import SettingsStore from './SettingsStore';
4import ServicesStore from './ServicesStore';
5import RecipesStore from './RecipesStore';
6import RecipePreviewsStore from './RecipePreviewsStore';
7import UIStore from './UIStore';
8import PaymentStore from './PaymentStore';
9import NewsStore from './NewsStore';
10import RequestStore from './RequestStore';
11import GlobalErrorStore from './GlobalErrorStore';
12
13export default (api, actions, router) => {
14 const stores = {};
15 Object.assign(stores, {
16 router,
17 app: new AppStore(stores, api, actions),
18 user: new UserStore(stores, api, actions),
19 settings: new SettingsStore(stores, api, actions),
20 services: new ServicesStore(stores, api, actions),
21 recipes: new RecipesStore(stores, api, actions),
22 recipePreviews: new RecipePreviewsStore(stores, api, actions),
23 ui: new UIStore(stores, api, actions),
24 payment: new PaymentStore(stores, api, actions),
25 news: new NewsStore(stores, api, actions),
26 requests: new RequestStore(stores, api, actions),
27 globalError: new GlobalErrorStore(stores, api, actions),
28 });
29 // Initialize all stores
30 Object.keys(stores).forEach((name) => {
31 if (stores[name] && stores[name].initialize) stores[name].initialize();
32 });
33 return stores;
34};
diff --git a/src/stores/lib/CachedRequest.js b/src/stores/lib/CachedRequest.js
new file mode 100644
index 000000000..c0c3d40a1
--- /dev/null
+++ b/src/stores/lib/CachedRequest.js
@@ -0,0 +1,106 @@
1// @flow
2import { action } from 'mobx';
3import { isEqual, remove } from 'lodash';
4import Request from './Request';
5
6export default class CachedRequest extends Request {
7 _apiCalls = [];
8 _isInvalidated = true;
9
10 execute(...callArgs) {
11 // Do not continue if this request is already loading
12 if (this._isWaitingForResponse) return this;
13
14 // Very simple caching strategy -> only continue if the call / args changed
15 // or the request was invalidated manually from outside
16 const existingApiCall = this._findApiCall(callArgs);
17
18 // Invalidate if new or different api call will be done
19 if (existingApiCall && existingApiCall !== this._currentApiCall) {
20 this._isInvalidated = true;
21 this._currentApiCall = existingApiCall;
22 } else if (!existingApiCall) {
23 this._isInvalidated = true;
24 this._currentApiCall = this._addApiCall(callArgs);
25 }
26
27 // Do not continue if this request is not invalidated (see above)
28 if (!this._isInvalidated) return this;
29
30 // This timeout is necessary to avoid warnings from mobx
31 // regarding triggering actions as side-effect of getters
32 setTimeout(action(() => {
33 this.isExecuting = true;
34 // Apply the previous result from this call immediately (cached)
35 if (existingApiCall) {
36 this.result = existingApiCall.result;
37 }
38 }), 0);
39
40 // Issue api call & save it as promise that is handled to update the results of the operation
41 this._promise = new Promise((resolve, reject) => {
42 this._api[this._method](...callArgs)
43 .then((result) => {
44 setTimeout(action(() => {
45 this.result = result;
46 if (this._currentApiCall) this._currentApiCall.result = result;
47 this.isExecuting = false;
48 this.isError = false;
49 this.wasExecuted = true;
50 this._isInvalidated = false;
51 this._isWaitingForResponse = false;
52 this._triggerHooks();
53 resolve(result);
54 }), 1);
55 return result;
56 })
57 .catch(action((error) => {
58 setTimeout(action(() => {
59 this.error = error;
60 this.isExecuting = false;
61 this.isError = true;
62 this.wasExecuted = true;
63 this._isWaitingForResponse = false;
64 this._triggerHooks();
65 reject(error);
66 }), 1);
67 }));
68 });
69
70 this._isWaitingForResponse = true;
71 return this;
72 }
73
74 invalidate(options = { immediately: false }) {
75 this._isInvalidated = true;
76 if (options.immediately && this._currentApiCall) {
77 return this.execute(...this._currentApiCall.args);
78 }
79 return this;
80 }
81
82 patch(modify) {
83 return new Promise((resolve) => {
84 setTimeout(action(() => {
85 const override = modify(this.result);
86 if (override !== undefined) this.result = override;
87 if (this._currentApiCall) this._currentApiCall.result = this.result;
88 resolve(this);
89 }), 0);
90 });
91 }
92
93 removeCacheForCallWith(...args) {
94 remove(this._apiCalls, c => isEqual(c.args, args));
95 }
96
97 _addApiCall(args) {
98 const newCall = { args, result: null };
99 this._apiCalls.push(newCall);
100 return newCall;
101 }
102
103 _findApiCall(args) {
104 return this._apiCalls.find(c => isEqual(c.args, args));
105 }
106}
diff --git a/src/stores/lib/Reaction.js b/src/stores/lib/Reaction.js
new file mode 100644
index 000000000..e9bc26d81
--- /dev/null
+++ b/src/stores/lib/Reaction.js
@@ -0,0 +1,22 @@
1// @flow
2import { autorun } from 'mobx';
3
4export default class Reaction {
5 reaction;
6 hasBeenStarted;
7 dispose;
8
9 constructor(reaction) {
10 this.reaction = reaction;
11 this.hasBeenStarted = false;
12 }
13
14 start() {
15 this.dispose = autorun(() => this.reaction());
16 this.hasBeenStarted = true;
17 }
18
19 stop() {
20 if (this.hasBeenStarted) this.dispose();
21 }
22}
diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js
new file mode 100644
index 000000000..4a6925cc5
--- /dev/null
+++ b/src/stores/lib/Request.js
@@ -0,0 +1,112 @@
1import { observable, action, computed } from 'mobx';
2import { isEqual } from 'lodash/fp';
3
4export default class Request {
5 static _hooks = [];
6
7 static registerHook(hook) {
8 Request._hooks.push(hook);
9 }
10
11 @observable result = null;
12 @observable error = null;
13 @observable isExecuting = false;
14 @observable isError = false;
15 @observable wasExecuted = false;
16
17 _promise = Promise;
18 _api = {};
19 _method = '';
20 _isWaitingForResponse = false;
21 _currentApiCall = null;
22
23 constructor(api, method) {
24 this._api = api;
25 this._method = method;
26 }
27
28 execute(...callArgs) {
29 // Do not continue if this request is already loading
30 if (this._isWaitingForResponse) return this;
31
32 if (!this._api[this._method]) {
33 throw new Error(`Missing method <${this._method}> on api object:`, this._api);
34 }
35
36 // This timeout is necessary to avoid warnings from mobx
37 // regarding triggering actions as side-effect of getters
38 setTimeout(action(() => {
39 this.isExecuting = true;
40 }), 0);
41
42 // Issue api call & save it as promise that is handled to update the results of the operation
43 this._promise = new Promise((resolve, reject) => {
44 this._api[this._method](...callArgs)
45 .then((result) => {
46 setTimeout(action(() => {
47 this.result = result;
48 if (this._currentApiCall) this._currentApiCall.result = result;
49 this.isExecuting = false;
50 this.isError = false;
51 this.wasExecuted = true;
52 this._isWaitingForResponse = false;
53 this._triggerHooks();
54 resolve(result);
55 }), 1);
56 return result;
57 })
58 .catch(action((error) => {
59 setTimeout(action(() => {
60 this.error = error;
61 this.isExecuting = false;
62 this.isError = true;
63 this.wasExecuted = true;
64 this._isWaitingForResponse = false;
65 this._triggerHooks();
66 reject(error);
67 }), 1);
68 }));
69 });
70
71 this._isWaitingForResponse = true;
72 this._currentApiCall = { args: callArgs, result: null };
73 return this;
74 }
75
76 reload() {
77 return this.execute(...this._currentApiCall.args);
78 }
79
80 isExecutingWithArgs(...args) {
81 return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args);
82 }
83
84 @computed get isExecutingFirstTime() {
85 return !this.wasExecuted && this.isExecuting;
86 }
87
88 then(...args) {
89 if (!this._promise) throw new Error('You have to call Request::execute before you can access it as promise');
90 return this._promise.then(...args);
91 }
92
93 catch(...args) {
94 if (!this._promise) throw new Error('You have to call Request::execute before you can access it as promise');
95 return this._promise.catch(...args);
96 }
97
98 _triggerHooks() {
99 Request._hooks.forEach(hook => hook(this));
100 }
101
102 reset() {
103 this.result = null;
104 this.isExecuting = false;
105 this.isError = false;
106 this.wasExecuted = false;
107 this._isWaitingForResponse = false;
108 this._promise = Promise;
109
110 return this;
111 }
112}
diff --git a/src/stores/lib/Store.js b/src/stores/lib/Store.js
new file mode 100644
index 000000000..873da7b37
--- /dev/null
+++ b/src/stores/lib/Store.js
@@ -0,0 +1,44 @@
1import { computed, observable } from 'mobx';
2import Reaction from './Reaction';
3
4export default class Store {
5 stores = {};
6 api = {};
7 actions = {};
8
9 _reactions = [];
10
11 // status implementation
12 @observable _status = null;
13 @computed get actionStatus() {
14 return this._status || [];
15 }
16 set actionStatus(status) {
17 this._status = status;
18 }
19
20 constructor(stores, api, actions) {
21 this.stores = stores;
22 this.api = api;
23 this.actions = actions;
24 }
25
26 registerReactions(reactions) {
27 reactions.forEach(reaction => this._reactions.push(new Reaction(reaction)));
28 }
29
30 setup() {}
31
32 initialize() {
33 this.setup();
34 this._reactions.forEach(reaction => reaction.start());
35 }
36
37 teardown() {
38 this._reactions.forEach(reaction => reaction.stop());
39 }
40
41 resetStatus() {
42 this._status = null;
43 }
44}
diff --git a/src/styles/animations.scss b/src/styles/animations.scss
new file mode 100644
index 000000000..1e49af207
--- /dev/null
+++ b/src/styles/animations.scss
@@ -0,0 +1,90 @@
1// FadeIn
2.fadeIn-appear {
3 opacity: 0.01;
4}
5
6.fadeIn-appear.fadeIn-appear-active {
7 opacity: 1;
8 transition: opacity 0.5s ease-out;
9}
10
11.fadeIn-enter {
12 opacity: 0.01;
13 transition: opacity 0.5s ease-out;
14}
15
16.fadeIn-leave {
17 opacity: 1;
18}
19
20.fadeIn-leave.fadeIn-leave-active {
21 opacity: 0.01;
22 transition: opacity 300ms ease-in;
23}
24
25// FadeIn Fast
26.fadeIn-fast-appear {
27 opacity: 0.01;
28}
29
30.fadeIn-fast-appear.fadeIn-fast-appear-active {
31 opacity: 1;
32 transition: opacity 0.25s ease-out;
33}
34
35.fadeIn-fast-enter {
36 opacity: 0.01;
37 transition: opacity 0.25s ease-out;
38}
39
40.fadeIn-fast-leave {
41 opacity: 1;
42}
43
44.fadeIn-fast-leave.fadeIn-fast-leave-active {
45 opacity: 0.01;
46 transition: opacity 0.25s ease-in;
47}
48
49// Slide down
50.slideDown-appear {
51 max-height: 0;
52 overflow-y: hidden;
53}
54
55.slideDown-appear.slideDown-appear-active {
56 max-height: 500px;
57 transition: max-height 0.5s ease-out;
58}
59
60.slideDown-enter {
61 max-height: 0;
62 transition: max-height 0.5s ease-out;
63}
64
65// Slide up
66.slideUp-appear {
67 transform: translateY(20px);
68 opacity: 0;
69}
70
71.slideUp-appear.slideUp-appear-active {
72 transform: translateY(0px);
73 opacity: 1;
74 transition: all 0.3s ease-out;
75}
76
77.slideUp-enter {
78 transform: translateY(20px);
79 opacity: 0;
80 transition: all 0.3s ease-out;
81}
82
83.slideUp-leave {
84 opacity: 1;
85}
86
87.slideUp-leave.slideUp-leave-active {
88 opacity: 0.01;
89 transition: opacity 300ms ease-in;
90}
diff --git a/src/styles/auth.scss b/src/styles/auth.scss
new file mode 100644
index 000000000..9ad71867c
--- /dev/null
+++ b/src/styles/auth.scss
@@ -0,0 +1,144 @@
1@import './config.scss';
2
3.auth {
4 display: flex;
5 justify-content: center;
6 background: $theme-brand-primary;
7
8 .auth__layout {
9 width: 100%;
10 &>div>span {
11 width: 100%;
12 }
13 // display: flex;
14 // align-items: center;
15 // justify-content: center;
16 // flex-direction: column;
17
18 // @media only screen and (max-height : 700px) {
19 // margin: 100px 0;
20 // }
21
22 &>div {
23 display: flex;
24 justify-content: center;
25 align-items: center;
26
27 &>span {
28 position: absolute;
29 }
30 }
31 }
32
33 .auth__container {
34 position: relative;
35 width: 350px;
36 height: auto;
37 margin: 40px auto 0 auto;
38 background: #FFF;
39 // padding: 20px;
40 border-radius: $theme-border-radius;
41 box-shadow: 0 0 50px rgba(black, 0.2);
42
43 &.auth__container--signup {
44 width: 450px;
45 // margin-left: auto;
46 // margin-right: auto;
47 }
48 }
49
50 .auth__logo {
51 display: block;
52 width: 150px;
53 height: auto;
54 margin: -105px auto 20px auto;
55 border-radius: $theme-border-radius;
56
57 &.auth__logo--sm {
58 border: 4px solid #FFF;
59 box-shadow: 0 0 6px rgba(black, 0.5);
60 border-radius: 100%;
61 }
62 }
63
64 .auth__form {
65 padding: 20px;
66
67 h1 {
68 text-align: center;
69 }
70 }
71
72 .auth__button {
73 width: 100%;
74
75 &.auth__button--skip {
76 margin: 10px auto 0;
77 }
78 }
79
80 .auth__links {
81 padding: 20px;
82 background: $theme-gray-lighter;
83 border-bottom-left-radius: $theme-border-radius;
84 border-bottom-right-radius: $theme-border-radius;
85
86 a {
87 display: block;
88 text-align: center;
89 color: $theme-gray;
90 margin-bottom: 8px;
91
92 &:last-of-type {
93 margin-bottom: 0;
94 }
95 }
96 }
97
98 .auth__adlk {
99 position: absolute;
100 right: 25px;
101 bottom: 15px;
102
103 img {
104 width: 65px;
105 }
106 }
107
108 .auth__letter {
109 margin-bottom: 30px;
110 }
111
112 .scroll-container {
113 z-index: 10;
114 }
115
116 .info-bar {
117 position: absolute;
118 }
119
120 &__scroll-container {
121 overflow: scroll;
122 width: 100%;
123 max-height: 100vh;
124 padding: 80px 0;
125 }
126
127 .available-services {
128 margin-bottom: 15px;
129 }
130
131 .unavailable-services {
132 margin: 15px 0;
133
134 p {
135 text-transform: capitalize;
136 }
137 }
138
139 .legal {
140 text-align: center;
141 margin-top: 20px;
142 color: $theme-gray-light;
143 }
144}
diff --git a/src/styles/badge.scss b/src/styles/badge.scss
new file mode 100644
index 000000000..d7dfaf783
--- /dev/null
+++ b/src/styles/badge.scss
@@ -0,0 +1,15 @@
1@import './config.scss';
2
3.badge {
4 font-size: 14px;
5 display: inline-block;
6 padding: 5px 10px;
7 border-radius: $theme-border-radius;
8 background: $theme-gray-lighter;
9
10 &.badge--primary,
11 &.badge--premium {
12 background: $theme-brand-primary;
13 color: #FFF;
14 }
15}
diff --git a/src/styles/button.scss b/src/styles/button.scss
new file mode 100644
index 000000000..c2dd91293
--- /dev/null
+++ b/src/styles/button.scss
@@ -0,0 +1,74 @@
1@import './config.scss';
2
3.franz-form {
4 .franz-form__button {
5 position: relative;
6 background: $theme-brand-primary;
7 display: block;
8 padding: 10px 20px;
9 color: #FFF;
10 border-radius: 3px;
11 transition: background 0.5s;
12 text-align: center;
13
14 &:hover {
15 background: darken($theme-brand-primary, 5%);
16 }
17
18 &:active {
19 transition: none;
20 background: lighten($theme-brand-primary, 5%);
21 }
22
23 &:disabled {
24 opacity: 0.2;
25 }
26
27 &.franz-form__button--secondary {
28 background: $theme-gray-lighter;
29 color: $theme-gray;
30
31 &:hover {
32 background: darken($theme-gray-lighter, 5%);
33 }
34
35 &:active {
36 background: lighten($theme-gray-lighter, 5%);
37 }
38 }
39
40 &.franz-form__button--danger {
41 background: $theme-brand-danger;
42
43 &:hover {
44 background: darken($theme-brand-danger, 5%);
45 }
46
47 &:active {
48 background: lighten($theme-brand-danger, 5%);
49 }
50 }
51
52 &.franz-form__button--inverted {
53 background: none;
54 padding: 10px 20px;
55 border: 2px solid $theme-brand-primary;
56 color: $theme-brand-primary;
57 transition: background 0.5s, color 0.5s;
58
59 &:hover {
60 background: darken($theme-brand-primary, 5%);
61 color: #FFF;
62 }
63 }
64
65 .loader {
66 position: relative;
67 width: 20px;
68 height: 12px;
69 z-index: 9999;
70 display: inline-block;
71 margin-right: 5px;
72 }
73 }
74}
diff --git a/src/styles/colors.scss b/src/styles/colors.scss
new file mode 100644
index 000000000..5d8302c28
--- /dev/null
+++ b/src/styles/colors.scss
@@ -0,0 +1,22 @@
1$theme-brand-primary: #3498db;
2$theme-brand-success: #5cb85c;
3$theme-brand-info: #5bc0de;
4$theme-brand-warning: #FF9F00;
5$theme-brand-danger: #d9534f;
6
7$theme-gray-dark: #373a3c;
8$theme-gray: #55595c;
9$theme-gray-light: #818a91;
10$theme-gray-lighter: #eceeef;
11$theme-gray-lightest: #f7f7f9;
12
13$theme-border-radius: 6px;
14$theme-border-radius-small: 3px;
15
16$theme-sidebar-width: 68px;
17
18$theme-text-color: $theme-gray-dark;
19
20$theme-transition-time: 0.5s;
21
22$theme-inset-shadow: inset 0 2px 5px rgba(0,0,0,0.03);
diff --git a/src/styles/config.scss b/src/styles/config.scss
new file mode 100644
index 000000000..7aa2d674f
--- /dev/null
+++ b/src/styles/config.scss
@@ -0,0 +1 @@
@import './colors.scss';
diff --git a/src/styles/content-tabs.scss b/src/styles/content-tabs.scss
new file mode 100644
index 000000000..aa3c8594b
--- /dev/null
+++ b/src/styles/content-tabs.scss
@@ -0,0 +1,52 @@
1@import './config.scss';
2
3.content-tabs {
4 .content-tabs__tabs {
5 display: flex;
6 border-top-left-radius: $theme-border-radius-small;
7 border-top-right-radius: $theme-border-radius-small;
8 overflow: hidden;
9
10 .content-tabs__item {
11 padding: 10px;
12 flex: 1;
13 // border: 1px solid $theme-gray-lightest;
14 color: $theme-gray-dark;
15 background: $theme-gray-lightest;
16 border-bottom: 1px solid $theme-gray-lighter;
17 box-shadow: inset 0px -3px 10px rgba(black, 0.05);
18 transition: all $theme-transition-time;
19
20 &.is-active {
21 background: $theme-brand-primary;
22 color: #FFF;
23 border-bottom: 1px solid $theme-brand-primary;
24 box-shadow: none;
25 }
26 }
27 }
28
29 .content-tabs__content {
30 padding: 20px 20px;
31 border-bottom-left-radius: $theme-border-radius-small;
32 border-bottom-right-radius: $theme-border-radius-small;
33 background: $theme-gray-lightest;
34
35 .content-tabs__item {
36 top: 0;
37 display: none;
38
39 &.is-active {
40 display: block;
41 }
42 }
43
44 .franz-form__input-wrapper {
45 background: #FFF;
46 }
47
48 .franz-form__field:last-of-type {
49 margin-bottom: 0;
50 }
51 }
52}
diff --git a/src/styles/fonts.scss b/src/styles/fonts.scss
new file mode 100644
index 000000000..bd96ea867
--- /dev/null
+++ b/src/styles/fonts.scss
@@ -0,0 +1,44 @@
1@import './config.scss';
2// @import './node_modules/mdi/scss/materialdesignicons.scss';
3
4@font-face {
5 font-family: 'Open Sans';
6 src: url('../assets/fonts/OpenSans-Light.ttf');
7 font-weight: 300;
8 font-style: normal;
9}
10
11@font-face {
12 font-family: 'Open Sans';
13 src: url('../assets/fonts/OpenSans-Regular.ttf');
14 font-weight: normal;
15 font-style: normal;
16}
17
18@font-face {
19 font-family: 'Open Sans';
20 src: url('../assets/fonts/OpenSans-Bold.ttf');
21 font-weight: bold;
22 font-style: normal;
23}
24
25@font-face {
26 font-family: 'Open Sans';
27 src: url('../assets/fonts/OpenSans-BoldItalic.ttf');
28 font-weight: bold;
29 font-style: italic;
30}
31
32@font-face {
33 font-family: 'Open Sans';
34 src: url('../assets/fonts/OpenSans-ExtraBold.ttf');
35 font-weight: 800;
36 font-style: normal;
37}
38
39@font-face {
40 font-family: 'Open Sans';
41 src: url('../assets/fonts/OpenSans-ExtraBoldItalic.ttf');
42 font-weight: 800;
43 font-style: italic;
44}
diff --git a/src/styles/info-bar.scss b/src/styles/info-bar.scss
new file mode 100644
index 000000000..c30c951ee
--- /dev/null
+++ b/src/styles/info-bar.scss
@@ -0,0 +1,79 @@
1@import './config.scss';
2
3.info-bar {
4 width: 100%;
5 height: 50px;
6 background: $theme-brand-primary;
7 display: flex;
8 align-items: center;
9 justify-content: center;
10 padding: 0 20px;
11 position: relative;
12 // bottom: 0;
13 z-index: 100;
14 box-shadow: 0 0 8px rgba(black, 0.2);
15
16 .info-bar__content {
17 height: auto;
18
19 .mdi {
20 margin-right: 5px;
21 }
22 }
23
24 .info-bar__close {
25 position: absolute;
26 right: 10px;
27 color: #FFF;
28 }
29
30 .info-bar__cta {
31 color: #FFF;
32 padding: 3px 8px;
33 border-radius: $theme-border-radius-small;
34 border-color: #FFF;
35 border-width: 2px;
36 border-style: solid;
37 margin-left: 15px;
38
39 .loader {
40 position: relative;
41 width: 20px;
42 height: 12px;
43 z-index: 9999;
44 display: inline-block;
45 margin-right: 5px;
46 }
47 }
48
49 &.info-bar--bottom {
50 order: 10;
51 }
52
53 &.info-bar--primary {
54 background: $theme-brand-primary;
55 color: #FFF;
56
57 a {
58 color: #FFF;
59 }
60 }
61
62 &.info-bar--warning {
63 background: $theme-brand-warning;
64 color: #FFF;
65
66 a {
67 color: #FFF;
68 }
69 }
70
71 &.info-bar--danger {
72 background: $theme-brand-danger;
73 color: #FFF;
74
75 a {
76 color: #FFF;
77 }
78 }
79}
diff --git a/src/styles/infobox.scss b/src/styles/infobox.scss
new file mode 100644
index 000000000..ad363314d
--- /dev/null
+++ b/src/styles/infobox.scss
@@ -0,0 +1,61 @@
1@import './config.scss';
2
3.infobox {
4 height: auto;
5 padding: 15px 20px;
6 margin-bottom: 30px;
7 border-radius: $theme-border-radius-small;
8 display: flex;
9 align-items: center;
10
11 a {
12 color: #FFF;
13 }
14
15 .infobox__content {
16 flex: 1;
17 }
18
19 &.infobox--success {
20 background: $theme-brand-success;
21 color: #FFF;
22 }
23
24 &.infobox--primary {
25 background: $theme-brand-primary;
26 color: #FFF;
27 }
28
29 &.infobox--danger {
30 background: $theme-brand-danger;
31 color: #FFF;
32 }
33
34 .mdi {
35 margin-right: 10px;
36 }
37
38 .infobox__cta {
39 color: #FFF;
40 padding: 3px 8px;
41 border-radius: $theme-border-radius-small;
42 border-color: #FFF;
43 border-width: 2px;
44 border-style: solid;
45 margin-left: 15px;
46
47 .loader {
48 position: relative;
49 width: 20px;
50 height: 12px;
51 z-index: 9999;
52 display: inline-block;
53 margin-right: 5px;
54 }
55 }
56
57 .infobox__delete {
58 color: #FFF;
59 margin-right: 0;
60 }
61}
diff --git a/src/styles/input.scss b/src/styles/input.scss
new file mode 100644
index 000000000..814dce5f8
--- /dev/null
+++ b/src/styles/input.scss
@@ -0,0 +1,99 @@
1@import './config.scss';
2@import './mixins.scss';
3
4.franz-form {
5 .franz-form__field {
6 display: flex;
7 flex: 1;
8 flex-direction: column;
9 margin-bottom: 20px;
10
11 &.has-error {
12 .franz-form__input-wrapper {
13 border-color: $theme-brand-danger;
14 }
15
16 .franz-form__input-modifier {
17 border-color: $theme-brand-danger;
18 }
19 }
20 }
21
22 .franz-form__label {
23 @include formLabel();
24 }
25
26 .franz-form__error {
27 color: $theme-brand-danger;
28 margin-top: 10px;
29 order: 2;
30 }
31
32 .franz-form__input-wrapper {
33 display: flex;
34 width: 100%;
35 order: 1;
36 border-radius: $theme-border-radius-small;
37 background: $theme-gray-lightest;
38 border: 1px solid $theme-gray-lighter;
39 flex-wrap: wrap;
40 }
41
42 .franz-form__input {
43 flex: 1;
44 border: 0;
45 background: none;
46 width: 100%;
47 padding: 8px;
48 // font-size: 18px;
49 color: $theme-gray;
50 }
51
52 .franz-form__input-prefix,
53 .franz-form__input-suffix {
54 padding: 0 10px;
55 background: $theme-gray-lighter;
56 color: $theme-gray-light;
57 line-height: 35px;
58 }
59
60 .franz-form__input-modifier {
61 padding: 0 20px;
62 border-left: 1px solid $theme-gray-lighter;
63 color: $theme-gray-light;
64 font-size: 20px;
65 }
66
67 .franz-form__password-score {
68 background: $theme-gray-lighter;
69 height: 5px;
70 flex-basis: 100%;
71 border-bottom-left-radius: 3px;
72 border-bottom-right-radius: 3px;
73
74 meter {
75 width: 100%;
76 height: 100%;
77 display: block;
78 border-bottom-left-radius: 3px;
79 border-bottom-right-radius: 3px;
80 overflow: hidden;
81
82 &::-webkit-meter-bar {
83 background: none;
84 }
85
86 &::-webkit-meter-even-less-good-value {
87 background: $theme-brand-danger;
88 }
89
90 &::-webkit-meter-suboptimum-value {
91 background: $theme-brand-warning;
92 }
93
94 &::-webkit-meter-optimum-value {
95 background: $theme-brand-success;
96 }
97 }
98 }
99}
diff --git a/src/styles/layout.scss b/src/styles/layout.scss
new file mode 100644
index 000000000..d87df2684
--- /dev/null
+++ b/src/styles/layout.scss
@@ -0,0 +1,141 @@
1@import './config.scss';
2
3html {
4 overflow: hidden;
5}
6
7.app {
8 display: flex;
9 flex-direction: row;
10
11 .app__service {
12 display: flex;
13 flex: 1;
14 flex-direction: column;
15 }
16}
17
18.window-draggable {
19 position: absolute;
20 width: 100%;
21 top: 0px;
22 left: 0px;
23 height: 35px;
24 pointer-events: none;
25 -webkit-app-region: drag;
26 z-index: 9999;
27}
28
29.darwin {
30 .sidebar {
31 padding-top: 23px;
32 }
33}
34
35.sidebar {
36 display: flex;
37 flex-direction: column;
38 align-items: center;
39 width: $theme-sidebar-width;
40 background: $theme-gray-lightest;
41 box-shadow: 1px 0 10px rgba(0,0,0,0.08);
42 z-index: 200;
43 text-align: center;
44 color: $theme-text-color;
45
46 .sidebar__add-service {
47 width: 32px;
48 height: 32px;
49 background: $theme-gray-lighter;
50 border-radius: $theme-border-radius-small;
51 margin: 10px auto;
52 color: $theme-gray-light;
53 }
54
55 .sidebar__settings-button {
56 height: auto;
57 padding: 20px 0;
58 font-size: 12px;
59 position: relative;
60
61 .emoji {
62 position: absolute;
63 top: 18px;
64 right: 12px;
65
66 img {
67 width: 18px;
68 }
69 }
70 }
71
72 .sidebar__logo {
73 width: 40px;
74 height: auto;
75 }
76
77 & > div {
78 display: flex;
79 overflow-y: scroll;
80
81 &::-webkit-scrollbar {
82 display: none;
83 }
84 }
85}
86
87.grid {
88 .grid__row {
89 display: flex;
90 flex-direction: row;
91
92 &>* {
93 margin-right: 20px;
94 }
95
96 & :last-child {
97 margin-right: 0;
98 }
99 }
100}
101
102.app-loader {
103 display: flex;
104 justify-content: center;
105 align-items: center;
106
107 .app-loader__title {
108 color: #FFF;
109 font-size: 40px;
110 }
111
112 &>span {
113 height: auto;
114 }
115}
116
117.dev-warning {
118 display: none;
119}
120
121.isDevMode {
122 .dev-warning {
123 display: block;
124 position: fixed;
125 background: $theme-brand-warning;
126 width: auto;
127 height: auto;
128 top: 5px;
129 right: 5px;
130 padding: 4px;
131 font-size: 10px;
132 color: #FFF;
133 z-index: 999999999;
134 border-radius: 3px;
135 transition: opacity 0.5s ease;
136
137 &:hover {
138 opacity: 0;
139 }
140 }
141}
diff --git a/src/styles/main.scss b/src/styles/main.scss
new file mode 100644
index 000000000..8afc86f98
--- /dev/null
+++ b/src/styles/main.scss
@@ -0,0 +1,36 @@
1$mdi-font-path: '../node_modules/mdi/fonts';
2@if $env == development {
3 $mdi-font-path: '../../node_modules/mdi/fonts';
4}
5
6@import './node_modules/mdi/scss/materialdesignicons.scss';
7
8// modules
9@import './reset.scss';
10@import './util.scss';
11@import './layout.scss';
12@import './tabs.scss';
13@import './services.scss';
14@import './settings.scss';
15@import './service-table.scss';
16@import './recipes.scss';
17@import './fonts.scss';
18@import './type.scss';
19@import './welcome.scss';
20@import './auth.scss';
21@import './tooltip.scss';
22@import './info-bar.scss';
23@import './animations.scss';
24@import './infobox.scss';
25@import './badge.scss';
26@import './subscription.scss';
27@import './subscription-popup.scss';
28@import './content-tabs.scss';
29
30// form
31@import './input.scss';
32@import './radio.scss';
33@import './toggle.scss';
34@import './button.scss';
35@import './searchInput.scss';
36@import './select.scss';
diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss
new file mode 100644
index 000000000..c9b1bc988
--- /dev/null
+++ b/src/styles/mixins.scss
@@ -0,0 +1,9 @@
1@import './config.scss';
2
3@mixin formLabel {
4 width: 100%;
5 color: $theme-gray-light;
6 display: block;
7 margin-bottom: 5px;
8 order: 0;
9}
diff --git a/src/styles/radio.scss b/src/styles/radio.scss
new file mode 100644
index 000000000..644478cd6
--- /dev/null
+++ b/src/styles/radio.scss
@@ -0,0 +1,34 @@
1@import './config.scss';
2
3.franz-form {
4 .franz-form__radio-wrapper {
5 display: flex;
6 }
7
8 .franz-form__radio {
9 // background: $theme-gray-lightest;
10 border: 2px solid $theme-gray-lighter;
11 color: $theme-gray;
12 padding: 11px;
13 margin-right: 20px;
14 text-align: center;
15 border-radius: $theme-border-radius-small;
16 flex: 1;
17 box-shadow: $theme-inset-shadow;
18 transition: background $theme-transition-time;
19
20 &:last-of-type {
21 margin-right: 0;
22 }
23
24 &.is-selected {
25 border: 2px solid $theme-brand-primary;
26 background: #FFF;
27 color: $theme-brand-primary;
28 }
29
30 input {
31 display: none;
32 }
33 }
34}
diff --git a/src/styles/recipes.scss b/src/styles/recipes.scss
new file mode 100644
index 000000000..017aa4fe2
--- /dev/null
+++ b/src/styles/recipes.scss
@@ -0,0 +1,72 @@
1@import './config.scss';
2
3.recipes {
4 .recipes__list {
5 display: flex;
6 flex-flow: row wrap;
7 align-content: flex-start;
8 min-height: 70%;
9 height: auto;
10
11 &.recipes__list--disabled {
12 opacity: 0.3;
13 filter: grayscale(100%);
14 pointer-events: none;
15 }
16 }
17
18 .recipes__navigation {
19 height: auto;
20 margin-bottom: 35px;
21
22 .badge {
23 margin-right: 10px;
24 }
25
26 &.recipes__navigation--disabled {
27 opacity: 0.3;
28 filter: grayscale(100%);
29 pointer-events: none;
30 }
31 }
32}
33
34.recipe-teaser {
35 position: relative;
36 width: calc(25% - 20px);
37 height: 120px;
38 margin: 0 20px 20px 0;
39 border-radius: $theme-border-radius;
40 background-color: $theme-gray-lightest;
41 transition: background $theme-transition-time;
42 overflow: hidden;
43
44 &:hover {
45 background-color: $theme-gray-lighter;
46 }
47
48 .recipe-teaser__icon {
49 width: 50px;
50 margin-bottom: 10px;
51 }
52
53 .recipe-teaser__label {
54 display: block;
55 }
56
57 h2 {
58 z-index: 10;
59 }
60
61 &__dev-badge {
62 position: absolute;
63 top: 5px;
64 right: -13px;
65 width: 50px;
66 background: $theme-brand-warning;
67 color: #FFF;
68 font-size: 10px;
69 transform: rotateZ(45deg);
70 box-shadow: 0 0 4px rgba(black, 0.2);
71 }
72}
diff --git a/src/styles/reset.scss b/src/styles/reset.scss
new file mode 100644
index 000000000..21763f44f
--- /dev/null
+++ b/src/styles/reset.scss
@@ -0,0 +1,95 @@
1@import './config.scss';
2
3/* ============ RESET =========== */
4/* http://meyerweb.com/eric/tools/css/reset */
5
6html, body, div, span, applet, object, iframe,
7h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8a, abbr, acronym, address, big, cite, code,
9del, dfn, em, img, ins, kbd, q, s, samp,
10small, strike, strong, sub, sup, tt, var,
11b, u, i, center,
12dl, dt, dd, ol, ul, li,
13fieldset, form, label, legend,
14table, caption, tbody, tfoot, thead, tr, th, td,
15article, aside, canvas, details, embed,
16figure, figcaption, footer, header, hgroup,
17menu, nav, output, ruby, section, summary,
18time, mark, audio, video {
19 margin: 0;
20 padding: 0;
21 border: 0;
22 font-size: 100%;
23 font: inherit;
24}
25/* HTML5 display-role reset for older browsers */
26article, aside, details, figcaption, figure,
27footer, header, hgroup, menu, nav, section {
28 display: block;
29}
30body {
31 line-height: 1;
32}
33ol, ul {
34 list-style: none;
35}
36blockquote, q {
37 quotes: none;
38}
39blockquote:before, blockquote:after, q:before, q:after {
40 content: '';
41 content: none;
42}
43table {
44 border-collapse: collapse;
45 border-spacing: 0;
46}
47
48/* Buttons should not have any special style applied by default */
49button {
50 background: none;
51 border: none;
52 padding: 0;
53}
54
55button:focus {
56 outline: 0;
57}
58
59html {
60 /* base for rem / 1rem = 10px */
61 font-size: 62.5%;
62 font-family: 'Open Sans';
63}
64
65body {
66 /* default font size = 14px */
67 font-size: 1.4rem;
68 color: $theme-gray-dark;
69}
70
71* {
72 -webkit-font-smoothing: antialiased;
73 box-sizing: border-box;
74 font-size: 1.4rem;
75 font-family: 'Open Sans';
76 -webkit-user-select: none;
77}
78
79html, body, div {
80 height: 100%;
81 background: none;
82 box-sizing: border-box;
83}
84
85*:focus {
86 outline: none;
87}
88
89img {
90 pointer-events: none;
91}
92
93a {
94 cursor: default;
95}
diff --git a/src/styles/searchInput.scss b/src/styles/searchInput.scss
new file mode 100644
index 000000000..28ff09fc4
--- /dev/null
+++ b/src/styles/searchInput.scss
@@ -0,0 +1,4 @@
1.search-input {
2 width: 100%;
3 height: auto;
4}
diff --git a/src/styles/select.scss b/src/styles/select.scss
new file mode 100644
index 000000000..965b4321a
--- /dev/null
+++ b/src/styles/select.scss
@@ -0,0 +1,19 @@
1@import './config.scss';
2@import './mixins.scss';
3
4$toggle: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0ic3ZnMiIKICAgdmlld0JveD0iMCAwIDM1Ljk3MDk4MyAyMy4wOTE1MTgiCiAgIGhlaWdodD0iNi41MTY5Mzk2bW0iCiAgIHdpZHRoPSIxMC4xNTE4MTFtbSI+CiAgPGRlZnMKICAgICBpZD0iZGVmczQiIC8+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgICA8ZGM6dGl0bGU+PC9kYzp0aXRsZT4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAyLjAxNDUxLC00MDcuMTIyMjUpIgogICAgIGlkPSJsYXllcjEiPgogICAgPHRleHQKICAgICAgIGlkPSJ0ZXh0MzMzNiIKICAgICAgIHk9IjYyOS41MDUwNyIKICAgICAgIHg9IjI5MS40Mjg1NiIKICAgICAgIHN0eWxlPSJmb250LXN0eWxlOm5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zaXplOjQwcHg7bGluZS1oZWlnaHQ6MTI1JTtmb250LWZhbWlseTpzYW5zLXNlcmlmO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iNjI5LjUwNTA3IgogICAgICAgICB4PSIyOTEuNDI4NTYiCiAgICAgICAgIGlkPSJ0c3BhbjMzMzgiPjwvdHNwYW4+PC90ZXh0PgogICAgPGcKICAgICAgIGlkPSJ0ZXh0MzM0MCIKICAgICAgIHN0eWxlPSJmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2ZvbnQtc2l6ZTo0MHB4O2xpbmUtaGVpZ2h0OjEyNSU7Zm9udC1mYW1pbHk6Rm9udEF3ZXNvbWU7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpGb250QXdlc29tZTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIj4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGgzMzQ1IgogICAgICAgICBzdHlsZT0iZmlsbDojMzMzMzMzO2ZpbGwtb3BhY2l0eToxIgogICAgICAgICBkPSJtIDIzNy41NjY5Niw0MTMuMjU1MDcgYyAwLjU1ODA0LC0wLjU1ODA0IDAuNTU4MDQsLTEuNDczMjIgMCwtMi4wMzEyNSBsIC0zLjcwNTM1LC0zLjY4MzA0IGMgLTAuNTU4MDQsLTAuNTU4MDQgLTEuNDUwOSwtMC41NTgwNCAtMi4wMDg5MywwIEwgMjIwLDQxOS4zOTM0NiAyMDguMTQ3MzIsNDA3LjU0MDc4IGMgLTAuNTU4MDMsLTAuNTU4MDQgLTEuNDUwODksLTAuNTU4MDQgLTIuMDA4OTMsMCBsIC0zLjcwNTM1LDMuNjgzMDQgYyAtMC41NTgwNCwwLjU1ODAzIC0wLjU1ODA0LDEuNDczMjEgMCwyLjAzMTI1IGwgMTYuNTYyNSwxNi41NDAxNyBjIDAuNTU4MDMsMC41NTgwNCAxLjQ1MDg5LDAuNTU4MDQgMi4wMDg5MiwwIGwgMTYuNTYyNSwtMTYuNTQwMTcgeiIgLz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=";
5
6.franz-form {
7 .franz-form__select {
8 -webkit-appearance: none;
9 min-width: 200px;
10 padding: 10px;
11 background-color: $theme-gray-lightest;
12 background-position: right center;
13 background-repeat: no-repeat;
14 background-size: 1ex;
15 background-origin: content-box;
16 background-image: url(data:image/svg+xml;base64,#{$toggle});
17 border: 1px solid $theme-gray-lighter;
18 }
19}
diff --git a/src/styles/service-table.scss b/src/styles/service-table.scss
new file mode 100644
index 000000000..66d5ac941
--- /dev/null
+++ b/src/styles/service-table.scss
@@ -0,0 +1,62 @@
1@import './config.scss';
2
3.service-table {
4 width: 100%;
5
6 .service-table__toggle {
7 width: 60px;
8
9 .franz-form__field {
10 margin-bottom: 0;
11 }
12 }
13
14 .service-table__icon {
15 width: 35px;
16
17 &.has-custom-icon {
18 border-radius: $theme-border-radius;
19 border: 1px solid $theme-gray-lighter;
20 width: 37px;
21 }
22 }
23
24 .service-table__column-icon {
25 width: 40px;
26 }
27
28 .service-table__column-action {
29 width: 40px
30 }
31
32 .service-table__column-info {
33 width: 40px;
34
35 .mdi {
36 display: block;
37 font-size: 18px;
38 color: $theme-gray-light;
39 }
40 }
41
42 .service-table__row {
43 border-bottom: 1px solid $theme-gray-lightest;
44
45 &:hover {
46 background: $theme-gray-lightest;
47 }
48
49 &.service-table__row--disabled {
50 color: $theme-gray-light;
51
52 .service-table__column-icon {
53 filter: grayscale(100%);
54 opacity: 0.5;
55 }
56 }
57 }
58
59 td {
60 padding: 10px;
61 }
62}
diff --git a/src/styles/services.scss b/src/styles/services.scss
new file mode 100644
index 000000000..3347ea9d7
--- /dev/null
+++ b/src/styles/services.scss
@@ -0,0 +1,60 @@
1@import './config.scss';
2
3.services {
4 flex: 1;
5 height: 100%;
6 position: relative;
7 overflow: hidden;
8 background: #FFF;
9 order: 5;
10
11 .services__webview {
12 position: absolute;
13 width: 100%;
14 top: 0;
15 left: 0;
16 z-index: 0;
17
18 webview {
19 display: inline-flex;
20 width: 0px;
21 height: 0px;
22 }
23
24 &.is-active {
25 z-index: 100;
26
27 webview {
28 flex: 0 1;
29 width: 100%;
30 height: 100%;
31 }
32 }
33
34 &--force-repaint {
35 webview {
36 z-index: 5;
37 }
38 }
39 }
40
41 .services__no-service {
42 display: flex;
43 flex-direction: column;
44 justify-content: center;
45 align-items: center;
46 text-align: center;
47 background: $theme-gray-lighter;
48
49 h1 {
50 margin: 25px 0 40px;
51 color: $theme-gray-dark;
52 }
53
54 a.button {
55 margin-top: 40px;
56 // color: #FFF;
57 // border-color: #FFF;
58 }
59 }
60}
diff --git a/src/styles/settings.scss b/src/styles/settings.scss
new file mode 100644
index 000000000..9b19deb4e
--- /dev/null
+++ b/src/styles/settings.scss
@@ -0,0 +1,392 @@
1@import './config.scss';
2
3%headline {
4 font-size: 20px;
5 font-weight: 400;
6 letter-spacing: -1px;
7 color: $theme-gray-light;
8
9 a {
10 color: $theme-gray-light;
11 }
12}
13
14.settings-wrapper {
15 background: rgba(black, 0.5);
16 position: absolute;
17 width: 100%;
18 height: 100%;
19 top: 0;
20 left: 0;
21 z-index: 9998;
22 display: flex;
23 justify-content: center;
24 align-items: center;
25 padding: 25px;
26
27 .settings-wrapper__action {
28 position: absolute;
29 width: 100%;
30 height: 100%;
31 top: 0;
32 left: 0;
33 }
34}
35
36.settings {
37 position: relative;
38 display: flex;
39 height: 100%;
40 width: 100%;
41 max-width: 900px;
42 min-height: 400px;
43 max-height: 600px;
44 z-index: 9999;
45 background: #FFF;
46 border-radius: $theme-border-radius;
47 box-shadow: 0 20px 50px rgba(black, 0.5);
48 overflow: hidden;
49 // margin-top: -10%;
50
51 .settings__main {
52 flex: 1;
53 display: flex;
54 flex-direction: column;
55 height: auto;
56 }
57
58 .settings__header {
59 display: flex;
60 align-items: center;
61 width: calc(100% - 60px);
62 height: 50px;
63 padding: 0 40px;
64 background: $theme-gray-lighter;
65
66 h1 {
67 @extend %headline;
68 margin: 0;
69 }
70
71 .settings__header-item {
72 @extend %headline;
73 }
74
75 .separator {
76 height: 100%;
77 margin: 0 15px;
78 border-right: 1px solid darken($theme-gray-lighter, 10%);
79 transform: skew(15deg) rotate(2deg);
80 }
81
82 .mdi {
83 color: $theme-gray-light;
84 }
85 }
86
87 .settings__body {
88 flex: 1;
89 padding: 25px 15px 15px 25px;
90 margin: 15px;
91 overflow-y: scroll;
92
93 &::-webkit-scrollbar {
94 width: 8px;
95 }
96
97 /* Track */
98 &::-webkit-scrollbar-track {
99 -webkit-border-radius: 10px;
100 border-radius: 10px;
101 background: none;
102 }
103
104 /* Handle */
105 &::-webkit-scrollbar-thumb {
106 -webkit-border-radius: 10px;
107 border-radius: 10px;
108 background: $theme-gray-lighter;
109 }
110
111 &::-webkit-scrollbar-thumb:window-inactive {
112 background: none;
113 }
114 }
115
116 .settings__close {
117 position: absolute;
118 right: 0;
119 background: $theme-gray-lighter;
120 height: 50px;
121 padding: 0 20px;
122 font-size: 20px;
123 border-left: 1px solid darken($theme-gray-lighter, 5%);
124 color: $theme-gray-light;
125 transition: background $theme-transition-time;
126
127 &:hover {
128 background: darken($theme-gray-lighter, 5%);
129 }
130 }
131
132 .settings__search-header {
133 display: flex;
134 align-items: center;
135 padding: 0 10px;
136 border-radius: $theme-border-radius;
137 transition: background $theme-transition-time;
138 @extend %headline;
139 font-size: 22px;
140
141 &:hover {
142 background: darken($theme-gray-lighter, 5%);
143 }
144
145 input {
146 padding-left: 10px;
147 background: none;
148 border: 0;
149 flex: 1;
150 @extend %headline;
151 }
152 }
153
154 .settings__options {
155 margin-top: 30px;
156 }
157
158 .settings__message {
159 display: flex;
160 margin-top: 40px;
161 padding-top: 15px;
162 border-top: 1px solid $theme-gray-lighter;
163 color: $theme-gray-light;
164
165 .mdi {
166 color: $theme-gray-light;
167 font-size: 20px;
168 margin-right: 10px;
169 }
170 }
171
172 .settings__indirect-message-help {
173 margin: -10px 0 20px 55px;;
174 font-size: 12px;
175 color: $theme-gray-light;
176
177 &:last-of-type {
178 margin-bottom: 30px;
179 }
180 }
181
182 .settings__controls {
183 display: flex;
184 justify-content: space-between;
185 padding: 10px 20px;
186 height: auto;
187 background: $theme-gray-lighter;
188
189 .franz-form__button {
190 &[type='submit'] {
191 margin-left: auto;
192 }
193
194 &.franz-form__button--secondary {
195 background: $theme-gray-light;
196 }
197 }
198 }
199
200 .settings__delete-button {
201 right: 0;
202 }
203
204 .settings__empty-state {
205 width: 100%;
206 height: auto;
207 min-height: 70%;
208 text-align: center;
209 align-self: center;
210 // margin-top: -20px;
211 align-items: center;
212
213 a.button {
214 margin-top: 40px;
215 }
216 }
217
218 .account {
219 height: auto;
220 // padding: 20px;
221
222 .account__box {
223 background: $theme-gray-lightest;
224 border-radius: $theme-border-radius;
225 padding: 20px;
226 margin-bottom: 40px;
227 align-items: center;
228
229 &.account__box--flex {
230 display: flex;
231 }
232
233 &.account__box--last {
234 margin-bottom: 0;
235 }
236
237 .auth__button {
238 width: 100%;
239 margin-top: 10px;
240 }
241 }
242
243 .account__avatar {
244 margin-right: 20px;
245 position: relative;
246
247 .emoji img {
248 width: 30px;
249 }
250 }
251
252 .account__avatar-premium {
253 position: absolute;
254 top: 2px;
255 right: 2px;
256 font-size: 26px;
257 }
258
259 .account__info {
260 flex: 1;
261
262 h2 {
263 margin-bottom: 5px;
264 }
265
266 .badge {
267 margin-top: 5px;
268 }
269 }
270
271 .account__subscription {
272 display: flex;
273 align-items: center;
274
275 .badge {
276 margin-left: 10px;
277 }
278 }
279
280 .account__subscription-button {
281 margin-left: auto;
282 }
283
284 div {
285 height: auto;
286 }
287
288 .invoices {
289 width: 100%;
290
291 td {
292 padding: 15px 0;
293 border-bottom: 1px solid $theme-gray-lighter;
294 }
295
296 tr:last-of-type td {
297 border: 0;
298 padding-bottom: 0;
299 }
300
301 .invoices__action {
302 text-align: right;
303
304 button {
305 color: $theme-brand-primary;
306 }
307 }
308 }
309 }
310
311 // @include element(add-service-teaser) {
312 // height: auto;
313 // margin-top: 20px;
314 // display: block;
315 // text-align: center;
316 // }
317 .emoji {
318 display: block;
319 font-size: 40px;
320 margin-bottom: 20px;
321
322 img {
323 width: 40px;
324 }
325 }
326
327 .premium-info {
328 background: lighten($theme-brand-primary, 40%);
329 padding: 20px;
330 border-radius: $theme-border-radius;
331 }
332
333 .content-tabs .premium-info {
334 background: none;
335 padding: 0;
336 }
337}
338
339.settings-navigation {
340 width: 200px;
341 height: auto;
342 background: $theme-gray-lightest;
343 display: flex;
344 flex-direction: column;
345
346 .settings-navigation__link {
347 display: block;
348 height: 50px;
349 line-height: 50px;
350 text-decoration: none;
351 color: $theme-text-color;
352 padding: 0 20px;
353 transition: background $theme-transition-time, color $theme-transition-time;
354
355 &:hover {
356 background: darken($theme-gray-lightest, 5%);
357
358 .badge {
359 background: #FFF;
360 }
361 }
362
363 &.is-active {
364 background: $theme-brand-primary;
365 color: #FFF;
366
367 .badge {
368 background: #FFF;
369 color: $theme-brand-primary;
370 }
371 }
372 }
373
374 .settings-navigation__expander {
375 flex: 1;
376 }
377
378 .badge {
379 transition: background $theme-transition-time, color $theme-transition-time;
380 display: initial;
381 margin-left: 5px;
382 }
383
384 .settings-navigation__action-badge {
385 display: inline-block;
386 width: 7px;
387 height: 7px;
388 background: $theme-brand-danger;
389 border-radius: 100%;
390 margin-left: 5px;
391 }
392}
diff --git a/src/styles/subscription-popup.scss b/src/styles/subscription-popup.scss
new file mode 100644
index 000000000..b6f232fcb
--- /dev/null
+++ b/src/styles/subscription-popup.scss
@@ -0,0 +1,20 @@
1.subscription-popup {
2 height: 100%;
3
4 &__content {
5 height: calc(100% - 60px);
6 }
7
8 &__webview {
9 height: 100%;
10 }
11
12 &__toolbar {
13 height: 60px;
14 background: $theme-gray-lightest;
15 display: flex;
16 justify-content: space-between;
17 padding: 10px;
18 border-top: 1px solid $theme-gray-lighter;
19 }
20}
diff --git a/src/styles/subscription.scss b/src/styles/subscription.scss
new file mode 100644
index 000000000..63183f085
--- /dev/null
+++ b/src/styles/subscription.scss
@@ -0,0 +1,72 @@
1.subscription {
2 .subscription__premium-features {
3 margin: 10px 0;
4
5 li {
6 height: 30px;
7 align-items: center;
8 display: flex;
9
10 &:before {
11 content: "👍";
12 margin-right: 10px;
13 }
14
15 .badge {
16 margin-left: 10px;
17 }
18 }
19 }
20
21 .subscription__premium-info {
22 margin: 15px 0 25px;
23 }
24}
25
26.paymentTiers {
27 .franz-form__radio-wrapper {
28 flex-flow: wrap;
29
30 .franz-form__radio {
31 width: 32%;
32 flex: initial;
33 margin-right: 2%;
34
35 &:nth-child(3) {
36 margin-right: 0;
37 }
38
39 &:last-of-type {
40 margin-right: 0;
41 margin-top: 2%;
42 width: 100%;
43 }
44 }
45 }
46}
47
48.settings {
49 .paymentTiers {
50 .franz-form__radio-wrapper {
51 .franz-form__radio {
52 width: 49%;
53
54 &:nth-child(2) {
55 margin-right: 0;
56 }
57
58 &:last-of-type {
59 width: 100%;
60 }
61 }
62 }
63 }
64}
65
66.mining-details {
67 margin-bottom: 15px;
68
69 button {
70 color: $theme-brand-primary;
71 }
72}
diff --git a/src/styles/tabs.scss b/src/styles/tabs.scss
new file mode 100644
index 000000000..75568898b
--- /dev/null
+++ b/src/styles/tabs.scss
@@ -0,0 +1,72 @@
1@import './config.scss';
2
3.tabs {
4 display: flex;
5 // flex: 1;
6 flex-direction: column;
7 flex-shrink: 1;
8 // align-items: center;
9 // height: auto;
10
11 .placeholder {
12 width: 100%;
13 height: 40px;
14 }
15}
16
17.tab-item {
18 display: flex;
19 justify-content: center;
20 align-items: center;
21 position: relative;
22 width: $theme-sidebar-width;
23 height: $theme-sidebar-width;
24 min-height: 50px;
25 transition: background $theme-transition-time;
26
27 &.is-active {
28 border-left: 4px solid $theme-brand-primary;
29 background: lighten($theme-brand-primary, 35%);
30
31 .tab-item__icon {
32 margin-left: -4px;
33 }
34 }
35
36 &.has-custom-icon {
37 .tab-item__icon {
38 border-radius: $theme-border-radius;
39 // border: 1px solid $theme-gray-lighter;
40 // width: 32px;
41 }
42 }
43
44 .tab-item__icon {
45 width: 30px;
46 height: auto;
47 }
48
49 .tab-item__message-count {
50 min-width: 17px;
51 min-height: 17px;
52 background: $theme-brand-danger;
53 color: #FFF;
54 border-radius: 20px;
55 padding: 0px 5px;
56 font-size: 11px;
57 position: absolute;
58 right: 5px;
59 bottom: 5px;
60 display: flex;
61 justify-content: center;
62 align-items: center;
63
64 &.is-indirect {
65 padding-top: 0px;
66 }
67 }
68
69 &.is-reordering {
70 z-index: 99999;
71 }
72}
diff --git a/src/styles/toggle.scss b/src/styles/toggle.scss
new file mode 100644
index 000000000..5b47e6495
--- /dev/null
+++ b/src/styles/toggle.scss
@@ -0,0 +1,47 @@
1@import './config.scss';
2
3$toggle-size: 14px;
4$toggle-width: 40px;
5$toggle-button-size: 22px;
6
7.franz-form {
8 .franz-form__toggle-wrapper {
9 display: flex;
10 flex-direction: row;
11
12 .franz-form__label {
13 margin-left: 20px;
14 }
15
16 .franz-form__toggle {
17 width: $toggle-width;
18 height: $toggle-size;
19 position: relative;
20 background: $theme-gray-lighter;
21 border-radius: $theme-border-radius;
22
23 .franz-form__toggle-button {
24 position: absolute;
25 left: 0;
26 top: -($toggle-button-size - $toggle-size) / 2;
27 width: $toggle-button-size;
28 height: $toggle-button-size;
29 background: $theme-gray-light;
30 border-radius: 100%;
31 transition: all 0.5s;
32 box-shadow: 0 1px 4px rgba(0,0,0,0.3);
33 }
34
35 &.is-active {
36 .franz-form__toggle-button {
37 left: $toggle-width - $toggle-button-size;
38 background: $theme-brand-primary;
39 }
40 }
41
42 input {
43 display: none;
44 }
45 }
46 }
47}
diff --git a/src/styles/tooltip.scss b/src/styles/tooltip.scss
new file mode 100644
index 000000000..1194e7fbb
--- /dev/null
+++ b/src/styles/tooltip.scss
@@ -0,0 +1,4 @@
1.__react_component_tooltip {
2 padding: 10px !important;
3 height: auto;
4}
diff --git a/src/styles/type.scss b/src/styles/type.scss
new file mode 100644
index 000000000..935a36f4b
--- /dev/null
+++ b/src/styles/type.scss
@@ -0,0 +1,73 @@
1@import './config.scss';
2@import './mixins.scss';
3
4h1 {
5 font-size: 30px;
6 font-weight: 300;
7 letter-spacing: -1px;
8 margin-bottom: 25px;
9}
10
11h2 {
12 font-size: 20px;
13 font-weight: 500;
14 letter-spacing: -1px;
15 margin-bottom: 25px;
16 margin-top: 55px;
17
18 &:first-of-type {
19 margin-top: 0;
20 }
21}
22
23p {
24 margin-bottom: 10px;
25 line-height: 1.7rem;
26
27 &:last-of-type {
28 margin-bottom: 0;
29 }
30}
31
32strong {
33 font-weight: bold;
34}
35
36a {
37 text-decoration: none;
38 color: $theme-text-color;
39
40 &.button {
41 position: relative;
42 background: none;
43 display: inline-block;
44 padding: 10px 20px;
45 border: 2px solid $theme-brand-primary;
46 color: $theme-brand-primary;
47 border-radius: 3px;
48 transition: background 0.5s, color 0.5s;
49 text-align: center;
50
51 &:hover {
52 background: darken($theme-brand-primary, 5%);
53 color: #FFF;
54 }
55 }
56
57 &.link {
58 color: $theme-brand-primary;
59 }
60}
61
62.error-message, .error-message:last-of-type {
63 margin: 10px 0;
64 color: $theme-brand-danger;
65}
66
67.center {
68 text-align: center;
69}
70
71.label {
72 @include formLabel();
73}
diff --git a/src/styles/util.scss b/src/styles/util.scss
new file mode 100644
index 000000000..3faad8db3
--- /dev/null
+++ b/src/styles/util.scss
@@ -0,0 +1,20 @@
1.scroll-container {
2 height: 100%;
3 flex: 1;
4 overflow-y: scroll;
5 overflow-x: hidden;
6}
7
8.loader {
9 position: relative;
10 z-index: 9999;
11 display: block;
12 width: 100%;
13 height: 40px;
14}
15
16.align-middle {
17 display: flex;
18 flex-direction: column;
19 justify-content: center;
20}
diff --git a/src/styles/welcome.scss b/src/styles/welcome.scss
new file mode 100644
index 000000000..5365921fb
--- /dev/null
+++ b/src/styles/welcome.scss
@@ -0,0 +1,75 @@
1.auth {
2 .welcome {
3
4 &__content {
5 display: flex;
6 align-items: center;
7 justify-content: center;
8 color: #FFF;
9 }
10
11 &__logo {
12 width: 100px;
13 }
14
15 &__text {
16 margin-left: 40px;
17 padding-left: 40px;
18 border-left: 1px solid #FFF;
19
20 h1 {
21 font-size: 60px;
22 letter-spacing: -0.4rem;
23 margin-bottom: 5px;
24 }
25
26 h2 {
27 margin-left: 2px;
28 margin-bottom: 0;
29 }
30 }
31
32 &__services {
33 width: 100%;
34 max-width: 800px;
35 height: 100%;
36 max-height: 600px;
37 margin-left: -450px;
38 }
39
40 &__buttons {
41 display: block;
42 margin-top: 100px;
43 text-align: center;
44
45 .button:first-of-type {
46 margin-right: 25px;
47 }
48 }
49
50 .button {
51 border-color: #FFF;
52 color: #FFF;
53
54 &:hover {
55 background: #FFF;
56 color: $theme-brand-primary;
57 }
58 }
59
60 &__featured-services {
61 margin-top: 150px;
62 text-align: center;
63 margin-top: 80px;
64 }
65
66 &__featured-service {
67 width: 35px;
68 margin-right: 30px;
69
70 &:last-of-type {
71 margin-right: 0;
72 }
73 }
74 }
75}
diff --git a/src/webview/ime.js b/src/webview/ime.js
new file mode 100644
index 000000000..43df6267c
--- /dev/null
+++ b/src/webview/ime.js
@@ -0,0 +1,10 @@
1const { ipcRenderer } = require('electron');
2const { claimDocumentFocus } = require('../helpers/webview-ime-focus-helpers');
3
4ipcRenderer.on('claim-document-focus', claimDocumentFocus);
5
6window.addEventListener('DOMContentLoaded', () => {
7 if (document.querySelector('[autofocus]')) {
8 ipcRenderer.sendToHost('autofocus');
9 }
10});
diff --git a/src/webview/lib/RecipeWebview.js b/src/webview/lib/RecipeWebview.js
new file mode 100644
index 000000000..1787f85e2
--- /dev/null
+++ b/src/webview/lib/RecipeWebview.js
@@ -0,0 +1,74 @@
1// @flow
2const { ipcRenderer } = require('electron');
3const fs = require('fs-extra');
4
5class RecipeWebview {
6 constructor() {
7 this.countCache = {
8 direct: 0,
9 indirect: 0,
10 };
11
12 ipcRenderer.on('poll', () => {
13 this.loopFunc();
14 });
15 }
16
17 loopFunc = () => null;
18
19 /**
20 * Initialize the loop
21 *
22 * @param {Function} Function that will be executed
23 */
24 loop(fn) {
25 this.loopFunc = fn;
26 }
27
28 /**
29 * Set the unread message badge
30 *
31 * @param {int} direct Set the count of direct messages
32 * eg. Slack direct mentions, or a
33 * message to @channel
34 * @param {int} indirect Set a badge that defines there are
35 * new messages but they do not involve
36 * me directly to me eg. in a channel
37 */
38 setBadge(direct = 0, indirect = 0) {
39 if (this.countCache.direct === direct
40 && this.countCache.indirect === indirect) return;
41
42 const count = {
43 direct,
44 indirect,
45 };
46
47 ipcRenderer.sendToHost('messages', count);
48 Object.assign(this.countCache, count);
49 }
50
51 /**
52 * Injects the contents of a CSS file into the current webview
53 *
54 * @param {Array} files CSS files that should be injected. This must
55 * be an absolute path to the file
56 */
57 injectCSS(...files) {
58 files.forEach((file) => {
59 const data = fs.readFileSync(file);
60 const styles = document.createElement('style');
61 styles.innerHTML = data.toString();
62
63 document.querySelector('head').appendChild(styles);
64 });
65 }
66
67 initialize(fn) {
68 if (typeof fn === 'function') {
69 fn();
70 }
71 }
72}
73
74module.exports = RecipeWebview;
diff --git a/src/webview/notifications.js b/src/webview/notifications.js
new file mode 100644
index 000000000..97ce9d69b
--- /dev/null
+++ b/src/webview/notifications.js
@@ -0,0 +1,45 @@
1const { ipcRenderer } = require('electron');
2const uuidV1 = require('uuid/v1');
3// const FranzNotificationStore = [];
4
5class Notification {
6 constructor(title = '', options = {}) {
7 this.title = title;
8 this.options = options;
9 this.notificationId = uuidV1();
10 this.onclick = () => {};
11
12 ipcRenderer.sendToHost('notification', {
13 notificationId: this.notificationId,
14 title,
15 options,
16 });
17
18 ipcRenderer.on(`notification-onclick:${this.notificationId}`, () => {
19 this.onclick();
20 });
21 }
22}
23
24Notification.permission = 'granted';
25
26Notification.requestPermission = (cb = null) => {
27 console.log(this);
28 if (!cb) {
29 return new Promise((resolve) => {
30 resolve(Notification.permission);
31 });
32 }
33
34 if (typeof (cb) === 'function') {
35 return cb(Notification.permission);
36 }
37
38 return Notification.permission;
39};
40
41Notification.close = () => {
42 // no implementation yet
43};
44
45window.Notification = Notification;
diff --git a/src/webview/plugin.js b/src/webview/plugin.js
new file mode 100644
index 000000000..ffc9084e4
--- /dev/null
+++ b/src/webview/plugin.js
@@ -0,0 +1,24 @@
1const { ipcRenderer } = require('electron');
2const path = require('path');
3
4const RecipeWebview = require('./lib/RecipeWebview');
5
6require('./notifications.js');
7require('./spellchecker.js');
8require('./ime.js');
9
10ipcRenderer.on('initializeRecipe', (e, data) => {
11 const modulePath = path.join(data.recipe.path, 'webview.js');
12 // Delete module from cache
13 delete require.cache[require.resolve(modulePath)];
14 try {
15 // eslint-disable-next-line
16 require(modulePath)(new RecipeWebview(), data);
17 } catch (err) {
18 console.error(err);
19 }
20});
21
22document.addEventListener('DOMContentLoaded', () => {
23 ipcRenderer.sendToHost('hello');
24}, false);
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js
new file mode 100644
index 000000000..ec8807874
--- /dev/null
+++ b/src/webview/spellchecker.js
@@ -0,0 +1,14 @@
1import { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } from 'electron-spellchecker';
2
3window.spellCheckHandler = new SpellCheckHandler();
4setTimeout(() => {
5 window.spellCheckHandler.attachToInput();
6}, 1000);
7
8// TODO: should we set the language to user settings?
9// window.spellCheckHandler.switchLanguage('en-US');
10
11const contextMenuBuilder = new ContextMenuBuilder(window.spellCheckHandler);
12const contextMenuListener = new ContextMenuListener((info) => { // eslint-disable-line
13 contextMenuBuilder.showPopupMenu(info);
14});
diff --git a/src/webview/zoom.js b/src/webview/zoom.js
new file mode 100644
index 000000000..99c647036
--- /dev/null
+++ b/src/webview/zoom.js
@@ -0,0 +1,37 @@
1const electron = require('electron');
2
3const { ipcRenderer, webFrame } = electron;
4
5const maxZoomLevel = 9;
6const minZoomLevel = -8;
7let zoomLevel = 0;
8
9ipcRenderer.on('zoomIn', () => {
10 if (maxZoomLevel > zoomLevel) {
11 zoomLevel += 1;
12 }
13 webFrame.setZoomLevel(zoomLevel);
14
15 ipcRenderer.sendToHost('zoomLevel', { zoom: zoomLevel });
16});
17
18ipcRenderer.on('zoomOut', () => {
19 if (minZoomLevel < zoomLevel) {
20 zoomLevel -= 1;
21 }
22 webFrame.setZoomLevel(zoomLevel);
23
24 ipcRenderer.sendToHost('zoomLevel', { zoom: zoomLevel });
25});
26
27ipcRenderer.on('zoomReset', () => {
28 zoomLevel = 0;
29 webFrame.setZoomLevel(zoomLevel);
30
31 ipcRenderer.sendToHost('zoomLevel', { zoom: zoomLevel });
32});
33
34ipcRenderer.on('setZoom', (e, arg) => {
35 zoomLevel = arg;
36 webFrame.setZoomLevel(zoomLevel);
37});
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 000000000..c7a6fe805
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,6731 @@
1# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2# yarn lockfile v1
3
4
5"7zip-bin-linux@^1.1.0":
6 version "1.1.0"
7 resolved "https://registry.yarnpkg.com/7zip-bin-linux/-/7zip-bin-linux-1.1.0.tgz#2ca309fd6a2102e18bd81e3a5d91b39db9adab71"
8
9"7zip-bin-mac@^1.0.1":
10 version "1.0.1"
11 resolved "https://registry.yarnpkg.com/7zip-bin-mac/-/7zip-bin-mac-1.0.1.tgz#3e68778bbf0926adc68159427074505d47555c02"
12
13"7zip-bin-win@^2.1.0":
14 version "2.1.0"
15 resolved "https://registry.yarnpkg.com/7zip-bin-win/-/7zip-bin-win-2.1.0.tgz#ce632da797ec282c5d2a8d07b60e8df7ca7f164d"
16
17"7zip-bin@^2.1.0":
18 version "2.2.3"
19 resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-2.2.3.tgz#a249cad6c22f8289495741f5d9ea22368af1e078"
20 optionalDependencies:
21 "7zip-bin-linux" "^1.1.0"
22 "7zip-bin-mac" "^1.0.1"
23 "7zip-bin-win" "^2.1.0"
24
25"@paulcbetts/cld@^2.4.6":
26 version "2.4.6"
27 resolved "https://registry.yarnpkg.com/@paulcbetts/cld/-/cld-2.4.6.tgz#a992f6bc43cab212ac2c4488a671cf302f8b62e7"
28 dependencies:
29 glob "^5.0.10"
30 nan "^2.0.5"
31 rimraf "^2.4.0"
32 underscore "^1.6.0"
33
34"@paulcbetts/spellchecker@^4.0.5":
35 version "4.0.5"
36 resolved "https://registry.yarnpkg.com/@paulcbetts/spellchecker/-/spellchecker-4.0.5.tgz#4ea9bfb85faba53c094c0809a18986bf44265c5f"
37 dependencies:
38 nan "^2.0.0"
39
40"@paulcbetts/system-idle-time@^1.0.4":
41 version "1.0.4"
42 resolved "https://registry.yarnpkg.com/@paulcbetts/system-idle-time/-/system-idle-time-1.0.4.tgz#17b275530176d72695646380b13b79724288b1c6"
43 dependencies:
44 bindings "~1.2.1"
45 nan "^2.0.0"
46
47"@types/node@^7.0.18":
48 version "7.0.43"
49 resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c"
50
51abbrev@1:
52 version "1.1.0"
53 resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f"
54
55accepts@1.3.3:
56 version "1.3.3"
57 resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
58 dependencies:
59 mime-types "~2.1.11"
60 negotiator "0.6.1"
61
62accepts@~1.0.7:
63 version "1.0.7"
64 resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.0.7.tgz#5b501fb4f0704309964ccdb048172541208dab1a"
65 dependencies:
66 mime-types "~1.0.0"
67 negotiator "0.4.7"
68
69accepts@~1.3.4:
70 version "1.3.4"
71 resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f"
72 dependencies:
73 mime-types "~2.1.16"
74 negotiator "0.6.1"
75
76acorn-jsx@^3.0.0:
77 version "3.0.1"
78 resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
79 dependencies:
80 acorn "^3.0.4"
81
82acorn@^3.0.4:
83 version "3.3.0"
84 resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
85
86acorn@^5.1.1:
87 version "5.1.2"
88 resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.2.tgz#911cb53e036807cf0fa778dc5d370fbd864246d7"
89
90after@0.8.2:
91 version "0.8.2"
92 resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
93
94agent-base@^4.1.0:
95 version "4.1.1"
96 resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.1.1.tgz#92d8a4fc2524a3b09b3666a33b6c97960f23d6a4"
97 dependencies:
98 es6-promisify "^5.0.0"
99
100ajv-keywords@^1.0.0:
101 version "1.5.1"
102 resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
103
104ajv-keywords@^2.1.0:
105 version "2.1.0"
106 resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0"
107
108ajv@^4.7.0, ajv@^4.9.1:
109 version "4.11.8"
110 resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
111 dependencies:
112 co "^4.6.0"
113 json-stable-stringify "^1.0.1"
114
115ajv@^5.2.0, ajv@^5.2.1:
116 version "5.2.2"
117 resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39"
118 dependencies:
119 co "^4.6.0"
120 fast-deep-equal "^1.0.0"
121 json-schema-traverse "^0.3.0"
122 json-stable-stringify "^1.0.1"
123
124amdefine@>=0.0.4:
125 version "1.0.1"
126 resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
127
128ansi-align@^2.0.0:
129 version "2.0.0"
130 resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f"
131 dependencies:
132 string-width "^2.0.0"
133
134ansi-escapes@^2.0.0:
135 version "2.0.0"
136 resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b"
137
138ansi-escapes@^3.0.0:
139 version "3.0.0"
140 resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
141
142ansi-regex@^1.0.0, ansi-regex@^1.1.1:
143 version "1.1.1"
144 resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-1.1.1.tgz#41c847194646375e6a1a5d10c3ca054ef9fc980d"
145
146ansi-regex@^2.0.0:
147 version "2.1.1"
148 resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
149
150ansi-regex@^3.0.0:
151 version "3.0.0"
152 resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
153
154ansi-styles@^2.2.1:
155 version "2.2.1"
156 resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
157
158ansi-styles@^3.1.0:
159 version "3.2.0"
160 resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
161 dependencies:
162 color-convert "^1.9.0"
163
164anymatch@^1.3.0:
165 version "1.3.2"
166 resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
167 dependencies:
168 micromatch "^2.1.5"
169 normalize-path "^2.0.0"
170
171aproba@^1.0.3:
172 version "1.1.2"
173 resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1"
174
175archy@^1.0.0:
176 version "1.0.0"
177 resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40"
178
179are-we-there-yet@~1.1.2:
180 version "1.1.4"
181 resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
182 dependencies:
183 delegates "^1.0.0"
184 readable-stream "^2.0.6"
185
186argparse@^1.0.7:
187 version "1.0.9"
188 resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
189 dependencies:
190 sprintf-js "~1.0.2"
191
192arr-diff@^2.0.0:
193 version "2.0.0"
194 resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
195 dependencies:
196 arr-flatten "^1.0.1"
197
198arr-filter@^1.1.1:
199 version "1.1.2"
200 resolved "https://registry.yarnpkg.com/arr-filter/-/arr-filter-1.1.2.tgz#43fdddd091e8ef11aa4c45d9cdc18e2dff1711ee"
201 dependencies:
202 make-iterator "^1.0.0"
203
204arr-flatten@^1.0.1:
205 version "1.1.0"
206 resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
207
208arr-map@^2.0.0, arr-map@^2.0.2:
209 version "2.0.2"
210 resolved "https://registry.yarnpkg.com/arr-map/-/arr-map-2.0.2.tgz#3a77345ffc1cf35e2a91825601f9e58f2e24cac4"
211 dependencies:
212 make-iterator "^1.0.0"
213
214array-differ@^1.0.0:
215 version "1.0.0"
216 resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031"
217
218array-each@^1.0.0, array-each@^1.0.1:
219 version "1.0.1"
220 resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f"
221
222array-find-index@^1.0.1:
223 version "1.0.2"
224 resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
225
226array-flatten@1.1.1:
227 version "1.1.1"
228 resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
229
230array-initial@^1.0.0:
231 version "1.0.1"
232 resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.0.1.tgz#86122222a29c1ed42347f6334111afa40f8b20ec"
233 dependencies:
234 array-slice "^1.0.0"
235 is-number "^3.0.0"
236
237array-last@^1.1.1:
238 version "1.2.0"
239 resolved "https://registry.yarnpkg.com/array-last/-/array-last-1.2.0.tgz#0884a67ec2ac2a08133fc00f66779cfedb010986"
240 dependencies:
241 is-number "^3.0.0"
242
243array-slice@^1.0.0:
244 version "1.0.0"
245 resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.0.0.tgz#e73034f00dcc1f40876008fd20feae77bd4b7c2f"
246
247array-union@^1.0.1:
248 version "1.0.2"
249 resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
250 dependencies:
251 array-uniq "^1.0.1"
252
253array-uniq@^1.0.1, array-uniq@^1.0.2:
254 version "1.0.3"
255 resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
256
257array-unique@^0.2.1:
258 version "0.2.1"
259 resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
260
261array.prototype.find@^2.0.1:
262 version "2.0.4"
263 resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.4.tgz#556a5c5362c08648323ddaeb9de9d14bc1864c90"
264 dependencies:
265 define-properties "^1.1.2"
266 es-abstract "^1.7.0"
267
268arraybuffer.slice@0.0.6:
269 version "0.0.6"
270 resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca"
271
272arrify@^1.0.0:
273 version "1.0.1"
274 resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
275
276asap@~2.0.3:
277 version "2.0.6"
278 resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
279
280asar-integrity@0.1.1:
281 version "0.1.1"
282 resolved "https://registry.yarnpkg.com/asar-integrity/-/asar-integrity-0.1.1.tgz#1a709dd78443707fc260f7ce363d9569983caf76"
283 dependencies:
284 bluebird-lst "^1.0.2"
285 fs-extra-p "^4.3.0"
286
287asar@^0.13.0:
288 version "0.13.0"
289 resolved "https://registry.yarnpkg.com/asar/-/asar-0.13.0.tgz#df33dd9d01bff842464d0d9f095740d4a62afb14"
290 dependencies:
291 chromium-pickle-js "^0.2.0"
292 commander "^2.9.0"
293 cuint "^0.2.1"
294 glob "^6.0.4"
295 minimatch "^3.0.3"
296 mkdirp "^0.5.0"
297 mksnapshot "^0.3.0"
298 tmp "0.0.28"
299
300asn1@~0.2.3:
301 version "0.2.3"
302 resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
303
304assert-plus@1.0.0, assert-plus@^1.0.0:
305 version "1.0.0"
306 resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
307
308assert-plus@^0.2.0:
309 version "0.2.0"
310 resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
311
312async-done@^1.2.0, async-done@^1.2.2:
313 version "1.2.3"
314 resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.2.3.tgz#6c7abc7d61ca27fe6f1f2ba3206ea9ae60a43983"
315 dependencies:
316 end-of-stream "^1.1.0"
317 once "^1.3.2"
318 process-nextick-args "^1.0.7"
319 stream-exhaust "^1.0.1"
320
321async-each@^1.0.0:
322 version "1.0.1"
323 resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
324
325async-foreach@^0.1.3:
326 version "0.1.3"
327 resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
328
329async-limiter@~1.0.0:
330 version "1.0.0"
331 resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
332
333async-settle@^1.0.0:
334 version "1.0.0"
335 resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b"
336 dependencies:
337 async-done "^1.2.2"
338
339async@^0.9.0:
340 version "0.9.2"
341 resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
342
343asynckit@^0.4.0:
344 version "0.4.0"
345 resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
346
347aws-sign2@~0.6.0:
348 version "0.6.0"
349 resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
350
351aws4@^1.2.1:
352 version "1.6.0"
353 resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
354
355babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
356 version "6.26.0"
357 resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
358 dependencies:
359 chalk "^1.1.3"
360 esutils "^2.0.2"
361 js-tokens "^3.0.2"
362
363babel-core@^6.0.2, babel-core@^6.26.0:
364 version "6.26.0"
365 resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
366 dependencies:
367 babel-code-frame "^6.26.0"
368 babel-generator "^6.26.0"
369 babel-helpers "^6.24.1"
370 babel-messages "^6.23.0"
371 babel-register "^6.26.0"
372 babel-runtime "^6.26.0"
373 babel-template "^6.26.0"
374 babel-traverse "^6.26.0"
375 babel-types "^6.26.0"
376 babylon "^6.18.0"
377 convert-source-map "^1.5.0"
378 debug "^2.6.8"
379 json5 "^0.5.1"
380 lodash "^4.17.4"
381 minimatch "^3.0.4"
382 path-is-absolute "^1.0.1"
383 private "^0.1.7"
384 slash "^1.0.0"
385 source-map "^0.5.6"
386
387babel-eslint@^7.1.1:
388 version "7.2.3"
389 resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.3.tgz#b2fe2d80126470f5c19442dc757253a897710827"
390 dependencies:
391 babel-code-frame "^6.22.0"
392 babel-traverse "^6.23.1"
393 babel-types "^6.23.0"
394 babylon "^6.17.0"
395
396babel-generator@^6.26.0:
397 version "6.26.0"
398 resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5"
399 dependencies:
400 babel-messages "^6.23.0"
401 babel-runtime "^6.26.0"
402 babel-types "^6.26.0"
403 detect-indent "^4.0.0"
404 jsesc "^1.3.0"
405 lodash "^4.17.4"
406 source-map "^0.5.6"
407 trim-right "^1.0.1"
408
409babel-helper-bindify-decorators@^6.24.1:
410 version "6.24.1"
411 resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330"
412 dependencies:
413 babel-runtime "^6.22.0"
414 babel-traverse "^6.24.1"
415 babel-types "^6.24.1"
416
417babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
418 version "6.24.1"
419 resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
420 dependencies:
421 babel-helper-explode-assignable-expression "^6.24.1"
422 babel-runtime "^6.22.0"
423 babel-types "^6.24.1"
424
425babel-helper-builder-react-jsx@^6.24.1:
426 version "6.26.0"
427 resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0"
428 dependencies:
429 babel-runtime "^6.26.0"
430 babel-types "^6.26.0"
431 esutils "^2.0.2"
432
433babel-helper-call-delegate@^6.24.1:
434 version "6.24.1"
435 resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
436 dependencies:
437 babel-helper-hoist-variables "^6.24.1"
438 babel-runtime "^6.22.0"
439 babel-traverse "^6.24.1"
440 babel-types "^6.24.1"
441
442babel-helper-define-map@^6.24.1:
443 version "6.26.0"
444 resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f"
445 dependencies:
446 babel-helper-function-name "^6.24.1"
447 babel-runtime "^6.26.0"
448 babel-types "^6.26.0"
449 lodash "^4.17.4"
450
451babel-helper-explode-assignable-expression@^6.24.1:
452 version "6.24.1"
453 resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa"
454 dependencies:
455 babel-runtime "^6.22.0"
456 babel-traverse "^6.24.1"
457 babel-types "^6.24.1"
458
459babel-helper-explode-class@^6.24.1:
460 version "6.24.1"
461 resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb"
462 dependencies:
463 babel-helper-bindify-decorators "^6.24.1"
464 babel-runtime "^6.22.0"
465 babel-traverse "^6.24.1"
466 babel-types "^6.24.1"
467
468babel-helper-function-name@^6.24.1:
469 version "6.24.1"
470 resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
471 dependencies:
472 babel-helper-get-function-arity "^6.24.1"
473 babel-runtime "^6.22.0"
474 babel-template "^6.24.1"
475 babel-traverse "^6.24.1"
476 babel-types "^6.24.1"
477
478babel-helper-get-function-arity@^6.24.1:
479 version "6.24.1"
480 resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d"
481 dependencies:
482 babel-runtime "^6.22.0"
483 babel-types "^6.24.1"
484
485babel-helper-hoist-variables@^6.24.1:
486 version "6.24.1"
487 resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76"
488 dependencies:
489 babel-runtime "^6.22.0"
490 babel-types "^6.24.1"
491
492babel-helper-optimise-call-expression@^6.24.1:
493 version "6.24.1"
494 resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257"
495 dependencies:
496 babel-runtime "^6.22.0"
497 babel-types "^6.24.1"
498
499babel-helper-regex@^6.24.1:
500 version "6.26.0"
501 resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72"
502 dependencies:
503 babel-runtime "^6.26.0"
504 babel-types "^6.26.0"
505 lodash "^4.17.4"
506
507babel-helper-remap-async-to-generator@^6.24.1:
508 version "6.24.1"
509 resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b"
510 dependencies:
511 babel-helper-function-name "^6.24.1"
512 babel-runtime "^6.22.0"
513 babel-template "^6.24.1"
514 babel-traverse "^6.24.1"
515 babel-types "^6.24.1"
516
517babel-helper-replace-supers@^6.24.1:
518 version "6.24.1"
519 resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a"
520 dependencies:
521 babel-helper-optimise-call-expression "^6.24.1"
522 babel-messages "^6.23.0"
523 babel-runtime "^6.22.0"
524 babel-template "^6.24.1"
525 babel-traverse "^6.24.1"
526 babel-types "^6.24.1"
527
528babel-helpers@^6.24.1:
529 version "6.24.1"
530 resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
531 dependencies:
532 babel-runtime "^6.22.0"
533 babel-template "^6.24.1"
534
535babel-messages@^6.23.0:
536 version "6.23.0"
537 resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
538 dependencies:
539 babel-runtime "^6.22.0"
540
541babel-plugin-check-es2015-constants@^6.22.0:
542 version "6.22.0"
543 resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a"
544 dependencies:
545 babel-runtime "^6.22.0"
546
547babel-plugin-syntax-async-functions@^6.8.0:
548 version "6.13.0"
549 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
550
551babel-plugin-syntax-async-generators@^6.5.0:
552 version "6.13.0"
553 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a"
554
555babel-plugin-syntax-class-constructor-call@^6.18.0:
556 version "6.18.0"
557 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416"
558
559babel-plugin-syntax-class-properties@^6.8.0:
560 version "6.13.0"
561 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de"
562
563babel-plugin-syntax-decorators@^6.1.18, babel-plugin-syntax-decorators@^6.13.0:
564 version "6.13.0"
565 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b"
566
567babel-plugin-syntax-do-expressions@^6.8.0:
568 version "6.13.0"
569 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz#5747756139aa26d390d09410b03744ba07e4796d"
570
571babel-plugin-syntax-dynamic-import@^6.18.0:
572 version "6.18.0"
573 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
574
575babel-plugin-syntax-exponentiation-operator@^6.8.0:
576 version "6.13.0"
577 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
578
579babel-plugin-syntax-export-extensions@^6.8.0:
580 version "6.13.0"
581 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721"
582
583babel-plugin-syntax-flow@^6.18.0:
584 version "6.18.0"
585 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
586
587babel-plugin-syntax-function-bind@^6.8.0:
588 version "6.13.0"
589 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46"
590
591babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
592 version "6.18.0"
593 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
594
595babel-plugin-syntax-object-rest-spread@^6.8.0:
596 version "6.13.0"
597 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
598
599babel-plugin-syntax-trailing-function-commas@^6.22.0:
600 version "6.22.0"
601 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
602
603babel-plugin-transform-async-generator-functions@^6.24.1:
604 version "6.24.1"
605 resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db"
606 dependencies:
607 babel-helper-remap-async-to-generator "^6.24.1"
608 babel-plugin-syntax-async-generators "^6.5.0"
609 babel-runtime "^6.22.0"
610
611babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-to-generator@^6.24.1:
612 version "6.24.1"
613 resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
614 dependencies:
615 babel-helper-remap-async-to-generator "^6.24.1"
616 babel-plugin-syntax-async-functions "^6.8.0"
617 babel-runtime "^6.22.0"
618
619babel-plugin-transform-class-constructor-call@^6.24.1:
620 version "6.24.1"
621 resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz#80dc285505ac067dcb8d6c65e2f6f11ab7765ef9"
622 dependencies:
623 babel-plugin-syntax-class-constructor-call "^6.18.0"
624 babel-runtime "^6.22.0"
625 babel-template "^6.24.1"
626
627babel-plugin-transform-class-properties@^6.19.0, babel-plugin-transform-class-properties@^6.24.1:
628 version "6.24.1"
629 resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac"
630 dependencies:
631 babel-helper-function-name "^6.24.1"
632 babel-plugin-syntax-class-properties "^6.8.0"
633 babel-runtime "^6.22.0"
634 babel-template "^6.24.1"
635
636babel-plugin-transform-decorators-legacy@^1.3.4:
637 version "1.3.4"
638 resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.4.tgz#741b58f6c5bce9e6027e0882d9c994f04f366925"
639 dependencies:
640 babel-plugin-syntax-decorators "^6.1.18"
641 babel-runtime "^6.2.0"
642 babel-template "^6.3.0"
643
644babel-plugin-transform-decorators@^6.24.1:
645 version "6.24.1"
646 resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d"
647 dependencies:
648 babel-helper-explode-class "^6.24.1"
649 babel-plugin-syntax-decorators "^6.13.0"
650 babel-runtime "^6.22.0"
651 babel-template "^6.24.1"
652 babel-types "^6.24.1"
653
654babel-plugin-transform-do-expressions@^6.22.0:
655 version "6.22.0"
656 resolved "https://registry.yarnpkg.com/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz#28ccaf92812d949c2cd1281f690c8fdc468ae9bb"
657 dependencies:
658 babel-plugin-syntax-do-expressions "^6.8.0"
659 babel-runtime "^6.22.0"
660
661babel-plugin-transform-es2015-arrow-functions@^6.22.0:
662 version "6.22.0"
663 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
664 dependencies:
665 babel-runtime "^6.22.0"
666
667babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
668 version "6.22.0"
669 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141"
670 dependencies:
671 babel-runtime "^6.22.0"
672
673babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es2015-block-scoping@^6.24.1:
674 version "6.26.0"
675 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
676 dependencies:
677 babel-runtime "^6.26.0"
678 babel-template "^6.26.0"
679 babel-traverse "^6.26.0"
680 babel-types "^6.26.0"
681 lodash "^4.17.4"
682
683babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.24.1:
684 version "6.24.1"
685 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
686 dependencies:
687 babel-helper-define-map "^6.24.1"
688 babel-helper-function-name "^6.24.1"
689 babel-helper-optimise-call-expression "^6.24.1"
690 babel-helper-replace-supers "^6.24.1"
691 babel-messages "^6.23.0"
692 babel-runtime "^6.22.0"
693 babel-template "^6.24.1"
694 babel-traverse "^6.24.1"
695 babel-types "^6.24.1"
696
697babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1:
698 version "6.24.1"
699 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
700 dependencies:
701 babel-runtime "^6.22.0"
702 babel-template "^6.24.1"
703
704babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0:
705 version "6.23.0"
706 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
707 dependencies:
708 babel-runtime "^6.22.0"
709
710babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
711 version "6.24.1"
712 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
713 dependencies:
714 babel-runtime "^6.22.0"
715 babel-types "^6.24.1"
716
717babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0:
718 version "6.23.0"
719 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
720 dependencies:
721 babel-runtime "^6.22.0"
722
723babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1:
724 version "6.24.1"
725 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
726 dependencies:
727 babel-helper-function-name "^6.24.1"
728 babel-runtime "^6.22.0"
729 babel-types "^6.24.1"
730
731babel-plugin-transform-es2015-literals@^6.22.0:
732 version "6.22.0"
733 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e"
734 dependencies:
735 babel-runtime "^6.22.0"
736
737babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1:
738 version "6.24.1"
739 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
740 dependencies:
741 babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
742 babel-runtime "^6.22.0"
743 babel-template "^6.24.1"
744
745babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
746 version "6.26.0"
747 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
748 dependencies:
749 babel-plugin-transform-strict-mode "^6.24.1"
750 babel-runtime "^6.26.0"
751 babel-template "^6.26.0"
752 babel-types "^6.26.0"
753
754babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
755 version "6.24.1"
756 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
757 dependencies:
758 babel-helper-hoist-variables "^6.24.1"
759 babel-runtime "^6.22.0"
760 babel-template "^6.24.1"
761
762babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015-modules-umd@^6.24.1:
763 version "6.24.1"
764 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
765 dependencies:
766 babel-plugin-transform-es2015-modules-amd "^6.24.1"
767 babel-runtime "^6.22.0"
768 babel-template "^6.24.1"
769
770babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1:
771 version "6.24.1"
772 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
773 dependencies:
774 babel-helper-replace-supers "^6.24.1"
775 babel-runtime "^6.22.0"
776
777babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.24.1:
778 version "6.24.1"
779 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
780 dependencies:
781 babel-helper-call-delegate "^6.24.1"
782 babel-helper-get-function-arity "^6.24.1"
783 babel-runtime "^6.22.0"
784 babel-template "^6.24.1"
785 babel-traverse "^6.24.1"
786 babel-types "^6.24.1"
787
788babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
789 version "6.24.1"
790 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
791 dependencies:
792 babel-runtime "^6.22.0"
793 babel-types "^6.24.1"
794
795babel-plugin-transform-es2015-spread@^6.22.0:
796 version "6.22.0"
797 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1"
798 dependencies:
799 babel-runtime "^6.22.0"
800
801babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1:
802 version "6.24.1"
803 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
804 dependencies:
805 babel-helper-regex "^6.24.1"
806 babel-runtime "^6.22.0"
807 babel-types "^6.24.1"
808
809babel-plugin-transform-es2015-template-literals@^6.22.0:
810 version "6.22.0"
811 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d"
812 dependencies:
813 babel-runtime "^6.22.0"
814
815babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
816 version "6.23.0"
817 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
818 dependencies:
819 babel-runtime "^6.22.0"
820
821babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1:
822 version "6.24.1"
823 resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
824 dependencies:
825 babel-helper-regex "^6.24.1"
826 babel-runtime "^6.22.0"
827 regexpu-core "^2.0.0"
828
829babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-exponentiation-operator@^6.24.1:
830 version "6.24.1"
831 resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
832 dependencies:
833 babel-helper-builder-binary-assignment-operator-visitor "^6.24.1"
834 babel-plugin-syntax-exponentiation-operator "^6.8.0"
835 babel-runtime "^6.22.0"
836
837babel-plugin-transform-export-extensions@^6.22.0:
838 version "6.22.0"
839 resolved "https://registry.yarnpkg.com/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz#53738b47e75e8218589eea946cbbd39109bbe653"
840 dependencies:
841 babel-plugin-syntax-export-extensions "^6.8.0"
842 babel-runtime "^6.22.0"
843
844babel-plugin-transform-flow-strip-types@^6.22.0:
845 version "6.22.0"
846 resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf"
847 dependencies:
848 babel-plugin-syntax-flow "^6.18.0"
849 babel-runtime "^6.22.0"
850
851babel-plugin-transform-function-bind@^6.22.0:
852 version "6.22.0"
853 resolved "https://registry.yarnpkg.com/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz#c6fb8e96ac296a310b8cf8ea401462407ddf6a97"
854 dependencies:
855 babel-plugin-syntax-function-bind "^6.8.0"
856 babel-runtime "^6.22.0"
857
858babel-plugin-transform-object-rest-spread@^6.22.0:
859 version "6.26.0"
860 resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
861 dependencies:
862 babel-plugin-syntax-object-rest-spread "^6.8.0"
863 babel-runtime "^6.26.0"
864
865babel-plugin-transform-react-display-name@^6.23.0:
866 version "6.25.0"
867 resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1"
868 dependencies:
869 babel-runtime "^6.22.0"
870
871babel-plugin-transform-react-jsx-self@^6.22.0:
872 version "6.22.0"
873 resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e"
874 dependencies:
875 babel-plugin-syntax-jsx "^6.8.0"
876 babel-runtime "^6.22.0"
877
878babel-plugin-transform-react-jsx-source@^6.22.0:
879 version "6.22.0"
880 resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6"
881 dependencies:
882 babel-plugin-syntax-jsx "^6.8.0"
883 babel-runtime "^6.22.0"
884
885babel-plugin-transform-react-jsx@^6.24.1:
886 version "6.24.1"
887 resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3"
888 dependencies:
889 babel-helper-builder-react-jsx "^6.24.1"
890 babel-plugin-syntax-jsx "^6.8.0"
891 babel-runtime "^6.22.0"
892
893babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1:
894 version "6.26.0"
895 resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
896 dependencies:
897 regenerator-transform "^0.10.0"
898
899babel-plugin-transform-strict-mode@^6.24.1:
900 version "6.24.1"
901 resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
902 dependencies:
903 babel-runtime "^6.22.0"
904 babel-types "^6.24.1"
905
906babel-polyfill@^6.23.0:
907 version "6.26.0"
908 resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153"
909 dependencies:
910 babel-runtime "^6.26.0"
911 core-js "^2.5.0"
912 regenerator-runtime "^0.10.5"
913
914babel-preset-env@^1.5.2:
915 version "1.6.0"
916 resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.0.tgz#2de1c782a780a0a5d605d199c957596da43c44e4"
917 dependencies:
918 babel-plugin-check-es2015-constants "^6.22.0"
919 babel-plugin-syntax-trailing-function-commas "^6.22.0"
920 babel-plugin-transform-async-to-generator "^6.22.0"
921 babel-plugin-transform-es2015-arrow-functions "^6.22.0"
922 babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
923 babel-plugin-transform-es2015-block-scoping "^6.23.0"
924 babel-plugin-transform-es2015-classes "^6.23.0"
925 babel-plugin-transform-es2015-computed-properties "^6.22.0"
926 babel-plugin-transform-es2015-destructuring "^6.23.0"
927 babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
928 babel-plugin-transform-es2015-for-of "^6.23.0"
929 babel-plugin-transform-es2015-function-name "^6.22.0"
930 babel-plugin-transform-es2015-literals "^6.22.0"
931 babel-plugin-transform-es2015-modules-amd "^6.22.0"
932 babel-plugin-transform-es2015-modules-commonjs "^6.23.0"
933 babel-plugin-transform-es2015-modules-systemjs "^6.23.0"
934 babel-plugin-transform-es2015-modules-umd "^6.23.0"
935 babel-plugin-transform-es2015-object-super "^6.22.0"
936 babel-plugin-transform-es2015-parameters "^6.23.0"
937 babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
938 babel-plugin-transform-es2015-spread "^6.22.0"
939 babel-plugin-transform-es2015-sticky-regex "^6.22.0"
940 babel-plugin-transform-es2015-template-literals "^6.22.0"
941 babel-plugin-transform-es2015-typeof-symbol "^6.23.0"
942 babel-plugin-transform-es2015-unicode-regex "^6.22.0"
943 babel-plugin-transform-exponentiation-operator "^6.22.0"
944 babel-plugin-transform-regenerator "^6.22.0"
945 browserslist "^2.1.2"
946 invariant "^2.2.2"
947 semver "^5.3.0"
948
949babel-preset-es2015@^6.22.0:
950 version "6.24.1"
951 resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
952 dependencies:
953 babel-plugin-check-es2015-constants "^6.22.0"
954 babel-plugin-transform-es2015-arrow-functions "^6.22.0"
955 babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
956 babel-plugin-transform-es2015-block-scoping "^6.24.1"
957 babel-plugin-transform-es2015-classes "^6.24.1"
958 babel-plugin-transform-es2015-computed-properties "^6.24.1"
959 babel-plugin-transform-es2015-destructuring "^6.22.0"
960 babel-plugin-transform-es2015-duplicate-keys "^6.24.1"
961 babel-plugin-transform-es2015-for-of "^6.22.0"
962 babel-plugin-transform-es2015-function-name "^6.24.1"
963 babel-plugin-transform-es2015-literals "^6.22.0"
964 babel-plugin-transform-es2015-modules-amd "^6.24.1"
965 babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
966 babel-plugin-transform-es2015-modules-systemjs "^6.24.1"
967 babel-plugin-transform-es2015-modules-umd "^6.24.1"
968 babel-plugin-transform-es2015-object-super "^6.24.1"
969 babel-plugin-transform-es2015-parameters "^6.24.1"
970 babel-plugin-transform-es2015-shorthand-properties "^6.24.1"
971 babel-plugin-transform-es2015-spread "^6.22.0"
972 babel-plugin-transform-es2015-sticky-regex "^6.24.1"
973 babel-plugin-transform-es2015-template-literals "^6.22.0"
974 babel-plugin-transform-es2015-typeof-symbol "^6.22.0"
975 babel-plugin-transform-es2015-unicode-regex "^6.24.1"
976 babel-plugin-transform-regenerator "^6.24.1"
977
978babel-preset-es2016@^6.16.0:
979 version "6.24.1"
980 resolved "https://registry.yarnpkg.com/babel-preset-es2016/-/babel-preset-es2016-6.24.1.tgz#f900bf93e2ebc0d276df9b8ab59724ebfd959f8b"
981 dependencies:
982 babel-plugin-transform-exponentiation-operator "^6.24.1"
983
984babel-preset-es2017@^6.16.0:
985 version "6.24.1"
986 resolved "https://registry.yarnpkg.com/babel-preset-es2017/-/babel-preset-es2017-6.24.1.tgz#597beadfb9f7f208bcfd8a12e9b2b29b8b2f14d1"
987 dependencies:
988 babel-plugin-syntax-trailing-function-commas "^6.22.0"
989 babel-plugin-transform-async-to-generator "^6.24.1"
990
991babel-preset-flow@^6.23.0:
992 version "6.23.0"
993 resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d"
994 dependencies:
995 babel-plugin-transform-flow-strip-types "^6.22.0"
996
997babel-preset-react@^6.23.0:
998 version "6.24.1"
999 resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380"
1000 dependencies:
1001 babel-plugin-syntax-jsx "^6.3.13"
1002 babel-plugin-transform-react-display-name "^6.23.0"
1003 babel-plugin-transform-react-jsx "^6.24.1"
1004 babel-plugin-transform-react-jsx-self "^6.22.0"
1005 babel-plugin-transform-react-jsx-source "^6.22.0"
1006 babel-preset-flow "^6.23.0"
1007
1008babel-preset-stage-0@^6.22.0:
1009 version "6.24.1"
1010 resolved "https://registry.yarnpkg.com/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz#5642d15042f91384d7e5af8bc88b1db95b039e6a"
1011 dependencies:
1012 babel-plugin-transform-do-expressions "^6.22.0"
1013 babel-plugin-transform-function-bind "^6.22.0"
1014 babel-preset-stage-1 "^6.24.1"
1015
1016babel-preset-stage-1@^6.22.0, babel-preset-stage-1@^6.24.1:
1017 version "6.24.1"
1018 resolved "https://registry.yarnpkg.com/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz#7692cd7dcd6849907e6ae4a0a85589cfb9e2bfb0"
1019 dependencies:
1020 babel-plugin-transform-class-constructor-call "^6.24.1"
1021 babel-plugin-transform-export-extensions "^6.22.0"
1022 babel-preset-stage-2 "^6.24.1"
1023
1024babel-preset-stage-2@^6.24.1:
1025 version "6.24.1"
1026 resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1"
1027 dependencies:
1028 babel-plugin-syntax-dynamic-import "^6.18.0"
1029 babel-plugin-transform-class-properties "^6.24.1"
1030 babel-plugin-transform-decorators "^6.24.1"
1031 babel-preset-stage-3 "^6.24.1"
1032
1033babel-preset-stage-3@^6.24.1:
1034 version "6.24.1"
1035 resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395"
1036 dependencies:
1037 babel-plugin-syntax-trailing-function-commas "^6.22.0"
1038 babel-plugin-transform-async-generator-functions "^6.24.1"
1039 babel-plugin-transform-async-to-generator "^6.24.1"
1040 babel-plugin-transform-exponentiation-operator "^6.24.1"
1041 babel-plugin-transform-object-rest-spread "^6.22.0"
1042
1043babel-register@^6.26.0:
1044 version "6.26.0"
1045 resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
1046 dependencies:
1047 babel-core "^6.26.0"
1048 babel-runtime "^6.26.0"
1049 core-js "^2.5.0"
1050 home-or-tmp "^2.0.0"
1051 lodash "^4.17.4"
1052 mkdirp "^0.5.1"
1053 source-map-support "^0.4.15"
1054
1055babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
1056 version "6.26.0"
1057 resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
1058 dependencies:
1059 core-js "^2.4.0"
1060 regenerator-runtime "^0.11.0"
1061
1062babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0:
1063 version "6.26.0"
1064 resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
1065 dependencies:
1066 babel-runtime "^6.26.0"
1067 babel-traverse "^6.26.0"
1068 babel-types "^6.26.0"
1069 babylon "^6.18.0"
1070 lodash "^4.17.4"
1071
1072babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
1073 version "6.26.0"
1074 resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
1075 dependencies:
1076 babel-code-frame "^6.26.0"
1077 babel-messages "^6.23.0"
1078 babel-runtime "^6.26.0"
1079 babel-types "^6.26.0"
1080 babylon "^6.18.0"
1081 debug "^2.6.8"
1082 globals "^9.18.0"
1083 invariant "^2.2.2"
1084 lodash "^4.17.4"
1085
1086babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.26.0:
1087 version "6.26.0"
1088 resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
1089 dependencies:
1090 babel-runtime "^6.26.0"
1091 esutils "^2.0.2"
1092 lodash "^4.17.4"
1093 to-fast-properties "^1.0.3"
1094
1095babylon@^6.17.0, babylon@^6.18.0:
1096 version "6.18.0"
1097 resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
1098
1099bach@^1.0.0:
1100 version "1.2.0"
1101 resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880"
1102 dependencies:
1103 arr-filter "^1.1.1"
1104 arr-flatten "^1.0.1"
1105 arr-map "^2.0.0"
1106 array-each "^1.0.0"
1107 array-initial "^1.0.0"
1108 array-last "^1.1.1"
1109 async-done "^1.2.2"
1110 async-settle "^1.0.0"
1111 now-and-later "^2.0.0"
1112
1113backo2@1.0.2:
1114 version "1.0.2"
1115 resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
1116
1117balanced-match@^1.0.0:
1118 version "1.0.0"
1119 resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
1120
1121base64-arraybuffer@0.1.5:
1122 version "0.1.5"
1123 resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
1124
1125base64-js@1.2.0:
1126 version "1.2.0"
1127 resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
1128
1129base64id@1.0.0:
1130 version "1.0.0"
1131 resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6"
1132
1133base64url@2.0.0, base64url@^2.0.0:
1134 version "2.0.0"
1135 resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb"
1136
1137batch@0.5.1:
1138 version "0.5.1"
1139 resolved "https://registry.yarnpkg.com/batch/-/batch-0.5.1.tgz#36a4bab594c050fd7b507bca0db30c2d92af4ff2"
1140
1141bcp47@^1.1.2:
1142 version "1.1.2"
1143 resolved "https://registry.yarnpkg.com/bcp47/-/bcp47-1.1.2.tgz#354be3307ffd08433a78f5e1e2095845f89fc7fe"
1144
1145bcrypt-pbkdf@^1.0.0:
1146 version "1.0.1"
1147 resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
1148 dependencies:
1149 tweetnacl "^0.14.3"
1150
1151beeper@^1.0.0:
1152 version "1.1.1"
1153 resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809"
1154
1155better-assert@~1.0.0:
1156 version "1.0.2"
1157 resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
1158 dependencies:
1159 callsite "1.0.0"
1160
1161big.js@^3.1.3:
1162 version "3.2.0"
1163 resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
1164
1165binary-extensions@^1.0.0:
1166 version "1.10.0"
1167 resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0"
1168
1169binary@^0.3.0:
1170 version "0.3.0"
1171 resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
1172 dependencies:
1173 buffers "~0.1.1"
1174 chainsaw "~0.1.0"
1175
1176bindings@~1.2.1:
1177 version "1.2.1"
1178 resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11"
1179
1180bl@~0.9.4:
1181 version "0.9.5"
1182 resolved "https://registry.yarnpkg.com/bl/-/bl-0.9.5.tgz#c06b797af085ea00bc527afc8efcf11de2232054"
1183 dependencies:
1184 readable-stream "~1.0.26"
1185
1186blob@0.0.4:
1187 version "0.0.4"
1188 resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
1189
1190block-stream@*:
1191 version "0.0.9"
1192 resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
1193 dependencies:
1194 inherits "~2.0.0"
1195
1196bluebird-lst@^1.0.2, bluebird-lst@^1.0.3:
1197 version "1.0.3"
1198 resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.3.tgz#cc56c18660eff0a0b86e2c33d1659618f7005158"
1199 dependencies:
1200 bluebird "^3.5.0"
1201
1202bluebird@^2.9.34:
1203 version "2.11.0"
1204 resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
1205
1206bluebird@^3.1.1, bluebird@^3.4.7, bluebird@^3.5.0:
1207 version "3.5.0"
1208 resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
1209
1210body-parser@1.18.2:
1211 version "1.18.2"
1212 resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
1213 dependencies:
1214 bytes "3.0.0"
1215 content-type "~1.0.4"
1216 debug "2.6.9"
1217 depd "~1.1.1"
1218 http-errors "~1.6.2"
1219 iconv-lite "0.4.19"
1220 on-finished "~2.3.0"
1221 qs "6.5.1"
1222 raw-body "2.3.2"
1223 type-is "~1.6.15"
1224
1225boom@2.x.x:
1226 version "2.10.1"
1227 resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
1228 dependencies:
1229 hoek "2.x.x"
1230
1231boxen@^1.0.0:
1232 version "1.2.1"
1233 resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.2.1.tgz#0f11e7fe344edb9397977fc13ede7f64d956481d"
1234 dependencies:
1235 ansi-align "^2.0.0"
1236 camelcase "^4.0.0"
1237 chalk "^2.0.1"
1238 cli-boxes "^1.0.0"
1239 string-width "^2.0.0"
1240 term-size "^1.2.0"
1241 widest-line "^1.0.0"
1242
1243brace-expansion@^1.0.0, brace-expansion@^1.1.7:
1244 version "1.1.8"
1245 resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
1246 dependencies:
1247 balanced-match "^1.0.0"
1248 concat-map "0.0.1"
1249
1250braces@^1.8.2:
1251 version "1.8.5"
1252 resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
1253 dependencies:
1254 expand-range "^1.8.1"
1255 preserve "^0.2.0"
1256 repeat-element "^1.1.2"
1257
1258breakword@^1.0.3:
1259 version "1.0.3"
1260 resolved "https://registry.yarnpkg.com/breakword/-/breakword-1.0.3.tgz#71e091bbb78bb4ef003cf3ed2b2e062c6927f7dd"
1261 dependencies:
1262 wcwidth "^1.0.1"
1263
1264browserslist@^2.1.2:
1265 version "2.4.0"
1266 resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.4.0.tgz#693ee93d01e66468a6348da5498e011f578f87f8"
1267 dependencies:
1268 caniuse-lite "^1.0.30000718"
1269 electron-to-chromium "^1.3.18"
1270
1271buffer-equal-constant-time@1.0.1:
1272 version "1.0.1"
1273 resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
1274
1275buffers@~0.1.1:
1276 version "0.1.1"
1277 resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
1278
1279builtin-modules@^1.0.0, builtin-modules@^1.1.1:
1280 version "1.1.1"
1281 resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
1282
1283bytes@3.0.0:
1284 version "3.0.0"
1285 resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
1286
1287caller-path@^0.1.0:
1288 version "0.1.0"
1289 resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
1290 dependencies:
1291 callsites "^0.2.0"
1292
1293callsite@1.0.0:
1294 version "1.0.0"
1295 resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
1296
1297callsites@^0.2.0:
1298 version "0.2.0"
1299 resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
1300
1301camelcase-keys@^2.0.0:
1302 version "2.1.0"
1303 resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
1304 dependencies:
1305 camelcase "^2.0.0"
1306 map-obj "^1.0.0"
1307
1308camelcase@^2.0.0, camelcase@^2.0.1:
1309 version "2.1.1"
1310 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
1311
1312camelcase@^3.0.0:
1313 version "3.0.0"
1314 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
1315
1316camelcase@^4.0.0, camelcase@^4.1.0:
1317 version "4.1.0"
1318 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
1319
1320caniuse-lite@^1.0.30000718:
1321 version "1.0.30000722"
1322 resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000722.tgz#8cbfe07440478e3a16ab0d3b182feef1901eab55"
1323
1324capture-stack-trace@^1.0.0:
1325 version "1.0.0"
1326 resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d"
1327
1328caseless@~0.12.0:
1329 version "0.12.0"
1330 resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
1331
1332chain-function@^1.0.0:
1333 version "1.0.0"
1334 resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
1335
1336chainsaw@~0.1.0:
1337 version "0.1.0"
1338 resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
1339 dependencies:
1340 traverse ">=0.3.0 <0.4"
1341
1342chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
1343 version "1.1.3"
1344 resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
1345 dependencies:
1346 ansi-styles "^2.2.1"
1347 escape-string-regexp "^1.0.2"
1348 has-ansi "^2.0.0"
1349 strip-ansi "^3.0.0"
1350 supports-color "^2.0.0"
1351
1352chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
1353 version "2.1.0"
1354 resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e"
1355 dependencies:
1356 ansi-styles "^3.1.0"
1357 escape-string-regexp "^1.0.5"
1358 supports-color "^4.0.0"
1359
1360chokidar@^1.4.3:
1361 version "1.7.0"
1362 resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
1363 dependencies:
1364 anymatch "^1.3.0"
1365 async-each "^1.0.0"
1366 glob-parent "^2.0.0"
1367 inherits "^2.0.1"
1368 is-binary-path "^1.0.0"
1369 is-glob "^2.0.0"
1370 path-is-absolute "^1.0.0"
1371 readdirp "^2.0.0"
1372 optionalDependencies:
1373 fsevents "^1.0.0"
1374
1375chromium-pickle-js@^0.2.0:
1376 version "0.2.0"
1377 resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205"
1378
1379ci-info@^1.0.0:
1380 version "1.0.0"
1381 resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534"
1382
1383circular-json@^0.3.1:
1384 version "0.3.3"
1385 resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
1386
1387classnames@^2.2.0, classnames@^2.2.5:
1388 version "2.2.5"
1389 resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
1390
1391cli-boxes@^1.0.0:
1392 version "1.0.0"
1393 resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
1394
1395cli-cursor@^2.0.0, cli-cursor@^2.1.0:
1396 version "2.1.0"
1397 resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
1398 dependencies:
1399 restore-cursor "^2.0.0"
1400
1401cli-spinners@^1.0.0:
1402 version "1.0.0"
1403 resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.0.0.tgz#ef987ed3d48391ac3dab9180b406a742180d6e6a"
1404
1405cli-width@^1.0.1:
1406 version "1.1.1"
1407 resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-1.1.1.tgz#a4d293ef67ebb7b88d4a4d42c0ccf00c4d1e366d"
1408
1409cli-width@^2.0.0:
1410 version "2.2.0"
1411 resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
1412
1413cliui@^3.0.3, cliui@^3.2.0:
1414 version "3.2.0"
1415 resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
1416 dependencies:
1417 string-width "^1.0.1"
1418 strip-ansi "^3.0.1"
1419 wrap-ansi "^2.0.0"
1420
1421clone-stats@^0.0.1:
1422 version "0.0.1"
1423 resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
1424
1425clone@^0.2.0:
1426 version "0.2.0"
1427 resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f"
1428
1429clone@^1.0.0, clone@^1.0.2:
1430 version "1.0.2"
1431 resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149"
1432
1433co@^4.6.0:
1434 version "4.6.0"
1435 resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
1436
1437code-point-at@^1.0.0:
1438 version "1.1.0"
1439 resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
1440
1441coin-hive-stratum@^1.0.0:
1442 version "1.0.0"
1443 resolved "https://registry.yarnpkg.com/coin-hive-stratum/-/coin-hive-stratum-1.0.0.tgz#b7714ae3537b5701e293d654c163f2b9bfa709cb"
1444 dependencies:
1445 minimist "^1.2.0"
1446 ws "^3.2.0"
1447
1448coin-hive@^1.6.0:
1449 version "1.6.0"
1450 resolved "https://registry.yarnpkg.com/coin-hive/-/coin-hive-1.6.0.tgz#32149e7552dfddae15121a36604db0e64a10b1d1"
1451 dependencies:
1452 coin-hive-stratum "^1.0.0"
1453 elegant-spinner "^1.0.1"
1454 express "^4.15.4"
1455 log-update "^2.1.0"
1456 minimist "^1.2.0"
1457 puppeteer "^0.10.2"
1458 tty-table "^2.5.5"
1459
1460collection-map@^1.0.0:
1461 version "1.0.0"
1462 resolved "https://registry.yarnpkg.com/collection-map/-/collection-map-1.0.0.tgz#aea0f06f8d26c780c2b75494385544b2255af18c"
1463 dependencies:
1464 arr-map "^2.0.2"
1465 for-own "^1.0.0"
1466 make-iterator "^1.0.0"
1467
1468color-convert@^1.9.0:
1469 version "1.9.0"
1470 resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
1471 dependencies:
1472 color-name "^1.1.1"
1473
1474color-convert@~0.5.0:
1475 version "0.5.3"
1476 resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd"
1477
1478color-name@^1.1.1:
1479 version "1.1.3"
1480 resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
1481
1482colors@^1.1.2:
1483 version "1.1.2"
1484 resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
1485
1486combined-stream@^1.0.5, combined-stream@~1.0.5:
1487 version "1.0.5"
1488 resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
1489 dependencies:
1490 delayed-stream "~1.0.0"
1491
1492commander@^2.8.1, commander@^2.9.0:
1493 version "2.11.0"
1494 resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
1495
1496commondir@^1.0.1:
1497 version "1.0.1"
1498 resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
1499
1500compare-version@^0.1.2:
1501 version "0.1.2"
1502 resolved "https://registry.yarnpkg.com/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080"
1503
1504component-bind@1.0.0:
1505 version "1.0.0"
1506 resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
1507
1508component-emitter@1.1.2:
1509 version "1.1.2"
1510 resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3"
1511
1512component-emitter@1.2.1:
1513 version "1.2.1"
1514 resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
1515
1516component-inherit@0.0.3:
1517 version "0.0.3"
1518 resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
1519
1520concat-map@0.0.1:
1521 version "0.0.1"
1522 resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
1523
1524concat-stream@1.6.0, concat-stream@^1.6.0:
1525 version "1.6.0"
1526 resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
1527 dependencies:
1528 inherits "^2.0.3"
1529 readable-stream "^2.2.2"
1530 typedarray "^0.0.6"
1531
1532configstore@^3.0.0:
1533 version "3.1.1"
1534 resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.1.tgz#094ee662ab83fad9917678de114faaea8fcdca90"
1535 dependencies:
1536 dot-prop "^4.1.0"
1537 graceful-fs "^4.1.2"
1538 make-dir "^1.0.0"
1539 unique-string "^1.0.0"
1540 write-file-atomic "^2.0.0"
1541 xdg-basedir "^3.0.0"
1542
1543connect-inject@~0.3.2:
1544 version "0.3.2"
1545 resolved "https://registry.yarnpkg.com/connect-inject/-/connect-inject-0.3.2.tgz#d96b516a5b6bb24c2191e622b5e82cdd62844ba3"
1546
1547connect@~3.1.1:
1548 version "3.1.1"
1549 resolved "https://registry.yarnpkg.com/connect/-/connect-3.1.1.tgz#a73e2449c3efc2dfd1661865977a09184d120196"
1550 dependencies:
1551 debug "1.0.4"
1552 finalhandler "0.1.0"
1553 parseurl "~1.3.0"
1554 utils-merge "1.0.0"
1555
1556console-control-strings@^1.0.0, console-control-strings@~1.1.0:
1557 version "1.1.0"
1558 resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
1559
1560contains-path@^0.1.0:
1561 version "0.1.0"
1562 resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
1563
1564content-disposition@0.5.2:
1565 version "0.5.2"
1566 resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
1567
1568content-type@~1.0.4:
1569 version "1.0.4"
1570 resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
1571
1572conventional-commit-types@^2.0.0:
1573 version "2.2.0"
1574 resolved "https://registry.yarnpkg.com/conventional-commit-types/-/conventional-commit-types-2.2.0.tgz#5db95739d6c212acbe7b6f656a11b940baa68946"
1575
1576convert-source-map@^1.1.1, convert-source-map@^1.5.0:
1577 version "1.5.0"
1578 resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
1579
1580cookie-signature@1.0.6:
1581 version "1.0.6"
1582 resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
1583
1584cookie@0.3.1:
1585 version "0.3.1"
1586 resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
1587
1588copy-props@^1.4.1:
1589 version "1.6.0"
1590 resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-1.6.0.tgz#f0324bbee99771101e7b3ada112f313c393db8ed"
1591 dependencies:
1592 each-props "^1.2.1"
1593 is-plain-object "^2.0.1"
1594
1595core-js@^1.0.0:
1596 version "1.2.7"
1597 resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
1598
1599core-js@^2.4.0, core-js@^2.5.0:
1600 version "2.5.1"
1601 resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
1602
1603core-util-is@1.0.2, core-util-is@~1.0.0:
1604 version "1.0.2"
1605 resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
1606
1607create-error-class@^3.0.0:
1608 version "3.0.2"
1609 resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
1610 dependencies:
1611 capture-stack-trace "^1.0.0"
1612
1613create-react-class@^15.5.1, create-react-class@^15.5.2, create-react-class@^15.6.0:
1614 version "15.6.0"
1615 resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.0.tgz#ab448497c26566e1e29413e883207d57cfe7bed4"
1616 dependencies:
1617 fbjs "^0.8.9"
1618 loose-envify "^1.3.1"
1619 object-assign "^4.1.1"
1620
1621cross-env@^5.0.5:
1622 version "5.0.5"
1623 resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.0.5.tgz#4383d364d9660873dd185b398af3bfef5efffef3"
1624 dependencies:
1625 cross-spawn "^5.1.0"
1626 is-windows "^1.0.0"
1627
1628cross-spawn@^3.0.0:
1629 version "3.0.1"
1630 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
1631 dependencies:
1632 lru-cache "^4.0.1"
1633 which "^1.2.9"
1634
1635cross-spawn@^5.0.1, cross-spawn@^5.1.0:
1636 version "5.1.0"
1637 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
1638 dependencies:
1639 lru-cache "^4.0.1"
1640 shebang-command "^1.2.0"
1641 which "^1.2.9"
1642
1643cryptiles@2.x.x:
1644 version "2.0.5"
1645 resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
1646 dependencies:
1647 boom "2.x.x"
1648
1649crypto-random-string@^1.0.0:
1650 version "1.0.0"
1651 resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
1652
1653csv-generate@^1.0.0:
1654 version "1.0.0"
1655 resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-1.0.0.tgz#bd52886859d0c925f3e51f60f3abed262fa15caf"
1656
1657csv-parse@^1.2.0:
1658 version "1.2.3"
1659 resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-1.2.3.tgz#466206b51eaf77ccb50c3fadebd098cdeb2c69e7"
1660
1661csv-stringify@^1.0.0:
1662 version "1.0.4"
1663 resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-1.0.4.tgz#bc18bab9ad4cef3195fd257980b58b479c42d3e5"
1664 dependencies:
1665 lodash.get "^4.0.0"
1666
1667csv@^1.1.1:
1668 version "1.1.1"
1669 resolved "https://registry.yarnpkg.com/csv/-/csv-1.1.1.tgz#d9952d59b1f964a7afbcdd804d6818a73199a477"
1670 dependencies:
1671 csv-generate "^1.0.0"
1672 csv-parse "^1.2.0"
1673 csv-stringify "^1.0.0"
1674 stream-transform "^0.1.0"
1675
1676cuint@^0.2.1, cuint@^0.2.2:
1677 version "0.2.2"
1678 resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"
1679
1680currently-unhandled@^0.4.1:
1681 version "0.4.1"
1682 resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
1683 dependencies:
1684 array-find-index "^1.0.1"
1685
1686cz-conventional-changelog@^2.0.0:
1687 version "2.0.0"
1688 resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-2.0.0.tgz#55a979afdfe95e7024879d2a0f5924630170b533"
1689 dependencies:
1690 conventional-commit-types "^2.0.0"
1691 lodash.map "^4.5.1"
1692 longest "^1.0.1"
1693 pad-right "^0.2.2"
1694 right-pad "^1.0.1"
1695 word-wrap "^1.0.3"
1696
1697d@1:
1698 version "1.0.0"
1699 resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
1700 dependencies:
1701 es5-ext "^0.10.9"
1702
1703damerau-levenshtein@^1.0.0:
1704 version "1.0.4"
1705 resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514"
1706
1707dashdash@^1.12.0:
1708 version "1.14.1"
1709 resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
1710 dependencies:
1711 assert-plus "^1.0.0"
1712
1713dateformat@^2.0.0:
1714 version "2.0.0"
1715 resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.0.0.tgz#2743e3abb5c3fc2462e527dca445e04e9f4dee17"
1716
1717debug@1.0.4:
1718 version "1.0.4"
1719 resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.4.tgz#5b9c256bd54b6ec02283176fa8a0ede6d154cbf8"
1720 dependencies:
1721 ms "0.6.2"
1722
1723debug@2.2.0:
1724 version "2.2.0"
1725 resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
1726 dependencies:
1727 ms "0.7.1"
1728
1729debug@2.3.3:
1730 version "2.3.3"
1731 resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c"
1732 dependencies:
1733 ms "0.7.2"
1734
1735debug@2.6.9, debug@^2.4.1:
1736 version "2.6.9"
1737 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
1738 dependencies:
1739 ms "2.0.0"
1740
1741debug@^2.1.3, debug@^2.2.0, debug@^2.5.1, debug@^2.6.1, debug@^2.6.3, debug@^2.6.6, debug@^2.6.8:
1742 version "2.6.8"
1743 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
1744 dependencies:
1745 ms "2.0.0"
1746
1747debug@^3.0.1:
1748 version "3.0.1"
1749 resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.1.tgz#0564c612b521dc92d9f2988f0549e34f9c98db64"
1750 dependencies:
1751 ms "2.0.0"
1752
1753decamelize@^1.1.1, decamelize@^1.1.2:
1754 version "1.2.0"
1755 resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
1756
1757decompress-zip@0.3.0:
1758 version "0.3.0"
1759 resolved "https://registry.yarnpkg.com/decompress-zip/-/decompress-zip-0.3.0.tgz#ae3bcb7e34c65879adfe77e19c30f86602b4bdb0"
1760 dependencies:
1761 binary "^0.3.0"
1762 graceful-fs "^4.1.3"
1763 mkpath "^0.1.0"
1764 nopt "^3.0.1"
1765 q "^1.1.2"
1766 readable-stream "^1.1.8"
1767 touch "0.0.3"
1768
1769deep-equal@^1.0.1:
1770 version "1.0.1"
1771 resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
1772
1773deep-extend@~0.4.0:
1774 version "0.4.2"
1775 resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
1776
1777deep-is@~0.1.3:
1778 version "0.1.3"
1779 resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
1780
1781default-resolution@^2.0.0:
1782 version "2.0.0"
1783 resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684"
1784
1785defaults@^1.0.3:
1786 version "1.0.3"
1787 resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
1788 dependencies:
1789 clone "^1.0.2"
1790
1791define-properties@^1.1.2:
1792 version "1.1.2"
1793 resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
1794 dependencies:
1795 foreach "^2.0.5"
1796 object-keys "^1.0.8"
1797
1798del@^2.0.2, del@^2.2.2:
1799 version "2.2.2"
1800 resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
1801 dependencies:
1802 globby "^5.0.0"
1803 is-path-cwd "^1.0.0"
1804 is-path-in-cwd "^1.0.0"
1805 object-assign "^4.0.1"
1806 pify "^2.0.0"
1807 pinkie-promise "^2.0.0"
1808 rimraf "^2.2.8"
1809
1810delayed-stream@~1.0.0:
1811 version "1.0.0"
1812 resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
1813
1814delegates@^1.0.0:
1815 version "1.0.0"
1816 resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
1817
1818depd@0.4.4:
1819 version "0.4.4"
1820 resolved "https://registry.yarnpkg.com/depd/-/depd-0.4.4.tgz#07091fae75f97828d89b4a02a2d4778f0e7c0662"
1821
1822depd@1.1.1, depd@~1.1.1:
1823 version "1.1.1"
1824 resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
1825
1826destroy@1.0.3:
1827 version "1.0.3"
1828 resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.3.tgz#b433b4724e71fd8551d9885174851c5fc377e2c9"
1829
1830destroy@~1.0.4:
1831 version "1.0.4"
1832 resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
1833
1834detect-file@^0.1.0:
1835 version "0.1.0"
1836 resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-0.1.0.tgz#4935dedfd9488648e006b0129566e9386711ea63"
1837 dependencies:
1838 fs-exists-sync "^0.1.0"
1839
1840detect-indent@^4.0.0:
1841 version "4.0.0"
1842 resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
1843 dependencies:
1844 repeating "^2.0.0"
1845
1846doctrine@1.5.0, doctrine@^1.2.2:
1847 version "1.5.0"
1848 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
1849 dependencies:
1850 esutils "^2.0.2"
1851 isarray "^1.0.0"
1852
1853doctrine@^2.0.0:
1854 version "2.0.0"
1855 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
1856 dependencies:
1857 esutils "^2.0.2"
1858 isarray "^1.0.0"
1859
1860dom-helpers@^3.2.0:
1861 version "3.2.1"
1862 resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a"
1863
1864dot-prop@^4.1.0:
1865 version "4.2.0"
1866 resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57"
1867 dependencies:
1868 is-obj "^1.0.0"
1869
1870dotenv@^4.0.0:
1871 version "4.0.0"
1872 resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d"
1873
1874duplexer2@0.0.2, duplexer2@~0.0.2:
1875 version "0.0.2"
1876 resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db"
1877 dependencies:
1878 readable-stream "~1.1.9"
1879
1880duplexer3@^0.1.4:
1881 version "0.1.4"
1882 resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
1883
1884duplexify@^3.2.0:
1885 version "3.5.1"
1886 resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.1.tgz#4e1516be68838bc90a49994f0b39a6e5960befcd"
1887 dependencies:
1888 end-of-stream "^1.0.0"
1889 inherits "^2.0.1"
1890 readable-stream "^2.0.0"
1891 stream-shift "^1.0.0"
1892
1893each-props@^1.2.1:
1894 version "1.3.1"
1895 resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.1.tgz#fc138f51e3a2774286d4858e02d6e7de462de158"
1896 dependencies:
1897 is-plain-object "^2.0.1"
1898 object.defaults "^1.1.0"
1899
1900ecc-jsbn@~0.1.1:
1901 version "0.1.1"
1902 resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
1903 dependencies:
1904 jsbn "~0.1.0"
1905
1906ecdsa-sig-formatter@1.0.9:
1907 version "1.0.9"
1908 resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1"
1909 dependencies:
1910 base64url "^2.0.0"
1911 safe-buffer "^5.0.1"
1912
1913editor@^1.0.0:
1914 version "1.0.0"
1915 resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742"
1916
1917ee-first@1.0.5:
1918 version "1.0.5"
1919 resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.0.5.tgz#8c9b212898d8cd9f1a9436650ce7be202c9e9ff0"
1920
1921ee-first@1.1.1:
1922 version "1.1.1"
1923 resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
1924
1925electron-builder-http@19.15.0, electron-builder-http@~19.15.0:
1926 version "19.15.0"
1927 resolved "https://registry.yarnpkg.com/electron-builder-http/-/electron-builder-http-19.15.0.tgz#d34cc28cf9db9ad28494283be8116e83a2e78f4c"
1928 dependencies:
1929 bluebird-lst "^1.0.2"
1930 debug "^2.6.8"
1931 fs-extra-p "^4.3.0"
1932
1933electron-builder-http@~19.27.5:
1934 version "19.27.5"
1935 resolved "https://registry.yarnpkg.com/electron-builder-http/-/electron-builder-http-19.27.5.tgz#800865df2e618ffab9e5b3b895c15b4ce7fd7f17"
1936 dependencies:
1937 bluebird-lst "^1.0.3"
1938 debug "^3.0.1"
1939 fs-extra-p "^4.4.0"
1940
1941electron-builder-util@19.15.0, electron-builder-util@~19.15.0:
1942 version "19.15.0"
1943 resolved "https://registry.yarnpkg.com/electron-builder-util/-/electron-builder-util-19.15.0.tgz#3df92f43b13d1aa3fc4823a05cca96619892bff2"
1944 dependencies:
1945 "7zip-bin" "^2.1.0"
1946 bluebird-lst "^1.0.2"
1947 chalk "^2.0.1"
1948 debug "^2.6.8"
1949 electron-builder-http "~19.15.0"
1950 fcopy-pre-bundled "0.3.4"
1951 fs-extra-p "^4.3.0"
1952 ini "^1.3.4"
1953 is-ci "^1.0.10"
1954 node-emoji "^1.6.1"
1955 source-map-support "^0.4.15"
1956 stat-mode "^0.2.2"
1957 tunnel-agent "^0.6.0"
1958
1959electron-builder@19.15.1:
1960 version "19.15.1"
1961 resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-19.15.1.tgz#da5d61fbae512edbc5b0251f6d9cb7774830eede"
1962 dependencies:
1963 "7zip-bin" "^2.1.0"
1964 ajv "^5.2.1"
1965 ajv-keywords "^2.1.0"
1966 asar-integrity "0.1.1"
1967 bluebird-lst "^1.0.2"
1968 chalk "^2.0.1"
1969 chromium-pickle-js "^0.2.0"
1970 cuint "^0.2.2"
1971 debug "^2.6.8"
1972 electron-builder-http "19.15.0"
1973 electron-builder-util "19.15.0"
1974 electron-download-tf "4.3.1"
1975 electron-osx-sign "0.4.6"
1976 electron-publish "19.15.0"
1977 fs-extra-p "^4.3.0"
1978 hosted-git-info "^2.5.0"
1979 is-ci "^1.0.10"
1980 isbinaryfile "^3.0.2"
1981 js-yaml "^3.9.0"
1982 json5 "^0.5.1"
1983 minimatch "^3.0.4"
1984 normalize-package-data "^2.4.0"
1985 parse-color "^1.0.0"
1986 plist "^2.1.0"
1987 sanitize-filename "^1.6.1"
1988 semver "^5.3.0"
1989 update-notifier "^2.2.0"
1990 uuid-1345 "^0.99.6"
1991 yargs "^8.0.2"
1992
1993electron-download-tf@4.3.1:
1994 version "4.3.1"
1995 resolved "https://registry.yarnpkg.com/electron-download-tf/-/electron-download-tf-4.3.1.tgz#7930f24a08e3669eaad38a5f7f288a10461caf72"
1996 dependencies:
1997 debug "^2.6.6"
1998 env-paths "^1.0.0"
1999 fs-extra "^3.0.1"
2000 minimist "^1.2.0"
2001 nugget "^2.0.1"
2002 path-exists "^3.0.0"
2003 rc "^1.2.1"
2004 semver "^5.3.0"
2005 sumchecker "^2.0.2"
2006
2007electron-download@^3.0.1:
2008 version "3.3.0"
2009 resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-3.3.0.tgz#2cfd54d6966c019c4d49ad65fbe65cc9cdef68c8"
2010 dependencies:
2011 debug "^2.2.0"
2012 fs-extra "^0.30.0"
2013 home-path "^1.0.1"
2014 minimist "^1.2.0"
2015 nugget "^2.0.0"
2016 path-exists "^2.1.0"
2017 rc "^1.1.2"
2018 semver "^5.3.0"
2019 sumchecker "^1.2.0"
2020
2021electron-download@^4.0.0:
2022 version "4.1.0"
2023 resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.0.tgz#bf932c746f2f87ffcc09d1dd472f2ff6b9187845"
2024 dependencies:
2025 debug "^2.2.0"
2026 env-paths "^1.0.0"
2027 fs-extra "^2.0.0"
2028 minimist "^1.2.0"
2029 nugget "^2.0.0"
2030 path-exists "^3.0.0"
2031 rc "^1.1.2"
2032 semver "^5.3.0"
2033 sumchecker "^2.0.1"
2034
2035electron-fetch@^1.1.0:
2036 version "1.1.0"
2037 resolved "https://registry.yarnpkg.com/electron-fetch/-/electron-fetch-1.1.0.tgz#74b0ea547fe149620d38596a84fb104d34218e31"
2038 dependencies:
2039 encoding "^0.1.12"
2040
2041electron-is-dev@^0.3.0:
2042 version "0.3.0"
2043 resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe"
2044
2045electron-osx-sign@0.4.6, electron-osx-sign@^0.4.1:
2046 version "0.4.6"
2047 resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.4.6.tgz#2398e2d7cab5c1d8c3eeabb1cd490376528ec39a"
2048 dependencies:
2049 bluebird "^3.4.7"
2050 compare-version "^0.1.2"
2051 debug "^2.6.1"
2052 isbinaryfile "^3.0.2"
2053 minimist "^1.2.0"
2054 plist "^2.0.1"
2055 tempfile "^1.1.1"
2056
2057electron-packager@^8.7.0:
2058 version "8.7.2"
2059 resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-8.7.2.tgz#457d3bf24bc9607c06ad4b1eb6daa4accadc2108"
2060 dependencies:
2061 asar "^0.13.0"
2062 debug "^2.2.0"
2063 electron-download "^4.0.0"
2064 electron-osx-sign "^0.4.1"
2065 extract-zip "^1.0.3"
2066 fs-extra "^3.0.0"
2067 get-package-info "^1.0.0"
2068 minimist "^1.1.1"
2069 plist "^2.0.0"
2070 rcedit "^0.9.0"
2071 resolve "^1.1.6"
2072 run-series "^1.1.1"
2073 sanitize-filename "^1.6.0"
2074 semver "^5.3.0"
2075
2076electron-publish@19.15.0:
2077 version "19.15.0"
2078 resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-19.15.0.tgz#8bed54b827a161c325cb8739957c68fa2ca1092a"
2079 dependencies:
2080 bluebird-lst "^1.0.2"
2081 chalk "^2.0.1"
2082 electron-builder-http "~19.15.0"
2083 electron-builder-util "~19.15.0"
2084 fs-extra-p "^4.3.0"
2085 mime "^1.3.6"
2086
2087electron-rebuild@^1.6.0:
2088 version "1.6.0"
2089 resolved "https://registry.yarnpkg.com/electron-rebuild/-/electron-rebuild-1.6.0.tgz#e8d26f4d8e9fe5388df35864b3658e5cfd4dcb7e"
2090 dependencies:
2091 colors "^1.1.2"
2092 debug "^2.6.3"
2093 fs-extra "^3.0.1"
2094 node-abi "^2.0.0"
2095 node-gyp "^3.6.0"
2096 ora "^1.2.0"
2097 rimraf "^2.6.1"
2098 spawn-rx "^2.0.10"
2099 yargs "^7.0.2"
2100
2101electron-remote@^1.1.1:
2102 version "1.2.0"
2103 resolved "https://registry.yarnpkg.com/electron-remote/-/electron-remote-1.2.0.tgz#0f00c1d3803ce7651117f6fb6f274d26781ef9bd"
2104 dependencies:
2105 debug "^2.5.1"
2106 hashids "^1.1.1"
2107 lodash.get "^4.4.2"
2108 pify "^2.3.0"
2109 rxjs "^5.0.0-beta.12"
2110 xmlhttprequest "^1.8.0"
2111
2112electron-spellchecker@^1.2.0:
2113 version "1.2.0"
2114 resolved "https://registry.yarnpkg.com/electron-spellchecker/-/electron-spellchecker-1.2.0.tgz#f6306afd4078244c1e6311370667d95b873fbcbb"
2115 dependencies:
2116 "@paulcbetts/cld" "^2.4.6"
2117 "@paulcbetts/spellchecker" "^4.0.5"
2118 bcp47 "^1.1.2"
2119 debug "^2.6.3"
2120 electron-remote "^1.1.1"
2121 keyboard-layout "^2.0.7"
2122 lru-cache "^4.0.2"
2123 mkdirp "^0.5.1"
2124 pify "^2.3.0"
2125 rxjs "^5.0.1"
2126 rxjs-serial-subscription "^0.1.1"
2127 spawn-rx "^2.0.7"
2128
2129electron-squirrel-startup@^1.0.0:
2130 version "1.0.0"
2131 resolved "https://registry.yarnpkg.com/electron-squirrel-startup/-/electron-squirrel-startup-1.0.0.tgz#19b4e55933fa0ef8f556784b9c660f772546a0b8"
2132 dependencies:
2133 debug "^2.2.0"
2134
2135electron-to-chromium@^1.3.18:
2136 version "1.3.20"
2137 resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.20.tgz#2eedd5ccbae7ddc557f68ad1fce9c172e915e4e5"
2138
2139electron-updater@^2.4.3:
2140 version "2.8.9"
2141 resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-2.8.9.tgz#e2525dcbd7c27ff173bdfd2e87056d67310e2555"
2142 dependencies:
2143 bluebird-lst "^1.0.3"
2144 debug "^3.0.1"
2145 electron-builder-http "~19.27.5"
2146 electron-is-dev "^0.3.0"
2147 fs-extra-p "^4.4.0"
2148 js-yaml "^3.9.1"
2149 lazy-val "^1.0.2"
2150 lodash.isequal "^4.5.0"
2151 semver "^5.4.1"
2152 source-map-support "^0.4.16"
2153 uuid-1345 "^0.99.6"
2154 xelement "^1.0.16"
2155
2156electron-window-state@^4.1.0:
2157 version "4.1.1"
2158 resolved "https://registry.yarnpkg.com/electron-window-state/-/electron-window-state-4.1.1.tgz#6b34fdc31b38514dfec8b7c8f7b5d4addb67632d"
2159 dependencies:
2160 deep-equal "^1.0.1"
2161 jsonfile "^2.2.3"
2162 mkdirp "^0.5.1"
2163
2164electron@^1.7.6:
2165 version "1.7.6"
2166 resolved "https://registry.yarnpkg.com/electron/-/electron-1.7.6.tgz#fb69ea31bd03df0eff247f26f0b538bd29b6ee72"
2167 dependencies:
2168 "@types/node" "^7.0.18"
2169 electron-download "^3.0.1"
2170 extract-zip "^1.0.3"
2171
2172elegant-spinner@^1.0.1:
2173 version "1.0.1"
2174 resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
2175
2176emojis-list@^2.0.0:
2177 version "2.1.0"
2178 resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
2179
2180encodeurl@~1.0.1:
2181 version "1.0.1"
2182 resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
2183
2184encoding@^0.1.11, encoding@^0.1.12:
2185 version "0.1.12"
2186 resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
2187 dependencies:
2188 iconv-lite "~0.4.13"
2189
2190end-of-stream@^1.0.0, end-of-stream@^1.1.0:
2191 version "1.4.0"
2192 resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206"
2193 dependencies:
2194 once "^1.4.0"
2195
2196engine.io-client@~1.8.4:
2197 version "1.8.4"
2198 resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.4.tgz#9fe85dee25853ca6babe25bd2ad68710863e91c2"
2199 dependencies:
2200 component-emitter "1.2.1"
2201 component-inherit "0.0.3"
2202 debug "2.3.3"
2203 engine.io-parser "1.3.2"
2204 has-cors "1.1.0"
2205 indexof "0.0.1"
2206 parsejson "0.0.3"
2207 parseqs "0.0.5"
2208 parseuri "0.0.5"
2209 ws "1.1.2"
2210 xmlhttprequest-ssl "1.5.3"
2211 yeast "0.1.2"
2212
2213engine.io-parser@1.3.2:
2214 version "1.3.2"
2215 resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.2.tgz#937b079f0007d0893ec56d46cb220b8cb435220a"
2216 dependencies:
2217 after "0.8.2"
2218 arraybuffer.slice "0.0.6"
2219 base64-arraybuffer "0.1.5"
2220 blob "0.0.4"
2221 has-binary "0.1.7"
2222 wtf-8 "1.0.0"
2223
2224engine.io@~1.8.4:
2225 version "1.8.4"
2226 resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.4.tgz#77bce12b80e5d60429337fec3b0daf691ebc9003"
2227 dependencies:
2228 accepts "1.3.3"
2229 base64id "1.0.0"
2230 cookie "0.3.1"
2231 debug "2.3.3"
2232 engine.io-parser "1.3.2"
2233 ws "1.1.4"
2234
2235env-paths@^1.0.0:
2236 version "1.0.0"
2237 resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
2238
2239error-ex@^1.2.0:
2240 version "1.3.1"
2241 resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
2242 dependencies:
2243 is-arrayish "^0.2.1"
2244
2245es-abstract@^1.7.0:
2246 version "1.8.1"
2247 resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.8.1.tgz#fd85a3bdfa67786ce7be7e1584678e119cd70c04"
2248 dependencies:
2249 es-to-primitive "^1.1.1"
2250 function-bind "^1.1.1"
2251 has "^1.0.1"
2252 is-callable "^1.1.3"
2253 is-regex "^1.0.4"
2254
2255es-to-primitive@^1.1.1:
2256 version "1.1.1"
2257 resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
2258 dependencies:
2259 is-callable "^1.1.1"
2260 is-date-object "^1.0.1"
2261 is-symbol "^1.0.1"
2262
2263es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
2264 version "0.10.30"
2265 resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.30.tgz#7141a16836697dbabfaaaeee41495ce29f52c939"
2266 dependencies:
2267 es6-iterator "2"
2268 es6-symbol "~3.1"
2269
2270es6-iterator@2, es6-iterator@^2.0.1:
2271 version "2.0.1"
2272 resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512"
2273 dependencies:
2274 d "1"
2275 es5-ext "^0.10.14"
2276 es6-symbol "^3.1"
2277
2278es6-promise@^4.0.3, es6-promise@^4.0.5:
2279 version "4.1.1"
2280 resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a"
2281
2282es6-promisify@^5.0.0:
2283 version "5.0.0"
2284 resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
2285 dependencies:
2286 es6-promise "^4.0.3"
2287
2288es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1:
2289 version "3.1.1"
2290 resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
2291 dependencies:
2292 d "1"
2293 es5-ext "~0.10.14"
2294
2295es6-weak-map@^2.0.1:
2296 version "2.0.2"
2297 resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f"
2298 dependencies:
2299 d "1"
2300 es5-ext "^0.10.14"
2301 es6-iterator "^2.0.1"
2302 es6-symbol "^3.1.1"
2303
2304escape-html@1.0.1:
2305 version "1.0.1"
2306 resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.1.tgz#181a286ead397a39a92857cfb1d43052e356bff0"
2307
2308escape-html@~1.0.3:
2309 version "1.0.3"
2310 resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
2311
2312escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
2313 version "1.0.5"
2314 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
2315
2316eslint-config-airbnb-base@^11.1.0:
2317 version "11.3.2"
2318 resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.3.2.tgz#8703b11abe3c88ac7ec2b745b7fdf52e00ae680a"
2319 dependencies:
2320 eslint-restricted-globals "^0.1.1"
2321
2322eslint-config-airbnb@^14.1.0:
2323 version "14.1.0"
2324 resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-14.1.0.tgz#355d290040bbf8e00bf8b4b19f4b70cbe7c2317f"
2325 dependencies:
2326 eslint-config-airbnb-base "^11.1.0"
2327
2328eslint-import-resolver-node@^0.3.1:
2329 version "0.3.1"
2330 resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc"
2331 dependencies:
2332 debug "^2.6.8"
2333 resolve "^1.2.0"
2334
2335eslint-loader@^1.9.0:
2336 version "1.9.0"
2337 resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-1.9.0.tgz#7e1be9feddca328d3dcfaef1ad49d5beffe83a13"
2338 dependencies:
2339 loader-fs-cache "^1.0.0"
2340 loader-utils "^1.0.2"
2341 object-assign "^4.0.1"
2342 object-hash "^1.1.4"
2343 rimraf "^2.6.1"
2344
2345eslint-module-utils@^2.1.1:
2346 version "2.1.1"
2347 resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449"
2348 dependencies:
2349 debug "^2.6.8"
2350 pkg-dir "^1.0.0"
2351
2352eslint-plugin-import@^2.2.0:
2353 version "2.7.0"
2354 resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz#21de33380b9efb55f5ef6d2e210ec0e07e7fa69f"
2355 dependencies:
2356 builtin-modules "^1.1.1"
2357 contains-path "^0.1.0"
2358 debug "^2.6.8"
2359 doctrine "1.5.0"
2360 eslint-import-resolver-node "^0.3.1"
2361 eslint-module-utils "^2.1.1"
2362 has "^1.0.1"
2363 lodash.cond "^4.3.0"
2364 minimatch "^3.0.3"
2365 read-pkg-up "^2.0.0"
2366
2367eslint-plugin-jsx-a11y@^3.0.0:
2368 version "3.0.2"
2369 resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-3.0.2.tgz#9f0eabcafde3d2a2600d96a66adb90d099e841fe"
2370 dependencies:
2371 damerau-levenshtein "^1.0.0"
2372 jsx-ast-utils "^1.0.0"
2373 object-assign "^4.0.1"
2374
2375eslint-plugin-react@^6.10.0:
2376 version "6.10.3"
2377 resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz#c5435beb06774e12c7db2f6abaddcbf900cd3f78"
2378 dependencies:
2379 array.prototype.find "^2.0.1"
2380 doctrine "^1.2.2"
2381 has "^1.0.1"
2382 jsx-ast-utils "^1.3.4"
2383 object.assign "^4.0.4"
2384
2385eslint-restricted-globals@^0.1.1:
2386 version "0.1.1"
2387 resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7"
2388
2389eslint-scope@^3.7.1:
2390 version "3.7.1"
2391 resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
2392 dependencies:
2393 esrecurse "^4.1.0"
2394 estraverse "^4.1.1"
2395
2396eslint@^4.7.1:
2397 version "4.7.1"
2398 resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.7.1.tgz#849804136953ebe366782f9f8611e2cbd1b54681"
2399 dependencies:
2400 ajv "^5.2.0"
2401 babel-code-frame "^6.22.0"
2402 chalk "^2.1.0"
2403 concat-stream "^1.6.0"
2404 cross-spawn "^5.1.0"
2405 debug "^3.0.1"
2406 doctrine "^2.0.0"
2407 eslint-scope "^3.7.1"
2408 espree "^3.5.1"
2409 esquery "^1.0.0"
2410 estraverse "^4.2.0"
2411 esutils "^2.0.2"
2412 file-entry-cache "^2.0.0"
2413 functional-red-black-tree "^1.0.1"
2414 glob "^7.1.2"
2415 globals "^9.17.0"
2416 ignore "^3.3.3"
2417 imurmurhash "^0.1.4"
2418 inquirer "^3.0.6"
2419 is-resolvable "^1.0.0"
2420 js-yaml "^3.9.1"
2421 json-stable-stringify "^1.0.1"
2422 levn "^0.3.0"
2423 lodash "^4.17.4"
2424 minimatch "^3.0.2"
2425 mkdirp "^0.5.1"
2426 natural-compare "^1.4.0"
2427 optionator "^0.8.2"
2428 path-is-inside "^1.0.2"
2429 pluralize "^7.0.0"
2430 progress "^2.0.0"
2431 require-uncached "^1.0.3"
2432 semver "^5.3.0"
2433 strip-ansi "^4.0.0"
2434 strip-json-comments "~2.0.1"
2435 table "^4.0.1"
2436 text-table "~0.2.0"
2437
2438espree@^3.5.1:
2439 version "3.5.1"
2440 resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.1.tgz#0c988b8ab46db53100a1954ae4ba995ddd27d87e"
2441 dependencies:
2442 acorn "^5.1.1"
2443 acorn-jsx "^3.0.0"
2444
2445esprima@^4.0.0:
2446 version "4.0.0"
2447 resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
2448
2449esquery@^1.0.0:
2450 version "1.0.0"
2451 resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa"
2452 dependencies:
2453 estraverse "^4.0.0"
2454
2455esrecurse@^4.1.0:
2456 version "4.2.0"
2457 resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163"
2458 dependencies:
2459 estraverse "^4.1.0"
2460 object-assign "^4.0.1"
2461
2462estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
2463 version "4.2.0"
2464 resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
2465
2466esutils@^2.0.2:
2467 version "2.0.2"
2468 resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
2469
2470etag@~1.8.1:
2471 version "1.8.1"
2472 resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
2473
2474event-kit@^2.0.0:
2475 version "2.3.0"
2476 resolved "https://registry.yarnpkg.com/event-kit/-/event-kit-2.3.0.tgz#459ba0646d4b7dbca5d9bf2b3c4e2d0103e85e15"
2477
2478execa@^0.7.0:
2479 version "0.7.0"
2480 resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
2481 dependencies:
2482 cross-spawn "^5.0.1"
2483 get-stream "^3.0.0"
2484 is-stream "^1.1.0"
2485 npm-run-path "^2.0.0"
2486 p-finally "^1.0.0"
2487 signal-exit "^3.0.0"
2488 strip-eof "^1.0.0"
2489
2490expand-brackets@^0.1.4:
2491 version "0.1.5"
2492 resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
2493 dependencies:
2494 is-posix-bracket "^0.1.0"
2495
2496expand-range@^1.8.1:
2497 version "1.8.2"
2498 resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
2499 dependencies:
2500 fill-range "^2.1.0"
2501
2502expand-tilde@^1.2.2:
2503 version "1.2.2"
2504 resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449"
2505 dependencies:
2506 os-homedir "^1.0.1"
2507
2508expand-tilde@^2.0.2:
2509 version "2.0.2"
2510 resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
2511 dependencies:
2512 homedir-polyfill "^1.0.1"
2513
2514express@^4.15.4:
2515 version "4.16.1"
2516 resolved "https://registry.yarnpkg.com/express/-/express-4.16.1.tgz#6b33b560183c9b253b7b62144df33a4654ac9ed0"
2517 dependencies:
2518 accepts "~1.3.4"
2519 array-flatten "1.1.1"
2520 body-parser "1.18.2"
2521 content-disposition "0.5.2"
2522 content-type "~1.0.4"
2523 cookie "0.3.1"
2524 cookie-signature "1.0.6"
2525 debug "2.6.9"
2526 depd "~1.1.1"
2527 encodeurl "~1.0.1"
2528 escape-html "~1.0.3"
2529 etag "~1.8.1"
2530 finalhandler "1.1.0"
2531 fresh "0.5.2"
2532 merge-descriptors "1.0.1"
2533 methods "~1.1.2"
2534 on-finished "~2.3.0"
2535 parseurl "~1.3.2"
2536 path-to-regexp "0.1.7"
2537 proxy-addr "~2.0.2"
2538 qs "6.5.1"
2539 range-parser "~1.2.0"
2540 safe-buffer "5.1.1"
2541 send "0.16.1"
2542 serve-static "1.13.1"
2543 setprototypeof "1.1.0"
2544 statuses "~1.3.1"
2545 type-is "~1.6.15"
2546 utils-merge "1.0.1"
2547 vary "~1.1.2"
2548
2549extend-shallow@^2.0.1:
2550 version "2.0.1"
2551 resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
2552 dependencies:
2553 is-extendable "^0.1.0"
2554
2555extend@^3.0.0, extend@~3.0.0:
2556 version "3.0.1"
2557 resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
2558
2559external-editor@^2.0.4:
2560 version "2.0.5"
2561 resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.5.tgz#52c249a3981b9ba187c7cacf5beb50bf1d91a6bc"
2562 dependencies:
2563 iconv-lite "^0.4.17"
2564 jschardet "^1.4.2"
2565 tmp "^0.0.33"
2566
2567extglob@^0.3.1:
2568 version "0.3.2"
2569 resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
2570 dependencies:
2571 is-extglob "^1.0.0"
2572
2573extract-zip@^1.0.3, extract-zip@^1.6.5:
2574 version "1.6.5"
2575 resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.5.tgz#99a06735b6ea20ea9b705d779acffcc87cff0440"
2576 dependencies:
2577 concat-stream "1.6.0"
2578 debug "2.2.0"
2579 mkdirp "0.5.0"
2580 yauzl "2.4.1"
2581
2582extsprintf@1.3.0, extsprintf@^1.2.0:
2583 version "1.3.0"
2584 resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
2585
2586fancy-log@^1.1.0:
2587 version "1.3.0"
2588 resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.0.tgz#45be17d02bb9917d60ccffd4995c999e6c8c9948"
2589 dependencies:
2590 chalk "^1.1.1"
2591 time-stamp "^1.0.0"
2592
2593fast-deep-equal@^1.0.0:
2594 version "1.0.0"
2595 resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
2596
2597fast-levenshtein@~2.0.4:
2598 version "2.0.6"
2599 resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
2600
2601fbjs@^0.8.9:
2602 version "0.8.14"
2603 resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.14.tgz#d1dbe2be254c35a91e09f31f9cd50a40b2a0ed1c"
2604 dependencies:
2605 core-js "^1.0.0"
2606 isomorphic-fetch "^2.1.1"
2607 loose-envify "^1.0.0"
2608 object-assign "^4.1.0"
2609 promise "^7.1.1"
2610 setimmediate "^1.0.5"
2611 ua-parser-js "^0.7.9"
2612
2613fcopy-pre-bundled@0.3.4:
2614 version "0.3.4"
2615 resolved "https://registry.yarnpkg.com/fcopy-pre-bundled/-/fcopy-pre-bundled-0.3.4.tgz#7ff1a1c339e877baa86b0856bebb33621cd5620b"
2616
2617fd-slicer@~1.0.1:
2618 version "1.0.1"
2619 resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
2620 dependencies:
2621 pend "~1.2.0"
2622
2623figures@^1.3.5:
2624 version "1.7.0"
2625 resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
2626 dependencies:
2627 escape-string-regexp "^1.0.5"
2628 object-assign "^4.1.0"
2629
2630figures@^2.0.0:
2631 version "2.0.0"
2632 resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
2633 dependencies:
2634 escape-string-regexp "^1.0.5"
2635
2636file-entry-cache@^2.0.0:
2637 version "2.0.0"
2638 resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
2639 dependencies:
2640 flat-cache "^1.2.1"
2641 object-assign "^4.0.1"
2642
2643filename-regex@^2.0.0:
2644 version "2.0.1"
2645 resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
2646
2647fill-range@^2.1.0:
2648 version "2.2.3"
2649 resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
2650 dependencies:
2651 is-number "^2.1.0"
2652 isobject "^2.0.0"
2653 randomatic "^1.1.3"
2654 repeat-element "^1.1.2"
2655 repeat-string "^1.5.2"
2656
2657finalhandler@0.1.0:
2658 version "0.1.0"
2659 resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.1.0.tgz#da05bbc4f5f4a30c84ce1d91f3c154007c4e9daa"
2660 dependencies:
2661 debug "1.0.4"
2662 escape-html "1.0.1"
2663
2664finalhandler@1.1.0:
2665 version "1.1.0"
2666 resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5"
2667 dependencies:
2668 debug "2.6.9"
2669 encodeurl "~1.0.1"
2670 escape-html "~1.0.3"
2671 on-finished "~2.3.0"
2672 parseurl "~1.3.2"
2673 statuses "~1.3.1"
2674 unpipe "~1.0.0"
2675
2676find-cache-dir@^0.1.1:
2677 version "0.1.1"
2678 resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9"
2679 dependencies:
2680 commondir "^1.0.1"
2681 mkdirp "^0.5.1"
2682 pkg-dir "^1.0.0"
2683
2684find-index@^0.1.1:
2685 version "0.1.1"
2686 resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4"
2687
2688find-up@^1.0.0:
2689 version "1.1.2"
2690 resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
2691 dependencies:
2692 path-exists "^2.0.0"
2693 pinkie-promise "^2.0.0"
2694
2695find-up@^2.0.0:
2696 version "2.1.0"
2697 resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
2698 dependencies:
2699 locate-path "^2.0.0"
2700
2701findup-sync@^0.4.2:
2702 version "0.4.3"
2703 resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12"
2704 dependencies:
2705 detect-file "^0.1.0"
2706 is-glob "^2.0.1"
2707 micromatch "^2.3.7"
2708 resolve-dir "^0.1.0"
2709
2710findup-sync@~0.3.0:
2711 version "0.3.0"
2712 resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16"
2713 dependencies:
2714 glob "~5.0.0"
2715
2716fined@^1.0.1:
2717 version "1.1.0"
2718 resolved "https://registry.yarnpkg.com/fined/-/fined-1.1.0.tgz#b37dc844b76a2f5e7081e884f7c0ae344f153476"
2719 dependencies:
2720 expand-tilde "^2.0.2"
2721 is-plain-object "^2.0.3"
2722 object.defaults "^1.1.0"
2723 object.pick "^1.2.0"
2724 parse-filepath "^1.0.1"
2725
2726first-chunk-stream@^1.0.0:
2727 version "1.0.0"
2728 resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e"
2729
2730flagged-respawn@^0.3.2:
2731 version "0.3.2"
2732 resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-0.3.2.tgz#ff191eddcd7088a675b2610fffc976be9b8074b5"
2733
2734flat-cache@^1.2.1:
2735 version "1.2.2"
2736 resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96"
2737 dependencies:
2738 circular-json "^0.3.1"
2739 del "^2.0.2"
2740 graceful-fs "^4.1.2"
2741 write "^0.2.1"
2742
2743for-in@^1.0.1:
2744 version "1.0.2"
2745 resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
2746
2747for-own@^0.1.4:
2748 version "0.1.5"
2749 resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
2750 dependencies:
2751 for-in "^1.0.1"
2752
2753for-own@^1.0.0:
2754 version "1.0.0"
2755 resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b"
2756 dependencies:
2757 for-in "^1.0.1"
2758
2759foreach@^2.0.5:
2760 version "2.0.5"
2761 resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
2762
2763forever-agent@~0.6.1:
2764 version "0.6.1"
2765 resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
2766
2767form-data@~2.1.1:
2768 version "2.1.4"
2769 resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
2770 dependencies:
2771 asynckit "^0.4.0"
2772 combined-stream "^1.0.5"
2773 mime-types "^2.1.12"
2774
2775forwarded@~0.1.2:
2776 version "0.1.2"
2777 resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
2778
2779fresh@0.2.2:
2780 version "0.2.2"
2781 resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.2.2.tgz#9731dcf5678c7faeb44fb903c4f72df55187fa77"
2782
2783fresh@0.5.2:
2784 version "0.5.2"
2785 resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
2786
2787fs-exists-sync@^0.1.0:
2788 version "0.1.0"
2789 resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
2790
2791fs-extra-p@^4.3.0, fs-extra-p@^4.4.0:
2792 version "4.4.0"
2793 resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-4.4.0.tgz#729c601c4f4c701328921adc7cfe9b236f100660"
2794 dependencies:
2795 bluebird-lst "^1.0.2"
2796 fs-extra "^4.0.0"
2797
2798fs-extra@0.26.7:
2799 version "0.26.7"
2800 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9"
2801 dependencies:
2802 graceful-fs "^4.1.2"
2803 jsonfile "^2.1.0"
2804 klaw "^1.0.0"
2805 path-is-absolute "^1.0.0"
2806 rimraf "^2.2.8"
2807
2808fs-extra@^0.30.0:
2809 version "0.30.0"
2810 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
2811 dependencies:
2812 graceful-fs "^4.1.2"
2813 jsonfile "^2.1.0"
2814 klaw "^1.0.0"
2815 path-is-absolute "^1.0.0"
2816 rimraf "^2.2.8"
2817
2818fs-extra@^2.0.0:
2819 version "2.1.2"
2820 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-2.1.2.tgz#046c70163cef9aad46b0e4a7fa467fb22d71de35"
2821 dependencies:
2822 graceful-fs "^4.1.2"
2823 jsonfile "^2.1.0"
2824
2825fs-extra@^3.0.0, fs-extra@^3.0.1:
2826 version "3.0.1"
2827 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291"
2828 dependencies:
2829 graceful-fs "^4.1.2"
2830 jsonfile "^3.0.0"
2831 universalify "^0.1.0"
2832
2833fs-extra@^4.0.0:
2834 version "4.0.1"
2835 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.1.tgz#7fc0c6c8957f983f57f306a24e5b9ddd8d0dd880"
2836 dependencies:
2837 graceful-fs "^4.1.2"
2838 jsonfile "^3.0.0"
2839 universalify "^0.1.0"
2840
2841fs.realpath@^1.0.0:
2842 version "1.0.0"
2843 resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
2844
2845fsevents@^1.0.0:
2846 version "1.1.2"
2847 resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4"
2848 dependencies:
2849 nan "^2.3.0"
2850 node-pre-gyp "^0.6.36"
2851
2852fstream-ignore@^1.0.5:
2853 version "1.0.5"
2854 resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
2855 dependencies:
2856 fstream "^1.0.0"
2857 inherits "2"
2858 minimatch "^3.0.0"
2859
2860fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2, fstream@^1.0.8:
2861 version "1.0.11"
2862 resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
2863 dependencies:
2864 graceful-fs "^4.1.2"
2865 inherits "~2.0.0"
2866 mkdirp ">=0.5 0"
2867 rimraf "2"
2868
2869function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
2870 version "1.1.1"
2871 resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
2872
2873functional-red-black-tree@^1.0.1:
2874 version "1.0.1"
2875 resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
2876
2877gauge@~2.7.3:
2878 version "2.7.4"
2879 resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
2880 dependencies:
2881 aproba "^1.0.3"
2882 console-control-strings "^1.0.0"
2883 has-unicode "^2.0.0"
2884 object-assign "^4.1.0"
2885 signal-exit "^3.0.0"
2886 string-width "^1.0.1"
2887 strip-ansi "^3.0.1"
2888 wide-align "^1.1.0"
2889
2890gaze@^0.5.1:
2891 version "0.5.2"
2892 resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f"
2893 dependencies:
2894 globule "~0.1.0"
2895
2896gaze@^1.0.0:
2897 version "1.1.2"
2898 resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.2.tgz#847224677adb8870d679257ed3388fdb61e40105"
2899 dependencies:
2900 globule "^1.0.0"
2901
2902get-caller-file@^1.0.1:
2903 version "1.0.2"
2904 resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
2905
2906get-package-info@^1.0.0:
2907 version "1.0.0"
2908 resolved "https://registry.yarnpkg.com/get-package-info/-/get-package-info-1.0.0.tgz#6432796563e28113cd9474dbbd00052985a4999c"
2909 dependencies:
2910 bluebird "^3.1.1"
2911 debug "^2.2.0"
2912 lodash.get "^4.0.0"
2913 read-pkg-up "^2.0.0"
2914
2915get-stdin@^4.0.1:
2916 version "4.0.1"
2917 resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
2918
2919get-stream@^3.0.0:
2920 version "3.0.0"
2921 resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
2922
2923getpass@^0.1.1:
2924 version "0.1.7"
2925 resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
2926 dependencies:
2927 assert-plus "^1.0.0"
2928
2929ghauth@^2.0.0:
2930 version "2.0.1"
2931 resolved "https://registry.yarnpkg.com/ghauth/-/ghauth-2.0.1.tgz#79b7d68b0bcf8e7d0852a23b147539dfd314acf6"
2932 dependencies:
2933 bl "~0.9.4"
2934 hyperquest "~1.2.0"
2935 mkdirp "~0.5.0"
2936 read "~1.0.5"
2937 xtend "~4.0.0"
2938
2939github-url-to-object@^1.4.2:
2940 version "1.6.0"
2941 resolved "https://registry.yarnpkg.com/github-url-to-object/-/github-url-to-object-1.6.0.tgz#891ef7fbbfaba8fed71510acdb1b4e9346a970dc"
2942 dependencies:
2943 is-url "^1.1.0"
2944
2945glob-base@^0.3.0:
2946 version "0.3.0"
2947 resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
2948 dependencies:
2949 glob-parent "^2.0.0"
2950 is-glob "^2.0.0"
2951
2952glob-parent@^2.0.0:
2953 version "2.0.0"
2954 resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
2955 dependencies:
2956 is-glob "^2.0.0"
2957
2958glob-parent@^3.0.0:
2959 version "3.1.0"
2960 resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
2961 dependencies:
2962 is-glob "^3.1.0"
2963 path-dirname "^1.0.0"
2964
2965glob-stream@^4.0.1:
2966 version "4.1.1"
2967 resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-4.1.1.tgz#b842df10d688c7eb6bcfcebd846f3852296b3200"
2968 dependencies:
2969 glob "^4.3.1"
2970 glob2base "^0.0.12"
2971 minimatch "^2.0.1"
2972 ordered-read-streams "^0.1.0"
2973 through2 "^0.6.1"
2974 unique-stream "^2.0.2"
2975
2976glob-stream@^5.3.2:
2977 version "5.3.5"
2978 resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22"
2979 dependencies:
2980 extend "^3.0.0"
2981 glob "^5.0.3"
2982 glob-parent "^3.0.0"
2983 micromatch "^2.3.7"
2984 ordered-read-streams "^0.3.0"
2985 through2 "^0.6.0"
2986 to-absolute-glob "^0.1.1"
2987 unique-stream "^2.0.2"
2988
2989glob-watcher@^0.0.8:
2990 version "0.0.8"
2991 resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.8.tgz#68aeb661e7e2ce8d3634381b2ec415f00c6bc2a4"
2992 dependencies:
2993 gaze "^0.5.1"
2994
2995glob-watcher@^3.0.0:
2996 version "3.2.0"
2997 resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-3.2.0.tgz#ffc1a2d3d07783b672f5e21799a4d0b3fed92daf"
2998 dependencies:
2999 async-done "^1.2.0"
3000 chokidar "^1.4.3"
3001 lodash.debounce "^4.0.6"
3002 object.defaults "^1.0.0"
3003
3004glob2base@^0.0.12:
3005 version "0.0.12"
3006 resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56"
3007 dependencies:
3008 find-index "^0.1.1"
3009
3010glob@^4.3.1:
3011 version "4.5.3"
3012 resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f"
3013 dependencies:
3014 inflight "^1.0.4"
3015 inherits "2"
3016 minimatch "^2.0.1"
3017 once "^1.3.0"
3018
3019glob@^5.0.10, glob@^5.0.3, glob@~5.0.0:
3020 version "5.0.15"
3021 resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
3022 dependencies:
3023 inflight "^1.0.4"
3024 inherits "2"
3025 minimatch "2 || 3"
3026 once "^1.3.0"
3027 path-is-absolute "^1.0.0"
3028
3029glob@^6.0.4:
3030 version "6.0.4"
3031 resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
3032 dependencies:
3033 inflight "^1.0.4"
3034 inherits "2"
3035 minimatch "2 || 3"
3036 once "^1.3.0"
3037 path-is-absolute "^1.0.0"
3038
3039glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2, glob@~7.1.1:
3040 version "7.1.2"
3041 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
3042 dependencies:
3043 fs.realpath "^1.0.0"
3044 inflight "^1.0.4"
3045 inherits "2"
3046 minimatch "^3.0.4"
3047 once "^1.3.0"
3048 path-is-absolute "^1.0.0"
3049
3050glob@~3.1.21:
3051 version "3.1.21"
3052 resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd"
3053 dependencies:
3054 graceful-fs "~1.2.0"
3055 inherits "1"
3056 minimatch "~0.2.11"
3057
3058global-modules@^0.2.3:
3059 version "0.2.3"
3060 resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d"
3061 dependencies:
3062 global-prefix "^0.1.4"
3063 is-windows "^0.2.0"
3064
3065global-prefix@^0.1.4:
3066 version "0.1.5"
3067 resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f"
3068 dependencies:
3069 homedir-polyfill "^1.0.0"
3070 ini "^1.3.4"
3071 is-windows "^0.2.0"
3072 which "^1.2.12"
3073
3074globals@^9.17.0, globals@^9.18.0:
3075 version "9.18.0"
3076 resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
3077
3078globby@^5.0.0:
3079 version "5.0.0"
3080 resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
3081 dependencies:
3082 array-union "^1.0.1"
3083 arrify "^1.0.0"
3084 glob "^7.0.3"
3085 object-assign "^4.0.1"
3086 pify "^2.0.0"
3087 pinkie-promise "^2.0.0"
3088
3089globule@^1.0.0:
3090 version "1.2.0"
3091 resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09"
3092 dependencies:
3093 glob "~7.1.1"
3094 lodash "~4.17.4"
3095 minimatch "~3.0.2"
3096
3097globule@~0.1.0:
3098 version "0.1.0"
3099 resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5"
3100 dependencies:
3101 glob "~3.1.21"
3102 lodash "~1.0.1"
3103 minimatch "~0.2.11"
3104
3105glogg@^1.0.0:
3106 version "1.0.0"
3107 resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5"
3108 dependencies:
3109 sparkles "^1.0.0"
3110
3111got@^6.7.1:
3112 version "6.7.1"
3113 resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
3114 dependencies:
3115 create-error-class "^3.0.0"
3116 duplexer3 "^0.1.4"
3117 get-stream "^3.0.0"
3118 is-redirect "^1.0.0"
3119 is-retry-allowed "^1.0.0"
3120 is-stream "^1.0.0"
3121 lowercase-keys "^1.0.0"
3122 safe-buffer "^5.0.1"
3123 timed-out "^4.0.0"
3124 unzip-response "^2.0.1"
3125 url-parse-lax "^1.0.0"
3126
3127graceful-fs@^3.0.0:
3128 version "3.0.11"
3129 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818"
3130 dependencies:
3131 natives "^1.1.0"
3132
3133graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
3134 version "4.1.11"
3135 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
3136
3137graceful-fs@~1.2.0:
3138 version "1.2.3"
3139 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364"
3140
3141gulp-babel@^6.1.2:
3142 version "6.1.2"
3143 resolved "https://registry.yarnpkg.com/gulp-babel/-/gulp-babel-6.1.2.tgz#7c0176e4ba3f244c60588a0c4b320a45d1adefce"
3144 dependencies:
3145 babel-core "^6.0.2"
3146 gulp-util "^3.0.0"
3147 object-assign "^4.0.1"
3148 replace-ext "0.0.1"
3149 through2 "^2.0.0"
3150 vinyl-sourcemaps-apply "^0.2.0"
3151
3152gulp-cli@^1.0.0:
3153 version "1.4.0"
3154 resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-1.4.0.tgz#6f5bbe2cd0bdb4849d12cf9e1246a5861f8b4f88"
3155 dependencies:
3156 archy "^1.0.0"
3157 chalk "^1.1.0"
3158 copy-props "^1.4.1"
3159 fancy-log "^1.1.0"
3160 gulplog "^1.0.0"
3161 interpret "^1.0.0"
3162 liftoff "^2.3.0"
3163 lodash.isfunction "^3.0.8"
3164 lodash.isplainobject "^4.0.4"
3165 lodash.sortby "^4.5.0"
3166 matchdep "^1.0.0"
3167 mute-stdout "^1.0.0"
3168 pretty-hrtime "^1.0.0"
3169 semver-greatest-satisfied-range "^1.0.0"
3170 tildify "^1.0.0"
3171 v8flags "^2.0.9"
3172 wreck "^6.3.0"
3173 yargs "^3.28.0"
3174
3175gulp-github-release@^1.2.1:
3176 version "1.2.1"
3177 resolved "https://registry.yarnpkg.com/gulp-github-release/-/gulp-github-release-1.2.1.tgz#e08217880fdd41d8a3a230d217f50d8ff21f47cf"
3178 dependencies:
3179 gulp-util "^3.0.7"
3180 publish-release "^1.3.2"
3181 through2 "^2.0.1"
3182
3183gulp-sass-variables@^1.1.1:
3184 version "1.1.1"
3185 resolved "https://registry.yarnpkg.com/gulp-sass-variables/-/gulp-sass-variables-1.1.1.tgz#95921590f8a1b84a687ebcb3ecbc30a74bcef44d"
3186 dependencies:
3187 gulp-util "^3.0.7"
3188 through2 "^2.0.1"
3189
3190gulp-sass@^3.1.0:
3191 version "3.1.0"
3192 resolved "https://registry.yarnpkg.com/gulp-sass/-/gulp-sass-3.1.0.tgz#53dc4b68a1f5ddfe4424ab4c247655269a8b74b7"
3193 dependencies:
3194 gulp-util "^3.0"
3195 lodash.clonedeep "^4.3.2"
3196 node-sass "^4.2.0"
3197 through2 "^2.0.0"
3198 vinyl-sourcemaps-apply "^0.2.0"
3199
3200gulp-server-livereload@^1.9.2:
3201 version "1.9.2"
3202 resolved "https://registry.yarnpkg.com/gulp-server-livereload/-/gulp-server-livereload-1.9.2.tgz#3db227f3463cfe4f29bf65a013baa162e2a740c6"
3203 dependencies:
3204 commander "^2.8.1"
3205 connect "~3.1.1"
3206 connect-inject "~0.3.2"
3207 glogg "^1.0.0"
3208 gulp-util "^3.0.7"
3209 gulplog "^1.0.0"
3210 lodash "^4.0.0"
3211 node-watch "^0.3.4"
3212 node.extend "~1.0.10"
3213 open "~0.0.5"
3214 proxy-middleware "~0.15.0"
3215 serve-index "~1.1.4"
3216 serve-static "~1.5.2"
3217 socket.io "^1.4.4"
3218 through2 "~0.5.1"
3219 vinyl-fs "^1.0.0"
3220
3221gulp-sourcemaps@1.6.0:
3222 version "1.6.0"
3223 resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz#b86ff349d801ceb56e1d9e7dc7bbcb4b7dee600c"
3224 dependencies:
3225 convert-source-map "^1.1.1"
3226 graceful-fs "^4.1.2"
3227 strip-bom "^2.0.0"
3228 through2 "^2.0.0"
3229 vinyl "^1.0.0"
3230
3231gulp-util@^3.0, gulp-util@^3.0.0, gulp-util@^3.0.7:
3232 version "3.0.8"
3233 resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f"
3234 dependencies:
3235 array-differ "^1.0.0"
3236 array-uniq "^1.0.2"
3237 beeper "^1.0.0"
3238 chalk "^1.0.0"
3239 dateformat "^2.0.0"
3240 fancy-log "^1.1.0"
3241 gulplog "^1.0.0"
3242 has-gulplog "^0.1.0"
3243 lodash._reescape "^3.0.0"
3244 lodash._reevaluate "^3.0.0"
3245 lodash._reinterpolate "^3.0.0"
3246 lodash.template "^3.0.0"
3247 minimist "^1.1.0"
3248 multipipe "^0.1.2"
3249 object-assign "^3.0.0"
3250 replace-ext "0.0.1"
3251 through2 "^2.0.0"
3252 vinyl "^0.5.0"
3253
3254gulp@gulpjs/gulp#4.0:
3255 version "4.0.0-alpha.2"
3256 resolved "https://codeload.github.com/gulpjs/gulp/tar.gz/6d71a658c61edb3090221579d8f97dbe086ba2ed"
3257 dependencies:
3258 glob-watcher "^3.0.0"
3259 gulp-cli "^1.0.0"
3260 undertaker "^1.0.0"
3261 vinyl-fs "^2.0.0"
3262
3263gulplog@^1.0.0:
3264 version "1.0.0"
3265 resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5"
3266 dependencies:
3267 glogg "^1.0.0"
3268
3269har-schema@^1.0.5:
3270 version "1.0.5"
3271 resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
3272
3273har-validator@~4.2.1:
3274 version "4.2.1"
3275 resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
3276 dependencies:
3277 ajv "^4.9.1"
3278 har-schema "^1.0.5"
3279
3280has-ansi@^2.0.0:
3281 version "2.0.0"
3282 resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
3283 dependencies:
3284 ansi-regex "^2.0.0"
3285
3286has-binary@0.1.7:
3287 version "0.1.7"
3288 resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.7.tgz#68e61eb16210c9545a0a5cce06a873912fe1e68c"
3289 dependencies:
3290 isarray "0.0.1"
3291
3292has-cors@1.1.0:
3293 version "1.1.0"
3294 resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
3295
3296has-flag@^2.0.0:
3297 version "2.0.0"
3298 resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
3299
3300has-gulplog@^0.1.0:
3301 version "0.1.0"
3302 resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce"
3303 dependencies:
3304 sparkles "^1.0.0"
3305
3306has-unicode@^2.0.0:
3307 version "2.0.1"
3308 resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
3309
3310has@^1.0.1:
3311 version "1.0.1"
3312 resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
3313 dependencies:
3314 function-bind "^1.0.2"
3315
3316hashids@^1.1.1:
3317 version "1.1.1"
3318 resolved "https://registry.yarnpkg.com/hashids/-/hashids-1.1.1.tgz#3c36fcc5b3ba1a96a8fa67a632eb7877c41c6d3e"
3319
3320hawk@~3.1.3:
3321 version "3.1.3"
3322 resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
3323 dependencies:
3324 boom "2.x.x"
3325 cryptiles "2.x.x"
3326 hoek "2.x.x"
3327 sntp "1.x.x"
3328
3329history@^3.0.0:
3330 version "3.3.0"
3331 resolved "https://registry.yarnpkg.com/history/-/history-3.3.0.tgz#fcedcce8f12975371545d735461033579a6dae9c"
3332 dependencies:
3333 invariant "^2.2.1"
3334 loose-envify "^1.2.0"
3335 query-string "^4.2.2"
3336 warning "^3.0.0"
3337
3338hoek@2.x.x:
3339 version "2.16.3"
3340 resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
3341
3342hoist-non-react-statics@^1.2.0:
3343 version "1.2.0"
3344 resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
3345
3346home-or-tmp@^2.0.0:
3347 version "2.0.0"
3348 resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
3349 dependencies:
3350 os-homedir "^1.0.0"
3351 os-tmpdir "^1.0.1"
3352
3353home-path@^1.0.1:
3354 version "1.0.5"
3355 resolved "https://registry.yarnpkg.com/home-path/-/home-path-1.0.5.tgz#788b29815b12d53bacf575648476e6f9041d133f"
3356
3357homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1:
3358 version "1.0.1"
3359 resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
3360 dependencies:
3361 parse-passwd "^1.0.0"
3362
3363hosted-git-info@^2.1.4, hosted-git-info@^2.5.0:
3364 version "2.5.0"
3365 resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
3366
3367http-errors@1.6.2, http-errors@~1.6.2:
3368 version "1.6.2"
3369 resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
3370 dependencies:
3371 depd "1.1.1"
3372 inherits "2.0.3"
3373 setprototypeof "1.0.3"
3374 statuses ">= 1.3.1 < 2"
3375
3376http-signature@~1.1.0:
3377 version "1.1.1"
3378 resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
3379 dependencies:
3380 assert-plus "^0.2.0"
3381 jsprim "^1.2.2"
3382 sshpk "^1.7.0"
3383
3384https-proxy-agent@^2.1.0:
3385 version "2.1.0"
3386 resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.1.0.tgz#1391bee7fd66aeabc0df2a1fa90f58954f43e443"
3387 dependencies:
3388 agent-base "^4.1.0"
3389 debug "^2.4.1"
3390
3391hyperquest@~1.2.0:
3392 version "1.2.0"
3393 resolved "https://registry.yarnpkg.com/hyperquest/-/hyperquest-1.2.0.tgz#39e1fef66888dc7ce0dec6c0dd814f6fc8944ad5"
3394 dependencies:
3395 duplexer2 "~0.0.2"
3396 through2 "~0.6.3"
3397
3398iconv-lite@0.4.19, iconv-lite@^0.4.17:
3399 version "0.4.19"
3400 resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
3401
3402iconv-lite@~0.4.13:
3403 version "0.4.18"
3404 resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
3405
3406ignore@^3.3.3:
3407 version "3.3.5"
3408 resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.5.tgz#c4e715455f6073a8d7e5dae72d2fc9d71663dba6"
3409
3410import-lazy@^2.1.0:
3411 version "2.1.0"
3412 resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
3413
3414imurmurhash@^0.1.4:
3415 version "0.1.4"
3416 resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
3417
3418in-publish@^2.0.0:
3419 version "2.0.0"
3420 resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51"
3421
3422indent-string@^2.1.0:
3423 version "2.1.0"
3424 resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
3425 dependencies:
3426 repeating "^2.0.0"
3427
3428indexof@0.0.1:
3429 version "0.0.1"
3430 resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
3431
3432inflight@^1.0.4:
3433 version "1.0.6"
3434 resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
3435 dependencies:
3436 once "^1.3.0"
3437 wrappy "1"
3438
3439inherits@1:
3440 version "1.0.2"
3441 resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b"
3442
3443inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
3444 version "2.0.3"
3445 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
3446
3447ini@^1.3.4, ini@~1.3.0:
3448 version "1.3.4"
3449 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
3450
3451inquirer@^0.8.2:
3452 version "0.8.5"
3453 resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.8.5.tgz#dbd740cf6ca3b731296a63ce6f6d961851f336df"
3454 dependencies:
3455 ansi-regex "^1.1.1"
3456 chalk "^1.0.0"
3457 cli-width "^1.0.1"
3458 figures "^1.3.5"
3459 lodash "^3.3.1"
3460 readline2 "^0.1.1"
3461 rx "^2.4.3"
3462 through "^2.3.6"
3463
3464inquirer@^3.0.6:
3465 version "3.3.0"
3466 resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
3467 dependencies:
3468 ansi-escapes "^3.0.0"
3469 chalk "^2.0.0"
3470 cli-cursor "^2.1.0"
3471 cli-width "^2.0.0"
3472 external-editor "^2.0.4"
3473 figures "^2.0.0"
3474 lodash "^4.3.0"
3475 mute-stream "0.0.7"
3476 run-async "^2.2.0"
3477 rx-lite "^4.0.8"
3478 rx-lite-aggregates "^4.0.8"
3479 string-width "^2.1.0"
3480 strip-ansi "^4.0.0"
3481 through "^2.3.6"
3482
3483interpret@^1.0.0:
3484 version "1.0.3"
3485 resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
3486
3487intl-format-cache@^2.0.5:
3488 version "2.0.5"
3489 resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-2.0.5.tgz#b484cefcb9353f374f25de389a3ceea1af18d7c9"
3490
3491intl-messageformat-parser@1.2.0:
3492 version "1.2.0"
3493 resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.2.0.tgz#5906b7f953ab7470e0dc8549097b648b991892ff"
3494
3495intl-messageformat@1.3.0, intl-messageformat@^1.3.0:
3496 version "1.3.0"
3497 resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-1.3.0.tgz#f7d926aded7a3ab19b2dc601efd54e99a4bd4eae"
3498 dependencies:
3499 intl-messageformat-parser "1.2.0"
3500
3501intl-relativeformat@^1.3.0:
3502 version "1.3.0"
3503 resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-1.3.0.tgz#893dc7076fccd380cf091a2300c380fa57ace45b"
3504 dependencies:
3505 intl-messageformat "1.3.0"
3506
3507invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
3508 version "2.2.2"
3509 resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
3510 dependencies:
3511 loose-envify "^1.0.0"
3512
3513invert-kv@^1.0.0:
3514 version "1.0.0"
3515 resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
3516
3517ipaddr.js@1.5.2:
3518 version "1.5.2"
3519 resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
3520
3521is-absolute@^0.2.3:
3522 version "0.2.6"
3523 resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb"
3524 dependencies:
3525 is-relative "^0.2.1"
3526 is-windows "^0.2.0"
3527
3528is-arrayish@^0.2.1:
3529 version "0.2.1"
3530 resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
3531
3532is-binary-path@^1.0.0:
3533 version "1.0.1"
3534 resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
3535 dependencies:
3536 binary-extensions "^1.0.0"
3537
3538is-buffer@^1.1.5:
3539 version "1.1.5"
3540 resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
3541
3542is-builtin-module@^1.0.0:
3543 version "1.0.0"
3544 resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
3545 dependencies:
3546 builtin-modules "^1.0.0"
3547
3548is-callable@^1.1.1, is-callable@^1.1.3:
3549 version "1.1.3"
3550 resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
3551
3552is-ci@^1.0.10:
3553 version "1.0.10"
3554 resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e"
3555 dependencies:
3556 ci-info "^1.0.0"
3557
3558is-date-object@^1.0.1:
3559 version "1.0.1"
3560 resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
3561
3562is-dotfile@^1.0.0:
3563 version "1.0.3"
3564 resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
3565
3566is-equal-shallow@^0.1.3:
3567 version "0.1.3"
3568 resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
3569 dependencies:
3570 is-primitive "^2.0.0"
3571
3572is-extendable@^0.1.0, is-extendable@^0.1.1:
3573 version "0.1.1"
3574 resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
3575
3576is-extglob@^1.0.0:
3577 version "1.0.0"
3578 resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
3579
3580is-extglob@^2.1.0:
3581 version "2.1.1"
3582 resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
3583
3584is-finite@^1.0.0:
3585 version "1.0.2"
3586 resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
3587 dependencies:
3588 number-is-nan "^1.0.0"
3589
3590is-fullwidth-code-point@^1.0.0:
3591 version "1.0.0"
3592 resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
3593 dependencies:
3594 number-is-nan "^1.0.0"
3595
3596is-fullwidth-code-point@^2.0.0:
3597 version "2.0.0"
3598 resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
3599
3600is-glob@^2.0.0, is-glob@^2.0.1:
3601 version "2.0.1"
3602 resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
3603 dependencies:
3604 is-extglob "^1.0.0"
3605
3606is-glob@^3.1.0:
3607 version "3.1.0"
3608 resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
3609 dependencies:
3610 is-extglob "^2.1.0"
3611
3612is-npm@^1.0.0:
3613 version "1.0.0"
3614 resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
3615
3616is-number@^2.1.0:
3617 version "2.1.0"
3618 resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
3619 dependencies:
3620 kind-of "^3.0.2"
3621
3622is-number@^3.0.0:
3623 version "3.0.0"
3624 resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
3625 dependencies:
3626 kind-of "^3.0.2"
3627
3628is-obj@^1.0.0:
3629 version "1.0.1"
3630 resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
3631
3632is-path-cwd@^1.0.0:
3633 version "1.0.0"
3634 resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
3635
3636is-path-in-cwd@^1.0.0:
3637 version "1.0.0"
3638 resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc"
3639 dependencies:
3640 is-path-inside "^1.0.0"
3641
3642is-path-inside@^1.0.0:
3643 version "1.0.0"
3644 resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f"
3645 dependencies:
3646 path-is-inside "^1.0.1"
3647
3648is-plain-obj@^1.0.0:
3649 version "1.1.0"
3650 resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
3651
3652is-plain-object@^2.0.1, is-plain-object@^2.0.3:
3653 version "2.0.4"
3654 resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
3655 dependencies:
3656 isobject "^3.0.1"
3657
3658is-posix-bracket@^0.1.0:
3659 version "0.1.1"
3660 resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
3661
3662is-primitive@^2.0.0:
3663 version "2.0.0"
3664 resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
3665
3666is-promise@^2.1.0:
3667 version "2.1.0"
3668 resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
3669
3670is-redirect@^1.0.0:
3671 version "1.0.0"
3672 resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
3673
3674is-regex@^1.0.4:
3675 version "1.0.4"
3676 resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
3677 dependencies:
3678 has "^1.0.1"
3679
3680is-relative@^0.2.1:
3681 version "0.2.1"
3682 resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5"
3683 dependencies:
3684 is-unc-path "^0.1.1"
3685
3686is-resolvable@^1.0.0:
3687 version "1.0.0"
3688 resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62"
3689 dependencies:
3690 tryit "^1.0.1"
3691
3692is-retry-allowed@^1.0.0:
3693 version "1.1.0"
3694 resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
3695
3696is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
3697 version "1.1.0"
3698 resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
3699
3700is-symbol@^1.0.1:
3701 version "1.0.1"
3702 resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
3703
3704is-typedarray@~1.0.0:
3705 version "1.0.0"
3706 resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
3707
3708is-unc-path@^0.1.1:
3709 version "0.1.2"
3710 resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.2.tgz#6ab053a72573c10250ff416a3814c35178af39b9"
3711 dependencies:
3712 unc-path-regex "^0.1.0"
3713
3714is-url@^1.1.0:
3715 version "1.2.2"
3716 resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.2.tgz#498905a593bf47cc2d9e7f738372bbf7696c7f26"
3717
3718is-utf8@^0.2.0:
3719 version "0.2.1"
3720 resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
3721
3722is-valid-glob@^0.3.0:
3723 version "0.3.0"
3724 resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe"
3725
3726is-windows@^0.2.0:
3727 version "0.2.0"
3728 resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
3729
3730is-windows@^1.0.0:
3731 version "1.0.1"
3732 resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9"
3733
3734is@~0.3.0:
3735 version "0.3.0"
3736 resolved "https://registry.yarnpkg.com/is/-/is-0.3.0.tgz#a8f71dfc8a6e28371627f26c929098c6f4d5d5d7"
3737
3738isarray@0.0.1:
3739 version "0.0.1"
3740 resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
3741
3742isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
3743 version "1.0.0"
3744 resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
3745
3746isbinaryfile@^3.0.2:
3747 version "3.0.2"
3748 resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.2.tgz#4a3e974ec0cba9004d3fc6cde7209ea69368a621"
3749
3750isemail@1.x.x:
3751 version "1.2.0"
3752 resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a"
3753
3754isexe@^2.0.0:
3755 version "2.0.0"
3756 resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
3757
3758isobject@^2.0.0:
3759 version "2.1.0"
3760 resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
3761 dependencies:
3762 isarray "1.0.0"
3763
3764isobject@^3.0.0, isobject@^3.0.1:
3765 version "3.0.1"
3766 resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
3767
3768isomorphic-fetch@^2.1.1:
3769 version "2.2.1"
3770 resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
3771 dependencies:
3772 node-fetch "^1.0.1"
3773 whatwg-fetch ">=0.10.0"
3774
3775isstream@~0.1.2:
3776 version "0.1.2"
3777 resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
3778
3779joi@^6.10.1:
3780 version "6.10.1"
3781 resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06"
3782 dependencies:
3783 hoek "2.x.x"
3784 isemail "1.x.x"
3785 moment "2.x.x"
3786 topo "1.x.x"
3787
3788js-base64@^2.1.8:
3789 version "2.1.9"
3790 resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce"
3791
3792js-tokens@^3.0.0, js-tokens@^3.0.2:
3793 version "3.0.2"
3794 resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
3795
3796js-yaml@^3.9.0, js-yaml@^3.9.1:
3797 version "3.9.1"
3798 resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0"
3799 dependencies:
3800 argparse "^1.0.7"
3801 esprima "^4.0.0"
3802
3803jsbn@~0.1.0:
3804 version "0.1.1"
3805 resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
3806
3807jschardet@^1.4.2:
3808 version "1.5.1"
3809 resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.1.tgz#c519f629f86b3a5bedba58a88d311309eec097f9"
3810
3811jsesc@^1.3.0:
3812 version "1.3.0"
3813 resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
3814
3815jsesc@~0.5.0:
3816 version "0.5.0"
3817 resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
3818
3819jshashes@^1.0.6:
3820 version "1.0.7"
3821 resolved "https://registry.yarnpkg.com/jshashes/-/jshashes-1.0.7.tgz#bed8c97a0e9632fd0513916f55f76dd5486be59f"
3822
3823json-schema-traverse@^0.3.0:
3824 version "0.3.1"
3825 resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
3826
3827json-schema@0.2.3:
3828 version "0.2.3"
3829 resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
3830
3831json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
3832 version "1.0.1"
3833 resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
3834 dependencies:
3835 jsonify "~0.0.0"
3836
3837json-stringify-safe@~5.0.1:
3838 version "5.0.1"
3839 resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
3840
3841json3@3.3.2:
3842 version "3.3.2"
3843 resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
3844
3845json5@^0.5.0, json5@^0.5.1:
3846 version "0.5.1"
3847 resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
3848
3849jsonfile@^2.1.0, jsonfile@^2.2.3:
3850 version "2.4.0"
3851 resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
3852 optionalDependencies:
3853 graceful-fs "^4.1.6"
3854
3855jsonfile@^3.0.0:
3856 version "3.0.1"
3857 resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
3858 optionalDependencies:
3859 graceful-fs "^4.1.6"
3860
3861jsonify@~0.0.0:
3862 version "0.0.0"
3863 resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
3864
3865jsonwebtoken@^7.4.1:
3866 version "7.4.3"
3867 resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.4.3.tgz#77f5021de058b605a1783fa1283e99812e645638"
3868 dependencies:
3869 joi "^6.10.1"
3870 jws "^3.1.4"
3871 lodash.once "^4.0.0"
3872 ms "^2.0.0"
3873 xtend "^4.0.1"
3874
3875jsprim@^1.2.2:
3876 version "1.4.1"
3877 resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
3878 dependencies:
3879 assert-plus "1.0.0"
3880 extsprintf "1.3.0"
3881 json-schema "0.2.3"
3882 verror "1.10.0"
3883
3884jsx-ast-utils@^1.0.0, jsx-ast-utils@^1.3.4:
3885 version "1.4.1"
3886 resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1"
3887
3888jwa@^1.1.4:
3889 version "1.1.5"
3890 resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
3891 dependencies:
3892 base64url "2.0.0"
3893 buffer-equal-constant-time "1.0.1"
3894 ecdsa-sig-formatter "1.0.9"
3895 safe-buffer "^5.0.1"
3896
3897jws@^3.1.4:
3898 version "3.1.4"
3899 resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2"
3900 dependencies:
3901 base64url "^2.0.0"
3902 jwa "^1.1.4"
3903 safe-buffer "^5.0.1"
3904
3905keyboard-layout@^2.0.7:
3906 version "2.0.13"
3907 resolved "https://registry.yarnpkg.com/keyboard-layout/-/keyboard-layout-2.0.13.tgz#5b4f5c25835e5d221a7b9da897663100d897487d"
3908 dependencies:
3909 event-kit "^2.0.0"
3910 nan "^2.0.0"
3911
3912keymaster@^1.6.2:
3913 version "1.6.2"
3914 resolved "https://registry.yarnpkg.com/keymaster/-/keymaster-1.6.2.tgz#e1ae54d0ea9488f9f60b66b668f02e9a1946c6eb"
3915
3916kind-of@^3.0.2, kind-of@^3.1.0:
3917 version "3.2.2"
3918 resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
3919 dependencies:
3920 is-buffer "^1.1.5"
3921
3922kind-of@^4.0.0:
3923 version "4.0.0"
3924 resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
3925 dependencies:
3926 is-buffer "^1.1.5"
3927
3928klaw@^1.0.0:
3929 version "1.3.1"
3930 resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
3931 optionalDependencies:
3932 graceful-fs "^4.1.9"
3933
3934last-run@^1.1.0:
3935 version "1.1.1"
3936 resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b"
3937 dependencies:
3938 default-resolution "^2.0.0"
3939 es6-weak-map "^2.0.1"
3940
3941latest-version@^3.0.0:
3942 version "3.1.0"
3943 resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"
3944 dependencies:
3945 package-json "^4.0.0"
3946
3947lazy-val@^1.0.2:
3948 version "1.0.2"
3949 resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.2.tgz#d9b07fb1fce54cbc99b3c611de431b83249369b6"
3950
3951lazystream@^1.0.0:
3952 version "1.0.0"
3953 resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4"
3954 dependencies:
3955 readable-stream "^2.0.5"
3956
3957lcid@^1.0.0:
3958 version "1.0.0"
3959 resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
3960 dependencies:
3961 invert-kv "^1.0.0"
3962
3963levn@^0.3.0, levn@~0.3.0:
3964 version "0.3.0"
3965 resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
3966 dependencies:
3967 prelude-ls "~1.1.2"
3968 type-check "~0.3.2"
3969
3970liftoff@^2.3.0:
3971 version "2.3.0"
3972 resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.3.0.tgz#a98f2ff67183d8ba7cfaca10548bd7ff0550b385"
3973 dependencies:
3974 extend "^3.0.0"
3975 findup-sync "^0.4.2"
3976 fined "^1.0.1"
3977 flagged-respawn "^0.3.2"
3978 lodash.isplainobject "^4.0.4"
3979 lodash.isstring "^4.0.1"
3980 lodash.mapvalues "^4.4.0"
3981 rechoir "^0.6.2"
3982 resolve "^1.1.7"
3983
3984load-json-file@^1.0.0:
3985 version "1.1.0"
3986 resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
3987 dependencies:
3988 graceful-fs "^4.1.2"
3989 parse-json "^2.2.0"
3990 pify "^2.0.0"
3991 pinkie-promise "^2.0.0"
3992 strip-bom "^2.0.0"
3993
3994load-json-file@^2.0.0:
3995 version "2.0.0"
3996 resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
3997 dependencies:
3998 graceful-fs "^4.1.2"
3999 parse-json "^2.2.0"
4000 pify "^2.0.0"
4001 strip-bom "^3.0.0"
4002
4003loader-fs-cache@^1.0.0:
4004 version "1.0.1"
4005 resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz#56e0bf08bd9708b26a765b68509840c8dec9fdbc"
4006 dependencies:
4007 find-cache-dir "^0.1.1"
4008 mkdirp "0.5.1"
4009
4010loader-utils@^1.0.2:
4011 version "1.1.0"
4012 resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
4013 dependencies:
4014 big.js "^3.1.3"
4015 emojis-list "^2.0.0"
4016 json5 "^0.5.0"
4017
4018locate-path@^2.0.0:
4019 version "2.0.0"
4020 resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
4021 dependencies:
4022 p-locate "^2.0.0"
4023 path-exists "^3.0.0"
4024
4025lodash._basecopy@^3.0.0:
4026 version "3.0.1"
4027 resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
4028
4029lodash._basetostring@^3.0.0:
4030 version "3.0.1"
4031 resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5"
4032
4033lodash._basevalues@^3.0.0:
4034 version "3.0.0"
4035 resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7"
4036
4037lodash._getnative@^3.0.0:
4038 version "3.9.1"
4039 resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
4040
4041lodash._isiterateecall@^3.0.0:
4042 version "3.0.9"
4043 resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
4044
4045lodash._reescape@^3.0.0:
4046 version "3.0.0"
4047 resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a"
4048
4049lodash._reevaluate@^3.0.0:
4050 version "3.0.0"
4051 resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed"
4052
4053lodash._reinterpolate@^3.0.0:
4054 version "3.0.0"
4055 resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
4056
4057lodash._root@^3.0.0:
4058 version "3.0.1"
4059 resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
4060
4061lodash.assign@^4.2.0:
4062 version "4.2.0"
4063 resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
4064
4065lodash.camelcase@^4.3.0:
4066 version "4.3.0"
4067 resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
4068
4069lodash.clonedeep@^4.3.2:
4070 version "4.5.0"
4071 resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
4072
4073lodash.cond@^4.3.0:
4074 version "4.5.2"
4075 resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
4076
4077lodash.debounce@^4.0.6:
4078 version "4.0.8"
4079 resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
4080
4081lodash.escape@^3.0.0:
4082 version "3.2.0"
4083 resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698"
4084 dependencies:
4085 lodash._root "^3.0.0"
4086
4087lodash.get@^4.0.0, lodash.get@^4.4.2:
4088 version "4.4.2"
4089 resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
4090
4091lodash.isarguments@^3.0.0:
4092 version "3.1.0"
4093 resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
4094
4095lodash.isarray@^3.0.0:
4096 version "3.0.4"
4097 resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
4098
4099lodash.isequal@^4.0.0, lodash.isequal@^4.5.0:
4100 version "4.5.0"
4101 resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
4102
4103lodash.isfunction@^3.0.8:
4104 version "3.0.8"
4105 resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.8.tgz#4db709fc81bc4a8fd7127a458a5346c5cdce2c6b"
4106
4107lodash.isplainobject@^4.0.4:
4108 version "4.0.6"
4109 resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
4110
4111lodash.isstring@^4.0.1:
4112 version "4.0.1"
4113 resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
4114
4115lodash.keys@^3.0.0:
4116 version "3.1.2"
4117 resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
4118 dependencies:
4119 lodash._getnative "^3.0.0"
4120 lodash.isarguments "^3.0.0"
4121 lodash.isarray "^3.0.0"
4122
4123lodash.map@^4.5.1:
4124 version "4.6.0"
4125 resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
4126
4127lodash.mapvalues@^4.4.0:
4128 version "4.6.0"
4129 resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
4130
4131lodash.mergewith@^4.6.0:
4132 version "4.6.0"
4133 resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55"
4134
4135lodash.once@^4.0.0:
4136 version "4.1.1"
4137 resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
4138
4139lodash.restparam@^3.0.0:
4140 version "3.6.1"
4141 resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
4142
4143lodash.sortby@^4.5.0:
4144 version "4.7.0"
4145 resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
4146
4147lodash.template@^3.0.0:
4148 version "3.6.2"
4149 resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f"
4150 dependencies:
4151 lodash._basecopy "^3.0.0"
4152 lodash._basetostring "^3.0.0"
4153 lodash._basevalues "^3.0.0"
4154 lodash._isiterateecall "^3.0.0"
4155 lodash._reinterpolate "^3.0.0"
4156 lodash.escape "^3.0.0"
4157 lodash.keys "^3.0.0"
4158 lodash.restparam "^3.0.0"
4159 lodash.templatesettings "^3.0.0"
4160
4161lodash.templatesettings@^3.0.0:
4162 version "3.1.1"
4163 resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5"
4164 dependencies:
4165 lodash._reinterpolate "^3.0.0"
4166 lodash.escape "^3.0.0"
4167
4168lodash.toarray@^4.4.0:
4169 version "4.4.0"
4170 resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
4171
4172lodash@^3.3.1, lodash@^3.6.0:
4173 version "3.10.1"
4174 resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
4175
4176lodash@^4.0.0, lodash@^4.12.0, lodash@^4.16.2, lodash@^4.17.4, lodash@^4.3.0, lodash@~4.17.4:
4177 version "4.17.4"
4178 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
4179
4180lodash@~1.0.1:
4181 version "1.0.2"
4182 resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551"
4183
4184log-symbols@^1.0.2:
4185 version "1.0.2"
4186 resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
4187 dependencies:
4188 chalk "^1.0.0"
4189
4190log-update@^2.1.0:
4191 version "2.1.0"
4192 resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.1.0.tgz#ea37258b5354edb02e73b29190016c87d1c87141"
4193 dependencies:
4194 ansi-escapes "^2.0.0"
4195 cli-cursor "^2.0.0"
4196 wrap-ansi "^3.0.1"
4197
4198longest@^1.0.1:
4199 version "1.0.1"
4200 resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
4201
4202loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1:
4203 version "1.3.1"
4204 resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
4205 dependencies:
4206 js-tokens "^3.0.0"
4207
4208loud-rejection@^1.0.0:
4209 version "1.6.0"
4210 resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
4211 dependencies:
4212 currently-unhandled "^0.4.1"
4213 signal-exit "^3.0.0"
4214
4215lowercase-keys@^1.0.0:
4216 version "1.0.0"
4217 resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
4218
4219lru-cache@2:
4220 version "2.7.3"
4221 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
4222
4223lru-cache@^4.0.1, lru-cache@^4.0.2:
4224 version "4.1.1"
4225 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
4226 dependencies:
4227 pseudomap "^1.0.2"
4228 yallist "^2.1.2"
4229
4230macaddress@^0.2.7:
4231 version "0.2.8"
4232 resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
4233
4234make-dir@^1.0.0:
4235 version "1.0.0"
4236 resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978"
4237 dependencies:
4238 pify "^2.3.0"
4239
4240make-iterator@^1.0.0:
4241 version "1.0.0"
4242 resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.0.tgz#57bef5dc85d23923ba23767324d8e8f8f3d9694b"
4243 dependencies:
4244 kind-of "^3.1.0"
4245
4246map-cache@^0.2.0:
4247 version "0.2.2"
4248 resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
4249
4250map-obj@^1.0.0, map-obj@^1.0.1:
4251 version "1.0.1"
4252 resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
4253
4254matchdep@^1.0.0:
4255 version "1.0.1"
4256 resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-1.0.1.tgz#a57a33804491fbae208aba8f68380437abc2dca5"
4257 dependencies:
4258 findup-sync "~0.3.0"
4259 micromatch "^2.3.7"
4260 resolve "~1.1.6"
4261 stack-trace "0.0.9"
4262
4263mdi@^1.9.33:
4264 version "1.9.33"
4265 resolved "https://registry.yarnpkg.com/mdi/-/mdi-1.9.33.tgz#3caf6d95fc6b800633630bd62ba0cf1fbde6b2e2"
4266
4267media-typer@0.3.0:
4268 version "0.3.0"
4269 resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
4270
4271mem@^1.1.0:
4272 version "1.1.0"
4273 resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
4274 dependencies:
4275 mimic-fn "^1.0.0"
4276
4277meow@^3.1.0, meow@^3.7.0:
4278 version "3.7.0"
4279 resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
4280 dependencies:
4281 camelcase-keys "^2.0.0"
4282 decamelize "^1.1.2"
4283 loud-rejection "^1.0.0"
4284 map-obj "^1.0.1"
4285 minimist "^1.1.3"
4286 normalize-package-data "^2.3.4"
4287 object-assign "^4.0.1"
4288 read-pkg-up "^1.0.1"
4289 redent "^1.0.0"
4290 trim-newlines "^1.0.0"
4291
4292merge-descriptors@1.0.1:
4293 version "1.0.1"
4294 resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
4295
4296merge-stream@^0.1.7:
4297 version "0.1.8"
4298 resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-0.1.8.tgz#48a07b3b4a121d74a3edbfdcdb4b08adbf0240b1"
4299 dependencies:
4300 through2 "^0.6.1"
4301
4302merge-stream@^1.0.0:
4303 version "1.0.1"
4304 resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1"
4305 dependencies:
4306 readable-stream "^2.0.1"
4307
4308merge@^1.2.0:
4309 version "1.2.0"
4310 resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
4311
4312methods@~1.1.2:
4313 version "1.1.2"
4314 resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
4315
4316micromatch@^2.1.5, micromatch@^2.3.7:
4317 version "2.3.11"
4318 resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
4319 dependencies:
4320 arr-diff "^2.0.0"
4321 array-unique "^0.2.1"
4322 braces "^1.8.2"
4323 expand-brackets "^0.1.4"
4324 extglob "^0.3.1"
4325 filename-regex "^2.0.0"
4326 is-extglob "^1.0.0"
4327 is-glob "^2.0.1"
4328 kind-of "^3.0.2"
4329 normalize-path "^2.0.1"
4330 object.omit "^2.0.0"
4331 parse-glob "^3.0.4"
4332 regex-cache "^0.4.2"
4333
4334mime-db@~1.30.0:
4335 version "1.30.0"
4336 resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
4337
4338mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.7:
4339 version "2.1.17"
4340 resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
4341 dependencies:
4342 mime-db "~1.30.0"
4343
4344mime-types@~1.0.0:
4345 version "1.0.2"
4346 resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-1.0.2.tgz#995ae1392ab8affcbfcb2641dd054e943c0d5dce"
4347
4348mime@1.2.11:
4349 version "1.2.11"
4350 resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10"
4351
4352mime@1.4.1:
4353 version "1.4.1"
4354 resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
4355
4356mime@^1.3.4, mime@^1.3.6:
4357 version "1.4.0"
4358 resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.0.tgz#69e9e0db51d44f2a3b56e48b7817d7d137f1a343"
4359
4360mimic-fn@^1.0.0:
4361 version "1.1.0"
4362 resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
4363
4364"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
4365 version "3.0.4"
4366 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
4367 dependencies:
4368 brace-expansion "^1.1.7"
4369
4370minimatch@^2.0.1:
4371 version "2.0.10"
4372 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7"
4373 dependencies:
4374 brace-expansion "^1.0.0"
4375
4376minimatch@~0.2.11:
4377 version "0.2.14"
4378 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a"
4379 dependencies:
4380 lru-cache "2"
4381 sigmund "~1.0.0"
4382
4383minimist@0.0.8:
4384 version "0.0.8"
4385 resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
4386
4387minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
4388 version "1.2.0"
4389 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
4390
4391mkdirp@0.5.0:
4392 version "0.5.0"
4393 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12"
4394 dependencies:
4395 minimist "0.0.8"
4396
4397mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
4398 version "0.5.1"
4399 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
4400 dependencies:
4401 minimist "0.0.8"
4402
4403mkpath@^0.1.0:
4404 version "0.1.0"
4405 resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-0.1.0.tgz#7554a6f8d871834cc97b5462b122c4c124d6de91"
4406
4407mksnapshot@^0.3.0:
4408 version "0.3.1"
4409 resolved "https://registry.yarnpkg.com/mksnapshot/-/mksnapshot-0.3.1.tgz#2501c05657436d742ce958a4ff92c77e40dd37e6"
4410 dependencies:
4411 decompress-zip "0.3.0"
4412 fs-extra "0.26.7"
4413 request "^2.79.0"
4414
4415mobx-react-form@1.24.0:
4416 version "1.24.0"
4417 resolved "https://registry.yarnpkg.com/mobx-react-form/-/mobx-react-form-1.24.0.tgz#bc9fbd652e65fb1f2b51917865d465fcaab7f0d9"
4418 dependencies:
4419 lodash "^4.16.2"
4420
4421mobx-react-router@^3.1.2:
4422 version "3.1.2"
4423 resolved "https://registry.yarnpkg.com/mobx-react-router/-/mobx-react-router-3.1.2.tgz#83328b108393017148d86fea17f611de2d2aacdc"
4424
4425mobx-react@^4.1.0:
4426 version "4.2.2"
4427 resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-4.2.2.tgz#db9cc3cafefbd830d0584c1149af5aae67829201"
4428 dependencies:
4429 hoist-non-react-statics "^1.2.0"
4430
4431mobx@^3.1.0:
4432 version "3.2.2"
4433 resolved "https://registry.yarnpkg.com/mobx/-/mobx-3.2.2.tgz#aa671459bededfd9880c948889a3f62bce09279c"
4434
4435moment@2.x.x, moment@^2.17.1:
4436 version "2.18.1"
4437 resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
4438
4439mout@^0.11.0:
4440 version "0.11.1"
4441 resolved "https://registry.yarnpkg.com/mout/-/mout-0.11.1.tgz#ba3611df5f0e5b1ffbfd01166b8f02d1f5fa2b99"
4442
4443ms@0.6.2:
4444 version "0.6.2"
4445 resolved "https://registry.yarnpkg.com/ms/-/ms-0.6.2.tgz#d89c2124c6fdc1353d65a8b77bf1aac4b193708c"
4446
4447ms@0.7.1:
4448 version "0.7.1"
4449 resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
4450
4451ms@0.7.2:
4452 version "0.7.2"
4453 resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
4454
4455ms@2.0.0, ms@^2.0.0:
4456 version "2.0.0"
4457 resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
4458
4459multipipe@^0.1.2:
4460 version "0.1.2"
4461 resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b"
4462 dependencies:
4463 duplexer2 "0.0.2"
4464
4465mute-stdout@^1.0.0:
4466 version "1.0.0"
4467 resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.0.tgz#5b32ea07eb43c9ded6130434cf926f46b2a7fd4d"
4468
4469mute-stream@0.0.4:
4470 version "0.0.4"
4471 resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.4.tgz#a9219960a6d5d5d046597aee51252c6655f7177e"
4472
4473mute-stream@0.0.7:
4474 version "0.0.7"
4475 resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
4476
4477mute-stream@~0.0.4:
4478 version "0.0.5"
4479 resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
4480
4481nan@^2.0.0, nan@^2.0.5, nan@^2.3.0, nan@^2.3.2:
4482 version "2.7.0"
4483 resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
4484
4485natives@^1.1.0:
4486 version "1.1.0"
4487 resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.0.tgz#e9ff841418a6b2ec7a495e939984f78f163e6e31"
4488
4489natural-compare@^1.4.0:
4490 version "1.4.0"
4491 resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
4492
4493negotiator@0.4.7:
4494 version "0.4.7"
4495 resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.4.7.tgz#a4160f7177ec806738631d0d3052325da42abdc8"
4496
4497negotiator@0.6.1:
4498 version "0.6.1"
4499 resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
4500
4501node-abi@^2.0.0:
4502 version "2.1.1"
4503 resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.1.1.tgz#c9cda256ec8aa99bcab2f6446db38af143338b2a"
4504
4505node-emoji@^1.6.1:
4506 version "1.8.1"
4507 resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826"
4508 dependencies:
4509 lodash.toarray "^4.4.0"
4510
4511node-fetch@^1.0.1:
4512 version "1.7.2"
4513 resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.2.tgz#c54e9aac57e432875233525f3c891c4159ffefd7"
4514 dependencies:
4515 encoding "^0.1.11"
4516 is-stream "^1.0.1"
4517
4518node-gyp@^3.3.1, node-gyp@^3.6.0:
4519 version "3.6.2"
4520 resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60"
4521 dependencies:
4522 fstream "^1.0.0"
4523 glob "^7.0.3"
4524 graceful-fs "^4.1.2"
4525 minimatch "^3.0.2"
4526 mkdirp "^0.5.0"
4527 nopt "2 || 3"
4528 npmlog "0 || 1 || 2 || 3 || 4"
4529 osenv "0"
4530 request "2"
4531 rimraf "2"
4532 semver "~5.3.0"
4533 tar "^2.0.0"
4534 which "1"
4535
4536node-pre-gyp@^0.6.36:
4537 version "0.6.36"
4538 resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786"
4539 dependencies:
4540 mkdirp "^0.5.1"
4541 nopt "^4.0.1"
4542 npmlog "^4.0.2"
4543 rc "^1.1.7"
4544 request "^2.81.0"
4545 rimraf "^2.6.1"
4546 semver "^5.3.0"
4547 tar "^2.2.1"
4548 tar-pack "^3.4.0"
4549
4550node-sass@^4.2.0, node-sass@^4.5.3:
4551 version "4.5.3"
4552 resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.3.tgz#d09c9d1179641239d1b97ffc6231fdcec53e1568"
4553 dependencies:
4554 async-foreach "^0.1.3"
4555 chalk "^1.1.1"
4556 cross-spawn "^3.0.0"
4557 gaze "^1.0.0"
4558 get-stdin "^4.0.1"
4559 glob "^7.0.3"
4560 in-publish "^2.0.0"
4561 lodash.assign "^4.2.0"
4562 lodash.clonedeep "^4.3.2"
4563 lodash.mergewith "^4.6.0"
4564 meow "^3.7.0"
4565 mkdirp "^0.5.1"
4566 nan "^2.3.2"
4567 node-gyp "^3.3.1"
4568 npmlog "^4.0.0"
4569 request "^2.79.0"
4570 sass-graph "^2.1.1"
4571 stdout-stream "^1.4.0"
4572
4573node-watch@^0.3.4:
4574 version "0.3.5"
4575 resolved "https://registry.yarnpkg.com/node-watch/-/node-watch-0.3.5.tgz#a07f253a4f538de9d4ca522dd7f1996eeec0d97e"
4576
4577node.extend@~1.0.10:
4578 version "1.0.10"
4579 resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-1.0.10.tgz#3269bddf81c54535f408abc784c32b0d2bd55f6f"
4580 dependencies:
4581 is "~0.3.0"
4582
4583"nopt@2 || 3", nopt@^3.0.1:
4584 version "3.0.6"
4585 resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
4586 dependencies:
4587 abbrev "1"
4588
4589nopt@^4.0.1:
4590 version "4.0.1"
4591 resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
4592 dependencies:
4593 abbrev "1"
4594 osenv "^0.1.4"
4595
4596nopt@~1.0.10:
4597 version "1.0.10"
4598 resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
4599 dependencies:
4600 abbrev "1"
4601
4602normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.4.0:
4603 version "2.4.0"
4604 resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
4605 dependencies:
4606 hosted-git-info "^2.1.4"
4607 is-builtin-module "^1.0.0"
4608 semver "2 || 3 || 4 || 5"
4609 validate-npm-package-license "^3.0.1"
4610
4611normalize-path@^2.0.0, normalize-path@^2.0.1:
4612 version "2.1.1"
4613 resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
4614 dependencies:
4615 remove-trailing-separator "^1.0.1"
4616
4617normalize-url@^1.9.1:
4618 version "1.9.1"
4619 resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
4620 dependencies:
4621 object-assign "^4.0.1"
4622 prepend-http "^1.0.0"
4623 query-string "^4.1.0"
4624 sort-keys "^1.0.0"
4625
4626now-and-later@^2.0.0:
4627 version "2.0.0"
4628 resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee"
4629 dependencies:
4630 once "^1.3.2"
4631
4632npm-run-path@^2.0.0:
4633 version "2.0.2"
4634 resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
4635 dependencies:
4636 path-key "^2.0.0"
4637
4638"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2:
4639 version "4.1.2"
4640 resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
4641 dependencies:
4642 are-we-there-yet "~1.1.2"
4643 console-control-strings "~1.1.0"
4644 gauge "~2.7.3"
4645 set-blocking "~2.0.0"
4646
4647nugget@^2.0.0, nugget@^2.0.1:
4648 version "2.0.1"
4649 resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0"
4650 dependencies:
4651 debug "^2.1.3"
4652 minimist "^1.1.0"
4653 pretty-bytes "^1.0.2"
4654 progress-stream "^1.1.0"
4655 request "^2.45.0"
4656 single-line-log "^1.1.2"
4657 throttleit "0.0.2"
4658
4659number-is-nan@^1.0.0:
4660 version "1.0.1"
4661 resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
4662
4663oauth-sign@~0.8.1:
4664 version "0.8.2"
4665 resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
4666
4667object-assign@4.1.0:
4668 version "4.1.0"
4669 resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
4670
4671object-assign@^2.0.0:
4672 version "2.1.1"
4673 resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa"
4674
4675object-assign@^3.0.0:
4676 version "3.0.0"
4677 resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
4678
4679object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
4680 version "4.1.1"
4681 resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
4682
4683object-component@0.0.3:
4684 version "0.0.3"
4685 resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
4686
4687object-hash@^1.1.4:
4688 version "1.1.8"
4689 resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.1.8.tgz#28a659cf987d96a4dabe7860289f3b5326c4a03c"
4690
4691object-keys@^1.0.10, object-keys@^1.0.8:
4692 version "1.0.11"
4693 resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
4694
4695object-keys@~0.4.0:
4696 version "0.4.0"
4697 resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
4698
4699object.assign@^4.0.4:
4700 version "4.0.4"
4701 resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc"
4702 dependencies:
4703 define-properties "^1.1.2"
4704 function-bind "^1.1.0"
4705 object-keys "^1.0.10"
4706
4707object.defaults@^1.0.0, object.defaults@^1.1.0:
4708 version "1.1.0"
4709 resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf"
4710 dependencies:
4711 array-each "^1.0.1"
4712 array-slice "^1.0.0"
4713 for-own "^1.0.0"
4714 isobject "^3.0.0"
4715
4716object.omit@^2.0.0:
4717 version "2.0.1"
4718 resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
4719 dependencies:
4720 for-own "^0.1.4"
4721 is-extendable "^0.1.1"
4722
4723object.pick@^1.2.0:
4724 version "1.3.0"
4725 resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
4726 dependencies:
4727 isobject "^3.0.1"
4728
4729object.reduce@^1.0.0:
4730 version "1.0.1"
4731 resolved "https://registry.yarnpkg.com/object.reduce/-/object.reduce-1.0.1.tgz#6fe348f2ac7fa0f95ca621226599096825bb03ad"
4732 dependencies:
4733 for-own "^1.0.0"
4734 make-iterator "^1.0.0"
4735
4736on-finished@2.1.0:
4737 version "2.1.0"
4738 resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.1.0.tgz#0c539f09291e8ffadde0c8a25850fb2cedc7022d"
4739 dependencies:
4740 ee-first "1.0.5"
4741
4742on-finished@~2.3.0:
4743 version "2.3.0"
4744 resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
4745 dependencies:
4746 ee-first "1.1.1"
4747
4748once@^1.3.0, once@^1.3.2, once@^1.3.3, once@^1.4.0:
4749 version "1.4.0"
4750 resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
4751 dependencies:
4752 wrappy "1"
4753
4754onetime@^2.0.0:
4755 version "2.0.1"
4756 resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
4757 dependencies:
4758 mimic-fn "^1.0.0"
4759
4760open@~0.0.5:
4761 version "0.0.5"
4762 resolved "https://registry.yarnpkg.com/open/-/open-0.0.5.tgz#42c3e18ec95466b6bf0dc42f3a2945c3f0cad8fc"
4763
4764optionator@^0.8.2:
4765 version "0.8.2"
4766 resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
4767 dependencies:
4768 deep-is "~0.1.3"
4769 fast-levenshtein "~2.0.4"
4770 levn "~0.3.0"
4771 prelude-ls "~1.1.2"
4772 type-check "~0.3.2"
4773 wordwrap "~1.0.0"
4774
4775options@>=0.0.5:
4776 version "0.0.6"
4777 resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
4778
4779ora@^1.2.0:
4780 version "1.3.0"
4781 resolved "https://registry.yarnpkg.com/ora/-/ora-1.3.0.tgz#80078dd2b92a934af66a3ad72a5b910694ede51a"
4782 dependencies:
4783 chalk "^1.1.1"
4784 cli-cursor "^2.1.0"
4785 cli-spinners "^1.0.0"
4786 log-symbols "^1.0.2"
4787
4788ordered-read-streams@^0.1.0:
4789 version "0.1.0"
4790 resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126"
4791
4792ordered-read-streams@^0.3.0:
4793 version "0.3.0"
4794 resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b"
4795 dependencies:
4796 is-stream "^1.0.1"
4797 readable-stream "^2.0.1"
4798
4799os-homedir@^1.0.0, os-homedir@^1.0.1:
4800 version "1.0.2"
4801 resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
4802
4803os-locale@^1.4.0:
4804 version "1.4.0"
4805 resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
4806 dependencies:
4807 lcid "^1.0.0"
4808
4809os-locale@^2.0.0:
4810 version "2.1.0"
4811 resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
4812 dependencies:
4813 execa "^0.7.0"
4814 lcid "^1.0.0"
4815 mem "^1.1.0"
4816
4817os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
4818 version "1.0.2"
4819 resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
4820
4821osenv@0, osenv@^0.1.4:
4822 version "0.1.4"
4823 resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
4824 dependencies:
4825 os-homedir "^1.0.0"
4826 os-tmpdir "^1.0.0"
4827
4828p-finally@^1.0.0:
4829 version "1.0.0"
4830 resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
4831
4832p-limit@^1.1.0:
4833 version "1.1.0"
4834 resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc"
4835
4836p-locate@^2.0.0:
4837 version "2.0.0"
4838 resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
4839 dependencies:
4840 p-limit "^1.1.0"
4841
4842package-json@^4.0.0:
4843 version "4.0.1"
4844 resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"
4845 dependencies:
4846 got "^6.7.1"
4847 registry-auth-token "^3.0.1"
4848 registry-url "^3.0.3"
4849 semver "^5.1.0"
4850
4851pad-right@^0.2.2:
4852 version "0.2.2"
4853 resolved "https://registry.yarnpkg.com/pad-right/-/pad-right-0.2.2.tgz#6fbc924045d244f2a2a244503060d3bfc6009774"
4854 dependencies:
4855 repeat-string "^1.5.2"
4856
4857parse-color@^1.0.0:
4858 version "1.0.0"
4859 resolved "https://registry.yarnpkg.com/parse-color/-/parse-color-1.0.0.tgz#7b748b95a83f03f16a94f535e52d7f3d94658619"
4860 dependencies:
4861 color-convert "~0.5.0"
4862
4863parse-filepath@^1.0.1:
4864 version "1.0.1"
4865 resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.1.tgz#159d6155d43904d16c10ef698911da1e91969b73"
4866 dependencies:
4867 is-absolute "^0.2.3"
4868 map-cache "^0.2.0"
4869 path-root "^0.1.1"
4870
4871parse-glob@^3.0.4:
4872 version "3.0.4"
4873 resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
4874 dependencies:
4875 glob-base "^0.3.0"
4876 is-dotfile "^1.0.0"
4877 is-extglob "^1.0.0"
4878 is-glob "^2.0.0"
4879
4880parse-json@^2.2.0:
4881 version "2.2.0"
4882 resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
4883 dependencies:
4884 error-ex "^1.2.0"
4885
4886parse-passwd@^1.0.0:
4887 version "1.0.0"
4888 resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
4889
4890parsejson@0.0.3:
4891 version "0.0.3"
4892 resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab"
4893 dependencies:
4894 better-assert "~1.0.0"
4895
4896parseqs@0.0.5:
4897 version "0.0.5"
4898 resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
4899 dependencies:
4900 better-assert "~1.0.0"
4901
4902parseuri@0.0.5:
4903 version "0.0.5"
4904 resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
4905 dependencies:
4906 better-assert "~1.0.0"
4907
4908parseurl@~1.3.0:
4909 version "1.3.1"
4910 resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
4911
4912parseurl@~1.3.2:
4913 version "1.3.2"
4914 resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
4915
4916path-dirname@^1.0.0:
4917 version "1.0.2"
4918 resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
4919
4920path-exists@^2.0.0, path-exists@^2.1.0:
4921 version "2.1.0"
4922 resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
4923 dependencies:
4924 pinkie-promise "^2.0.0"
4925
4926path-exists@^3.0.0:
4927 version "3.0.0"
4928 resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
4929
4930path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
4931 version "1.0.1"
4932 resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
4933
4934path-is-inside@^1.0.1, path-is-inside@^1.0.2:
4935 version "1.0.2"
4936 resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
4937
4938path-key@^2.0.0:
4939 version "2.0.1"
4940 resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
4941
4942path-parse@^1.0.5:
4943 version "1.0.5"
4944 resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
4945
4946path-root-regex@^0.1.0:
4947 version "0.1.2"
4948 resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d"
4949
4950path-root@^0.1.1:
4951 version "0.1.1"
4952 resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7"
4953 dependencies:
4954 path-root-regex "^0.1.0"
4955
4956path-to-regexp@0.1.7:
4957 version "0.1.7"
4958 resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
4959
4960path-type@^1.0.0:
4961 version "1.1.0"
4962 resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
4963 dependencies:
4964 graceful-fs "^4.1.2"
4965 pify "^2.0.0"
4966 pinkie-promise "^2.0.0"
4967
4968path-type@^2.0.0:
4969 version "2.0.0"
4970 resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
4971 dependencies:
4972 pify "^2.0.0"
4973
4974pend@~1.2.0:
4975 version "1.2.0"
4976 resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
4977
4978performance-now@^0.2.0:
4979 version "0.2.0"
4980 resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
4981
4982performance-now@^2.1.0:
4983 version "2.1.0"
4984 resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
4985
4986pify@^2.0.0, pify@^2.3.0:
4987 version "2.3.0"
4988 resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
4989
4990pinkie-promise@^2.0.0:
4991 version "2.0.1"
4992 resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
4993 dependencies:
4994 pinkie "^2.0.0"
4995
4996pinkie@^2.0.0:
4997 version "2.0.4"
4998 resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
4999
5000pkg-dir@^1.0.0:
5001 version "1.0.0"
5002 resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
5003 dependencies:
5004 find-up "^1.0.0"
5005
5006pkginfo@^0.3.0:
5007 version "0.3.1"
5008 resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
5009
5010plist@^2.0.0, plist@^2.0.1, plist@^2.1.0:
5011 version "2.1.0"
5012 resolved "https://registry.yarnpkg.com/plist/-/plist-2.1.0.tgz#57ccdb7a0821df21831217a3cad54e3e146a1025"
5013 dependencies:
5014 base64-js "1.2.0"
5015 xmlbuilder "8.2.2"
5016 xmldom "0.1.x"
5017
5018pluralize@^7.0.0:
5019 version "7.0.0"
5020 resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
5021
5022prelude-ls@~1.1.2:
5023 version "1.1.2"
5024 resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
5025
5026prepend-http@^1.0.0, prepend-http@^1.0.1:
5027 version "1.0.4"
5028 resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
5029
5030preserve@^0.2.0:
5031 version "0.2.0"
5032 resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
5033
5034pretty-bytes@^1.0.2, pretty-bytes@^1.0.4:
5035 version "1.0.4"
5036 resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
5037 dependencies:
5038 get-stdin "^4.0.1"
5039 meow "^3.1.0"
5040
5041pretty-hrtime@^1.0.0:
5042 version "1.0.3"
5043 resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
5044
5045private@^0.1.6, private@^0.1.7:
5046 version "0.1.7"
5047 resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
5048
5049process-nextick-args@^1.0.7, process-nextick-args@~1.0.6:
5050 version "1.0.7"
5051 resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
5052
5053progress-stream@^1.0.1, progress-stream@^1.1.0:
5054 version "1.2.0"
5055 resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77"
5056 dependencies:
5057 speedometer "~0.1.2"
5058 through2 "~0.2.3"
5059
5060progress@^2.0.0:
5061 version "2.0.0"
5062 resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
5063
5064promise@^7.1.1:
5065 version "7.3.1"
5066 resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
5067 dependencies:
5068 asap "~2.0.3"
5069
5070prop-types-extended@^0.2.1:
5071 version "0.2.1"
5072 resolved "https://registry.yarnpkg.com/prop-types-extended/-/prop-types-extended-0.2.1.tgz#ce23f3dbc48ccdc76cfa1a4c7e3f7ed9a5dd8259"
5073 dependencies:
5074 invariant "^2.2.0"
5075
5076prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8:
5077 version "15.5.10"
5078 resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
5079 dependencies:
5080 fbjs "^0.8.9"
5081 loose-envify "^1.3.1"
5082
5083proxy-addr@~2.0.2:
5084 version "2.0.2"
5085 resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
5086 dependencies:
5087 forwarded "~0.1.2"
5088 ipaddr.js "1.5.2"
5089
5090proxy-from-env@^1.0.0:
5091 version "1.0.0"
5092 resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee"
5093
5094proxy-middleware@~0.15.0:
5095 version "0.15.0"
5096 resolved "https://registry.yarnpkg.com/proxy-middleware/-/proxy-middleware-0.15.0.tgz#a3fdf1befb730f951965872ac2f6074c61477a56"
5097
5098pseudomap@^1.0.2:
5099 version "1.0.2"
5100 resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
5101
5102publish-release@^1.3.2:
5103 version "1.3.3"
5104 resolved "https://registry.yarnpkg.com/publish-release/-/publish-release-1.3.3.tgz#6cd11df835e14c13b0e08a35d3fb992b918bec3c"
5105 dependencies:
5106 async "^0.9.0"
5107 ghauth "^2.0.0"
5108 github-url-to-object "^1.4.2"
5109 inquirer "^0.8.2"
5110 lodash "^3.6.0"
5111 mime "^1.3.4"
5112 minimist "^1.1.1"
5113 pkginfo "^0.3.0"
5114 pretty-bytes "^1.0.4"
5115 progress-stream "^1.0.1"
5116 request "^2.54.0"
5117 single-line-log "^0.4.1"
5118 string-editor "^0.1.0"
5119
5120punycode@^1.4.1:
5121 version "1.4.1"
5122 resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
5123
5124puppeteer@^0.10.2:
5125 version "0.10.2"
5126 resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-0.10.2.tgz#b4a959a722bf626ca481f2aeba11fdb810f2c98f"
5127 dependencies:
5128 debug "^2.6.8"
5129 extract-zip "^1.6.5"
5130 https-proxy-agent "^2.1.0"
5131 mime "^1.3.4"
5132 progress "^2.0.0"
5133 proxy-from-env "^1.0.0"
5134 rimraf "^2.6.1"
5135 ws "^3.0.0"
5136
5137q@^1.1.2:
5138 version "1.5.0"
5139 resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
5140
5141qs@6.5.1:
5142 version "6.5.1"
5143 resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
5144
5145qs@~6.4.0:
5146 version "6.4.0"
5147 resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
5148
5149query-string@^4.1.0, query-string@^4.2.2:
5150 version "4.3.4"
5151 resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
5152 dependencies:
5153 object-assign "^4.1.0"
5154 strict-uri-encode "^1.0.0"
5155
5156raf@^3.1.0:
5157 version "3.3.2"
5158 resolved "https://registry.yarnpkg.com/raf/-/raf-3.3.2.tgz#0c13be0b5b49b46f76d6669248d527cf2b02fe27"
5159 dependencies:
5160 performance-now "^2.1.0"
5161
5162randomatic@^1.1.3:
5163 version "1.1.7"
5164 resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
5165 dependencies:
5166 is-number "^3.0.0"
5167 kind-of "^4.0.0"
5168
5169range-parser@~1.0.0:
5170 version "1.0.3"
5171 resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.0.3.tgz#6872823535c692e2c2a0103826afd82c2e0ff175"
5172
5173range-parser@~1.2.0:
5174 version "1.2.0"
5175 resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
5176
5177raw-body@2.3.2:
5178 version "2.3.2"
5179 resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
5180 dependencies:
5181 bytes "3.0.0"
5182 http-errors "1.6.2"
5183 iconv-lite "0.4.19"
5184 unpipe "1.0.0"
5185
5186rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.1.7, rc@^1.2.1:
5187 version "1.2.1"
5188 resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
5189 dependencies:
5190 deep-extend "~0.4.0"
5191 ini "~1.3.0"
5192 minimist "^1.2.0"
5193 strip-json-comments "~2.0.1"
5194
5195rcedit@^0.9.0:
5196 version "0.9.0"
5197 resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-0.9.0.tgz#3910df57345399e2b0325f4a519007f89e55ef1c"
5198
5199react-addons-css-transition-group@^15.4.2:
5200 version "15.6.0"
5201 resolved "https://registry.yarnpkg.com/react-addons-css-transition-group/-/react-addons-css-transition-group-15.6.0.tgz#69887cf6e4874d25cd66e22a699e29f0d648aba0"
5202 dependencies:
5203 react-transition-group "^1.2.0"
5204
5205react-dom@^15.4.1:
5206 version "15.6.1"
5207 resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.1.tgz#2cb0ed4191038e53c209eb3a79a23e2a4cf99470"
5208 dependencies:
5209 fbjs "^0.8.9"
5210 loose-envify "^1.1.0"
5211 object-assign "^4.1.0"
5212 prop-types "^15.5.10"
5213
5214react-electron-web-view@^2.0.1:
5215 version "2.0.1"
5216 resolved "https://registry.yarnpkg.com/react-electron-web-view/-/react-electron-web-view-2.0.1.tgz#984b7bbbeb77e35bcca921dc50120fc8f2b0f27d"
5217 dependencies:
5218 lodash.camelcase "^4.3.0"
5219
5220react-intl@^2.3.0:
5221 version "2.3.0"
5222 resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-2.3.0.tgz#e1df6af5667fdf01cbe4aab20e137251e2ae5142"
5223 dependencies:
5224 intl-format-cache "^2.0.5"
5225 intl-messageformat "^1.3.0"
5226 intl-relativeformat "^1.3.0"
5227 invariant "^2.1.1"
5228
5229react-loader@^2.4.0:
5230 version "2.4.2"
5231 resolved "https://registry.yarnpkg.com/react-loader/-/react-loader-2.4.2.tgz#14e2b9139fc5693da5cdbdc928032d5b1aeb94c2"
5232 dependencies:
5233 create-react-class "^15.5.2"
5234 prop-types "^15.5.8"
5235 spin.js "2.x"
5236
5237react-motion@^0.4.8:
5238 version "0.4.8"
5239 resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.4.8.tgz#23bb2dd27c2d8e00d229e45572d105efcf40a35e"
5240 dependencies:
5241 create-react-class "^15.5.2"
5242 performance-now "^0.2.0"
5243 prop-types "^15.5.8"
5244 raf "^3.1.0"
5245
5246react-router-transition@^0.1.1:
5247 version "0.1.1"
5248 resolved "https://registry.yarnpkg.com/react-router-transition/-/react-router-transition-0.1.1.tgz#6cc2cb747cb27e4d5632c82ca750b4312ccd3bd6"
5249 dependencies:
5250 prop-types "^15.5.8"
5251 react-motion "^0.4.8"
5252
5253react-router@^3.0.2:
5254 version "3.0.5"
5255 resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.0.5.tgz#c3b7873758045a8bbc9562aef4ff4bc8cce7c136"
5256 dependencies:
5257 create-react-class "^15.5.1"
5258 history "^3.0.0"
5259 hoist-non-react-statics "^1.2.0"
5260 invariant "^2.2.1"
5261 loose-envify "^1.2.0"
5262 prop-types "^15.5.6"
5263 warning "^3.0.0"
5264
5265react-sortable-hoc@^0.6.7:
5266 version "0.6.7"
5267 resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-0.6.7.tgz#e30d247bc36dd5a605430c331ac9cb50a5fa72a6"
5268 dependencies:
5269 babel-runtime "^6.11.6"
5270 invariant "^2.2.1"
5271 lodash "^4.12.0"
5272 prop-types "^15.5.7"
5273
5274react-tooltip@^3.2.7:
5275 version "3.3.0"
5276 resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-3.3.0.tgz#51c08ae0221075e2c43d83cd47fc78466612df7d"
5277 dependencies:
5278 classnames "^2.2.0"
5279 prop-types "^15.5.8"
5280
5281react-transition-group@^1.2.0:
5282 version "1.2.0"
5283 resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.0.tgz#b51fc921b0c3835a7ef7c571c79fc82c73e9204f"
5284 dependencies:
5285 chain-function "^1.0.0"
5286 dom-helpers "^3.2.0"
5287 loose-envify "^1.3.1"
5288 prop-types "^15.5.6"
5289 warning "^3.0.0"
5290
5291react@^15.4.1:
5292 version "15.6.1"
5293 resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df"
5294 dependencies:
5295 create-react-class "^15.6.0"
5296 fbjs "^0.8.9"
5297 loose-envify "^1.1.0"
5298 object-assign "^4.1.0"
5299 prop-types "^15.5.10"
5300
5301read-pkg-up@^1.0.1:
5302 version "1.0.1"
5303 resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
5304 dependencies:
5305 find-up "^1.0.0"
5306 read-pkg "^1.0.0"
5307
5308read-pkg-up@^2.0.0:
5309 version "2.0.0"
5310 resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
5311 dependencies:
5312 find-up "^2.0.0"
5313 read-pkg "^2.0.0"
5314
5315read-pkg@^1.0.0:
5316 version "1.1.0"
5317 resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
5318 dependencies:
5319 load-json-file "^1.0.0"
5320 normalize-package-data "^2.3.2"
5321 path-type "^1.0.0"
5322
5323read-pkg@^2.0.0:
5324 version "2.0.0"
5325 resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
5326 dependencies:
5327 load-json-file "^2.0.0"
5328 normalize-package-data "^2.3.2"
5329 path-type "^2.0.0"
5330
5331read@~1.0.5:
5332 version "1.0.7"
5333 resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4"
5334 dependencies:
5335 mute-stream "~0.0.4"
5336
5337"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.17, readable-stream@~1.0.26:
5338 version "1.0.34"
5339 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
5340 dependencies:
5341 core-util-is "~1.0.0"
5342 inherits "~2.0.1"
5343 isarray "0.0.1"
5344 string_decoder "~0.10.x"
5345
5346readable-stream@^1.1.8, readable-stream@~1.1.9:
5347 version "1.1.14"
5348 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
5349 dependencies:
5350 core-util-is "~1.0.0"
5351 inherits "~2.0.1"
5352 isarray "0.0.1"
5353 string_decoder "~0.10.x"
5354
5355readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2:
5356 version "2.3.3"
5357 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
5358 dependencies:
5359 core-util-is "~1.0.0"
5360 inherits "~2.0.3"
5361 isarray "~1.0.0"
5362 process-nextick-args "~1.0.6"
5363 safe-buffer "~5.1.1"
5364 string_decoder "~1.0.3"
5365 util-deprecate "~1.0.1"
5366
5367readdirp@^2.0.0:
5368 version "2.1.0"
5369 resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
5370 dependencies:
5371 graceful-fs "^4.1.2"
5372 minimatch "^3.0.2"
5373 readable-stream "^2.0.2"
5374 set-immediate-shim "^1.0.1"
5375
5376readline2@^0.1.1:
5377 version "0.1.1"
5378 resolved "https://registry.yarnpkg.com/readline2/-/readline2-0.1.1.tgz#99443ba6e83b830ef3051bfd7dc241a82728d568"
5379 dependencies:
5380 mute-stream "0.0.4"
5381 strip-ansi "^2.0.1"
5382
5383rechoir@^0.6.2:
5384 version "0.6.2"
5385 resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
5386 dependencies:
5387 resolve "^1.1.6"
5388
5389redent@^1.0.0:
5390 version "1.0.0"
5391 resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
5392 dependencies:
5393 indent-string "^2.1.0"
5394 strip-indent "^1.0.1"
5395
5396regenerate@^1.2.1:
5397 version "1.3.2"
5398 resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
5399
5400regenerator-runtime@^0.10.5:
5401 version "0.10.5"
5402 resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
5403
5404regenerator-runtime@^0.11.0:
5405 version "0.11.0"
5406 resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1"
5407
5408regenerator-transform@^0.10.0:
5409 version "0.10.1"
5410 resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
5411 dependencies:
5412 babel-runtime "^6.18.0"
5413 babel-types "^6.19.0"
5414 private "^0.1.6"
5415
5416regex-cache@^0.4.2:
5417 version "0.4.4"
5418 resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
5419 dependencies:
5420 is-equal-shallow "^0.1.3"
5421
5422regexpu-core@^2.0.0:
5423 version "2.0.0"
5424 resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
5425 dependencies:
5426 regenerate "^1.2.1"
5427 regjsgen "^0.2.0"
5428 regjsparser "^0.1.4"
5429
5430registry-auth-token@^3.0.1:
5431 version "3.3.1"
5432 resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.1.tgz#fb0d3289ee0d9ada2cbb52af5dfe66cb070d3006"
5433 dependencies:
5434 rc "^1.1.6"
5435 safe-buffer "^5.0.1"
5436
5437registry-url@^3.0.3:
5438 version "3.1.0"
5439 resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
5440 dependencies:
5441 rc "^1.0.1"
5442
5443regjsgen@^0.2.0:
5444 version "0.2.0"
5445 resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
5446
5447regjsparser@^0.1.4:
5448 version "0.1.5"
5449 resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
5450 dependencies:
5451 jsesc "~0.5.0"
5452
5453remove-trailing-separator@^1.0.1:
5454 version "1.1.0"
5455 resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
5456
5457repeat-element@^1.1.2:
5458 version "1.1.2"
5459 resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
5460
5461repeat-string@^1.5.2:
5462 version "1.6.1"
5463 resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
5464
5465repeating@^2.0.0:
5466 version "2.0.1"
5467 resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
5468 dependencies:
5469 is-finite "^1.0.0"
5470
5471replace-ext@0.0.1:
5472 version "0.0.1"
5473 resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924"
5474
5475request@2, request@^2.45.0, request@^2.54.0, request@^2.79.0, request@^2.81.0:
5476 version "2.81.0"
5477 resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
5478 dependencies:
5479 aws-sign2 "~0.6.0"
5480 aws4 "^1.2.1"
5481 caseless "~0.12.0"
5482 combined-stream "~1.0.5"
5483 extend "~3.0.0"
5484 forever-agent "~0.6.1"
5485 form-data "~2.1.1"
5486 har-validator "~4.2.1"
5487 hawk "~3.1.3"
5488 http-signature "~1.1.0"
5489 is-typedarray "~1.0.0"
5490 isstream "~0.1.2"
5491 json-stringify-safe "~5.0.1"
5492 mime-types "~2.1.7"
5493 oauth-sign "~0.8.1"
5494 performance-now "^0.2.0"
5495 qs "~6.4.0"
5496 safe-buffer "^5.0.1"
5497 stringstream "~0.0.4"
5498 tough-cookie "~2.3.0"
5499 tunnel-agent "^0.6.0"
5500 uuid "^3.0.0"
5501
5502require-directory@^2.1.1:
5503 version "2.1.1"
5504 resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
5505
5506require-main-filename@^1.0.1:
5507 version "1.0.1"
5508 resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
5509
5510require-uncached@^1.0.3:
5511 version "1.0.3"
5512 resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
5513 dependencies:
5514 caller-path "^0.1.0"
5515 resolve-from "^1.0.0"
5516
5517resolve-dir@^0.1.0:
5518 version "0.1.1"
5519 resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e"
5520 dependencies:
5521 expand-tilde "^1.2.2"
5522 global-modules "^0.2.3"
5523
5524resolve-from@^1.0.0:
5525 version "1.0.1"
5526 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
5527
5528resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0:
5529 version "1.4.0"
5530 resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86"
5531 dependencies:
5532 path-parse "^1.0.5"
5533
5534resolve@~1.1.6:
5535 version "1.1.7"
5536 resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
5537
5538restore-cursor@^2.0.0:
5539 version "2.0.0"
5540 resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
5541 dependencies:
5542 onetime "^2.0.0"
5543 signal-exit "^3.0.2"
5544
5545right-pad@^1.0.1:
5546 version "1.0.1"
5547 resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0"
5548
5549rimraf@2, rimraf@^2.2.8, rimraf@^2.4.0, rimraf@^2.5.1, rimraf@^2.6.1:
5550 version "2.6.1"
5551 resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
5552 dependencies:
5553 glob "^7.0.5"
5554
5555route-parser@^0.0.5:
5556 version "0.0.5"
5557 resolved "https://registry.yarnpkg.com/route-parser/-/route-parser-0.0.5.tgz#7d1d09d335e49094031ea16991a4a79b01bbe1f4"
5558
5559run-async@^2.2.0:
5560 version "2.3.0"
5561 resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
5562 dependencies:
5563 is-promise "^2.1.0"
5564
5565run-series@^1.1.1:
5566 version "1.1.4"
5567 resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.4.tgz#89a73ddc5e75c9ef8ab6320c0a1600d6a41179b9"
5568
5569rx-lite-aggregates@^4.0.8:
5570 version "4.0.8"
5571 resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
5572 dependencies:
5573 rx-lite "*"
5574
5575rx-lite@*, rx-lite@^4.0.8:
5576 version "4.0.8"
5577 resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
5578
5579rx@^2.4.3:
5580 version "2.5.3"
5581 resolved "https://registry.yarnpkg.com/rx/-/rx-2.5.3.tgz#21adc7d80f02002af50dae97fd9dbf248755f566"
5582
5583rxjs-serial-subscription@^0.1.1:
5584 version "0.1.1"
5585 resolved "https://registry.yarnpkg.com/rxjs-serial-subscription/-/rxjs-serial-subscription-0.1.1.tgz#a42b1db0bf1094b09231191e2778ca3fcf9ed147"
5586 dependencies:
5587 rxjs "^5.0.0-beta.12"
5588
5589rxjs@^5.0.0-beta.12, rxjs@^5.0.1, rxjs@^5.1.1:
5590 version "5.4.3"
5591 resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.3.tgz#0758cddee6033d68e0fd53676f0f3596ce3d483f"
5592 dependencies:
5593 symbol-observable "^1.0.1"
5594
5595safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
5596 version "5.1.1"
5597 resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
5598
5599sanitize-filename@^1.6.0, sanitize-filename@^1.6.1:
5600 version "1.6.1"
5601 resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.1.tgz#612da1c96473fa02dccda92dcd5b4ab164a6772a"
5602 dependencies:
5603 truncate-utf8-bytes "^1.0.0"
5604
5605sass-graph@^2.1.1:
5606 version "2.2.4"
5607 resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
5608 dependencies:
5609 glob "^7.0.0"
5610 lodash "^4.0.0"
5611 scss-tokenizer "^0.2.3"
5612 yargs "^7.0.0"
5613
5614sax@^1.2.1:
5615 version "1.2.4"
5616 resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
5617
5618scss-tokenizer@^0.2.3:
5619 version "0.2.3"
5620 resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
5621 dependencies:
5622 js-base64 "^2.1.8"
5623 source-map "^0.4.2"
5624
5625semver-diff@^2.0.0:
5626 version "2.1.0"
5627 resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
5628 dependencies:
5629 semver "^5.0.3"
5630
5631semver-greatest-satisfied-range@^1.0.0:
5632 version "1.1.0"
5633 resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b"
5634 dependencies:
5635 sver-compat "^1.5.0"
5636
5637"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1:
5638 version "5.4.1"
5639 resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
5640
5641semver@~5.3.0:
5642 version "5.3.0"
5643 resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
5644
5645send@0.16.1:
5646 version "0.16.1"
5647 resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
5648 dependencies:
5649 debug "2.6.9"
5650 depd "~1.1.1"
5651 destroy "~1.0.4"
5652 encodeurl "~1.0.1"
5653 escape-html "~1.0.3"
5654 etag "~1.8.1"
5655 fresh "0.5.2"
5656 http-errors "~1.6.2"
5657 mime "1.4.1"
5658 ms "2.0.0"
5659 on-finished "~2.3.0"
5660 range-parser "~1.2.0"
5661 statuses "~1.3.1"
5662
5663send@0.8.5:
5664 version "0.8.5"
5665 resolved "https://registry.yarnpkg.com/send/-/send-0.8.5.tgz#37f708216e6f50c175e74c69fec53484e2fd82c7"
5666 dependencies:
5667 debug "1.0.4"
5668 depd "0.4.4"
5669 destroy "1.0.3"
5670 escape-html "1.0.1"
5671 fresh "0.2.2"
5672 mime "1.2.11"
5673 ms "0.6.2"
5674 on-finished "2.1.0"
5675 range-parser "~1.0.0"
5676
5677serve-index@~1.1.4:
5678 version "1.1.6"
5679 resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.1.6.tgz#b758318fe781628383f66ac80dd447712ea7781f"
5680 dependencies:
5681 accepts "~1.0.7"
5682 batch "0.5.1"
5683 parseurl "~1.3.0"
5684
5685serve-static@1.13.1:
5686 version "1.13.1"
5687 resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719"
5688 dependencies:
5689 encodeurl "~1.0.1"
5690 escape-html "~1.0.3"
5691 parseurl "~1.3.2"
5692 send "0.16.1"
5693
5694serve-static@~1.5.2:
5695 version "1.5.4"
5696 resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.5.4.tgz#819fb37ae46bd02dd520b77fcf7fd8f5112f9782"
5697 dependencies:
5698 escape-html "1.0.1"
5699 parseurl "~1.3.0"
5700 send "0.8.5"
5701 utils-merge "1.0.0"
5702
5703set-blocking@^2.0.0, set-blocking@~2.0.0:
5704 version "2.0.0"
5705 resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
5706
5707set-immediate-shim@^1.0.1:
5708 version "1.0.1"
5709 resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
5710
5711setimmediate@^1.0.5:
5712 version "1.0.5"
5713 resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
5714
5715setprototypeof@1.0.3:
5716 version "1.0.3"
5717 resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
5718
5719setprototypeof@1.1.0:
5720 version "1.1.0"
5721 resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
5722
5723shebang-command@^1.2.0:
5724 version "1.2.0"
5725 resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
5726 dependencies:
5727 shebang-regex "^1.0.0"
5728
5729shebang-regex@^1.0.0:
5730 version "1.0.0"
5731 resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
5732
5733sigmund@~1.0.0:
5734 version "1.0.1"
5735 resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
5736
5737signal-exit@^3.0.0, signal-exit@^3.0.2:
5738 version "3.0.2"
5739 resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
5740
5741single-line-log@^0.4.1:
5742 version "0.4.1"
5743 resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-0.4.1.tgz#87a55649f749d783ec0dcd804e8140d9873c7cee"
5744
5745single-line-log@^1.1.2:
5746 version "1.1.2"
5747 resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364"
5748 dependencies:
5749 string-width "^1.0.1"
5750
5751slash@^1.0.0:
5752 version "1.0.0"
5753 resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
5754
5755slice-ansi@0.0.4:
5756 version "0.0.4"
5757 resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
5758
5759smartwrap@^1.0.7:
5760 version "1.0.9"
5761 resolved "https://registry.yarnpkg.com/smartwrap/-/smartwrap-1.0.9.tgz#7001aa8331482b6fc5021574d68ef04460bb21ca"
5762 dependencies:
5763 breakword "^1.0.3"
5764 merge "^1.2.0"
5765 wcwidth "^1.0.1"
5766 yargs "^8.0.1"
5767
5768smoothscroll-polyfill@^0.3.4:
5769 version "0.3.6"
5770 resolved "https://registry.yarnpkg.com/smoothscroll-polyfill/-/smoothscroll-polyfill-0.3.6.tgz#492be845195157cdc2fc529a95d89e7a71509172"
5771
5772sntp@1.x.x:
5773 version "1.0.9"
5774 resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
5775 dependencies:
5776 hoek "2.x.x"
5777
5778socket.io-adapter@0.5.0:
5779 version "0.5.0"
5780 resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b"
5781 dependencies:
5782 debug "2.3.3"
5783 socket.io-parser "2.3.1"
5784
5785socket.io-client@1.7.4:
5786 version "1.7.4"
5787 resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.4.tgz#ec9f820356ed99ef6d357f0756d648717bdd4281"
5788 dependencies:
5789 backo2 "1.0.2"
5790 component-bind "1.0.0"
5791 component-emitter "1.2.1"
5792 debug "2.3.3"
5793 engine.io-client "~1.8.4"
5794 has-binary "0.1.7"
5795 indexof "0.0.1"
5796 object-component "0.0.3"
5797 parseuri "0.0.5"
5798 socket.io-parser "2.3.1"
5799 to-array "0.1.4"
5800
5801socket.io-parser@2.3.1:
5802 version "2.3.1"
5803 resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0"
5804 dependencies:
5805 component-emitter "1.1.2"
5806 debug "2.2.0"
5807 isarray "0.0.1"
5808 json3 "3.3.2"
5809
5810socket.io@^1.4.4:
5811 version "1.7.4"
5812 resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.4.tgz#2f7ecedc3391bf2d5c73e291fe233e6e34d4dd00"
5813 dependencies:
5814 debug "2.3.3"
5815 engine.io "~1.8.4"
5816 has-binary "0.1.7"
5817 object-assign "4.1.0"
5818 socket.io-adapter "0.5.0"
5819 socket.io-client "1.7.4"
5820 socket.io-parser "2.3.1"
5821
5822sort-keys@^1.0.0:
5823 version "1.1.2"
5824 resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
5825 dependencies:
5826 is-plain-obj "^1.0.0"
5827
5828source-map-support@^0.4.15, source-map-support@^0.4.16:
5829 version "0.4.17"
5830 resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.17.tgz#6f2150553e6375375d0ccb3180502b78c18ba430"
5831 dependencies:
5832 source-map "^0.5.6"
5833
5834source-map@^0.4.2:
5835 version "0.4.4"
5836 resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
5837 dependencies:
5838 amdefine ">=0.0.4"
5839
5840source-map@^0.5.1, source-map@^0.5.6:
5841 version "0.5.7"
5842 resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
5843
5844sparkles@^1.0.0:
5845 version "1.0.0"
5846 resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3"
5847
5848spawn-rx@^2.0.10, spawn-rx@^2.0.7:
5849 version "2.0.11"
5850 resolved "https://registry.yarnpkg.com/spawn-rx/-/spawn-rx-2.0.11.tgz#65451ad65662801daea75549832a782de0048dbf"
5851 dependencies:
5852 debug "^2.5.1"
5853 lodash.assign "^4.2.0"
5854 rxjs "^5.1.1"
5855
5856spdx-correct@~1.0.0:
5857 version "1.0.2"
5858 resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
5859 dependencies:
5860 spdx-license-ids "^1.0.2"
5861
5862spdx-expression-parse@~1.0.0:
5863 version "1.0.4"
5864 resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
5865
5866spdx-license-ids@^1.0.2:
5867 version "1.2.2"
5868 resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
5869
5870speedometer@~0.1.2:
5871 version "0.1.4"
5872 resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d"
5873
5874spin.js@2.x:
5875 version "2.3.2"
5876 resolved "https://registry.yarnpkg.com/spin.js/-/spin.js-2.3.2.tgz#6caa56d520673450fd5cfbc6971e6d0772c37a1a"
5877
5878sprintf-js@~1.0.2:
5879 version "1.0.3"
5880 resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
5881
5882sshpk@^1.7.0:
5883 version "1.13.1"
5884 resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
5885 dependencies:
5886 asn1 "~0.2.3"
5887 assert-plus "^1.0.0"
5888 dashdash "^1.12.0"
5889 getpass "^0.1.1"
5890 optionalDependencies:
5891 bcrypt-pbkdf "^1.0.0"
5892 ecc-jsbn "~0.1.1"
5893 jsbn "~0.1.0"
5894 tweetnacl "~0.14.0"
5895
5896stack-trace@0.0.9:
5897 version "0.0.9"
5898 resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695"
5899
5900stat-mode@^0.2.2:
5901 version "0.2.2"
5902 resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502"
5903
5904"statuses@>= 1.3.1 < 2", statuses@~1.3.1:
5905 version "1.3.1"
5906 resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
5907
5908stdout-stream@^1.4.0:
5909 version "1.4.0"
5910 resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b"
5911 dependencies:
5912 readable-stream "^2.0.1"
5913
5914stream-exhaust@^1.0.1:
5915 version "1.0.2"
5916 resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d"
5917
5918stream-shift@^1.0.0:
5919 version "1.0.0"
5920 resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
5921
5922stream-transform@^0.1.0:
5923 version "0.1.2"
5924 resolved "https://registry.yarnpkg.com/stream-transform/-/stream-transform-0.1.2.tgz#7d8e6b4e03ac4781778f8c79517501bfb0762a9f"
5925
5926strict-uri-encode@^1.0.0:
5927 version "1.1.0"
5928 resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
5929
5930string-editor@^0.1.0:
5931 version "0.1.2"
5932 resolved "https://registry.yarnpkg.com/string-editor/-/string-editor-0.1.2.tgz#f5ff1b5ac4aed7ac6c2fb8de236d1551b20f61d0"
5933 dependencies:
5934 editor "^1.0.0"
5935
5936string-width@^1.0.1, string-width@^1.0.2:
5937 version "1.0.2"
5938 resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
5939 dependencies:
5940 code-point-at "^1.0.0"
5941 is-fullwidth-code-point "^1.0.0"
5942 strip-ansi "^3.0.0"
5943
5944string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
5945 version "2.1.1"
5946 resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
5947 dependencies:
5948 is-fullwidth-code-point "^2.0.0"
5949 strip-ansi "^4.0.0"
5950
5951string_decoder@~0.10.x:
5952 version "0.10.31"
5953 resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
5954
5955string_decoder@~1.0.3:
5956 version "1.0.3"
5957 resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
5958 dependencies:
5959 safe-buffer "~5.1.0"
5960
5961stringstream@~0.0.4:
5962 version "0.0.5"
5963 resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
5964
5965strip-ansi@^2.0.1:
5966 version "2.0.1"
5967 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-2.0.1.tgz#df62c1aa94ed2f114e1d0f21fd1d50482b79a60e"
5968 dependencies:
5969 ansi-regex "^1.0.0"
5970
5971strip-ansi@^3.0.0, strip-ansi@^3.0.1:
5972 version "3.0.1"
5973 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
5974 dependencies:
5975 ansi-regex "^2.0.0"
5976
5977strip-ansi@^4.0.0:
5978 version "4.0.0"
5979 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
5980 dependencies:
5981 ansi-regex "^3.0.0"
5982
5983strip-bom-stream@^1.0.0:
5984 version "1.0.0"
5985 resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee"
5986 dependencies:
5987 first-chunk-stream "^1.0.0"
5988 strip-bom "^2.0.0"
5989
5990strip-bom@^1.0.0:
5991 version "1.0.0"
5992 resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794"
5993 dependencies:
5994 first-chunk-stream "^1.0.0"
5995 is-utf8 "^0.2.0"
5996
5997strip-bom@^2.0.0:
5998 version "2.0.0"
5999 resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
6000 dependencies:
6001 is-utf8 "^0.2.0"
6002
6003strip-bom@^3.0.0:
6004 version "3.0.0"
6005 resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
6006
6007strip-eof@^1.0.0:
6008 version "1.0.0"
6009 resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
6010
6011strip-indent@^1.0.1:
6012 version "1.0.1"
6013 resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
6014 dependencies:
6015 get-stdin "^4.0.1"
6016
6017strip-json-comments@~2.0.1:
6018 version "2.0.1"
6019 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
6020
6021sumchecker@^1.2.0:
6022 version "1.3.1"
6023 resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-1.3.1.tgz#79bb3b4456dd04f18ebdbc0d703a1d1daec5105d"
6024 dependencies:
6025 debug "^2.2.0"
6026 es6-promise "^4.0.5"
6027
6028sumchecker@^2.0.1, sumchecker@^2.0.2:
6029 version "2.0.2"
6030 resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e"
6031 dependencies:
6032 debug "^2.2.0"
6033
6034supports-color@^2.0.0:
6035 version "2.0.0"
6036 resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
6037
6038supports-color@^4.0.0:
6039 version "4.4.0"
6040 resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
6041 dependencies:
6042 has-flag "^2.0.0"
6043
6044sver-compat@^1.5.0:
6045 version "1.5.0"
6046 resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8"
6047 dependencies:
6048 es6-iterator "^2.0.1"
6049 es6-symbol "^3.1.1"
6050
6051symbol-observable@^1.0.1:
6052 version "1.0.4"
6053 resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
6054
6055table@^4.0.1:
6056 version "4.0.1"
6057 resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435"
6058 dependencies:
6059 ajv "^4.7.0"
6060 ajv-keywords "^1.0.0"
6061 chalk "^1.1.1"
6062 lodash "^4.0.0"
6063 slice-ansi "0.0.4"
6064 string-width "^2.0.0"
6065
6066tar-pack@^3.4.0:
6067 version "3.4.0"
6068 resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984"
6069 dependencies:
6070 debug "^2.2.0"
6071 fstream "^1.0.10"
6072 fstream-ignore "^1.0.5"
6073 once "^1.3.3"
6074 readable-stream "^2.1.4"
6075 rimraf "^2.5.1"
6076 tar "^2.2.1"
6077 uid-number "^0.0.6"
6078
6079tar.gz@^1.0.5:
6080 version "1.0.5"
6081 resolved "https://registry.yarnpkg.com/tar.gz/-/tar.gz-1.0.5.tgz#e1ada7e45ef2241b4b1ee58123c8f40b5d3c1bc4"
6082 dependencies:
6083 bluebird "^2.9.34"
6084 commander "^2.8.1"
6085 fstream "^1.0.8"
6086 mout "^0.11.0"
6087 tar "^2.1.1"
6088
6089tar@^2.0.0, tar@^2.1.1, tar@^2.2.1:
6090 version "2.2.1"
6091 resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
6092 dependencies:
6093 block-stream "*"
6094 fstream "^1.0.2"
6095 inherits "2"
6096
6097tempfile@^1.1.1:
6098 version "1.1.1"
6099 resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-1.1.1.tgz#5bcc4eaecc4ab2c707d8bc11d99ccc9a2cb287f2"
6100 dependencies:
6101 os-tmpdir "^1.0.0"
6102 uuid "^2.0.1"
6103
6104term-size@^1.2.0:
6105 version "1.2.0"
6106 resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
6107 dependencies:
6108 execa "^0.7.0"
6109
6110text-table@~0.2.0:
6111 version "0.2.0"
6112 resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
6113
6114throttleit@0.0.2:
6115 version "0.0.2"
6116 resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf"
6117
6118through2-filter@^2.0.0:
6119 version "2.0.0"
6120 resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec"
6121 dependencies:
6122 through2 "~2.0.0"
6123 xtend "~4.0.0"
6124
6125through2@^0.6.0, through2@^0.6.1, through2@~0.6.3:
6126 version "0.6.5"
6127 resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
6128 dependencies:
6129 readable-stream ">=1.0.33-1 <1.1.0-0"
6130 xtend ">=4.0.0 <4.1.0-0"
6131
6132through2@^2.0.0, through2@^2.0.1, through2@~2.0.0:
6133 version "2.0.3"
6134 resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
6135 dependencies:
6136 readable-stream "^2.1.5"
6137 xtend "~4.0.1"
6138
6139through2@~0.2.3:
6140 version "0.2.3"
6141 resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f"
6142 dependencies:
6143 readable-stream "~1.1.9"
6144 xtend "~2.1.1"
6145
6146through2@~0.5.1:
6147 version "0.5.1"
6148 resolved "https://registry.yarnpkg.com/through2/-/through2-0.5.1.tgz#dfdd012eb9c700e2323fd334f38ac622ab372da7"
6149 dependencies:
6150 readable-stream "~1.0.17"
6151 xtend "~3.0.0"
6152
6153through@^2.3.6:
6154 version "2.3.8"
6155 resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
6156
6157tildify@^1.0.0:
6158 version "1.2.0"
6159 resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a"
6160 dependencies:
6161 os-homedir "^1.0.0"
6162
6163time-stamp@^1.0.0:
6164 version "1.1.0"
6165 resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3"
6166
6167timed-out@^4.0.0:
6168 version "4.0.1"
6169 resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
6170
6171tmp@0.0.28:
6172 version "0.0.28"
6173 resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120"
6174 dependencies:
6175 os-tmpdir "~1.0.1"
6176
6177tmp@^0.0.33:
6178 version "0.0.33"
6179 resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
6180 dependencies:
6181 os-tmpdir "~1.0.2"
6182
6183to-absolute-glob@^0.1.1:
6184 version "0.1.1"
6185 resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f"
6186 dependencies:
6187 extend-shallow "^2.0.1"
6188
6189to-array@0.1.4:
6190 version "0.1.4"
6191 resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
6192
6193to-fast-properties@^1.0.3:
6194 version "1.0.3"
6195 resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
6196
6197topo@1.x.x:
6198 version "1.1.0"
6199 resolved "https://registry.yarnpkg.com/topo/-/topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5"
6200 dependencies:
6201 hoek "2.x.x"
6202
6203touch@0.0.3:
6204 version "0.0.3"
6205 resolved "https://registry.yarnpkg.com/touch/-/touch-0.0.3.tgz#51aef3d449571d4f287a5d87c9c8b49181a0db1d"
6206 dependencies:
6207 nopt "~1.0.10"
6208
6209tough-cookie@~2.3.0:
6210 version "2.3.2"
6211 resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
6212 dependencies:
6213 punycode "^1.4.1"
6214
6215"traverse@>=0.3.0 <0.4":
6216 version "0.3.9"
6217 resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
6218
6219trim-newlines@^1.0.0:
6220 version "1.0.0"
6221 resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
6222
6223trim-right@^1.0.1:
6224 version "1.0.1"
6225 resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
6226
6227truncate-utf8-bytes@^1.0.0:
6228 version "1.0.2"
6229 resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
6230 dependencies:
6231 utf8-byte-length "^1.0.1"
6232
6233tryit@^1.0.1:
6234 version "1.0.3"
6235 resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb"
6236
6237tty-table@^2.5.5:
6238 version "2.5.5"
6239 resolved "https://registry.yarnpkg.com/tty-table/-/tty-table-2.5.5.tgz#248ef188468ff1dff75a379c77cbe1f1c81c8a79"
6240 dependencies:
6241 chalk "^1.1.0"
6242 csv "^1.1.1"
6243 merge "^1.2.0"
6244 smartwrap "^1.0.7"
6245 strip-ansi "^3.0.0"
6246 use-strict "^1.0.1"
6247 wcwidth "^1.0.1"
6248 yargs "^8.0.1"
6249
6250tunnel-agent@^0.6.0:
6251 version "0.6.0"
6252 resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
6253 dependencies:
6254 safe-buffer "^5.0.1"
6255
6256tweetnacl@^0.14.3, tweetnacl@~0.14.0:
6257 version "0.14.5"
6258 resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
6259
6260type-check@~0.3.2:
6261 version "0.3.2"
6262 resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
6263 dependencies:
6264 prelude-ls "~1.1.2"
6265
6266type-is@~1.6.15:
6267 version "1.6.15"
6268 resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
6269 dependencies:
6270 media-typer "0.3.0"
6271 mime-types "~2.1.15"
6272
6273typedarray@^0.0.6:
6274 version "0.0.6"
6275 resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
6276
6277ua-parser-js@^0.7.9:
6278 version "0.7.14"
6279 resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.14.tgz#110d53fa4c3f326c121292bbeac904d2e03387ca"
6280
6281uid-number@^0.0.6:
6282 version "0.0.6"
6283 resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
6284
6285ultron@1.0.x:
6286 version "1.0.2"
6287 resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
6288
6289ultron@~1.1.0:
6290 version "1.1.0"
6291 resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864"
6292
6293unc-path-regex@^0.1.0:
6294 version "0.1.2"
6295 resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
6296
6297underscore@^1.6.0:
6298 version "1.8.3"
6299 resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
6300
6301undertaker-registry@^1.0.0:
6302 version "1.0.0"
6303 resolved "https://registry.yarnpkg.com/undertaker-registry/-/undertaker-registry-1.0.0.tgz#2da716c765999d8c94b9f9ed2c006df4923b052b"
6304
6305undertaker@^1.0.0:
6306 version "1.2.0"
6307 resolved "https://registry.yarnpkg.com/undertaker/-/undertaker-1.2.0.tgz#339da4646252d082dc378e708067299750e11b49"
6308 dependencies:
6309 arr-flatten "^1.0.1"
6310 arr-map "^2.0.0"
6311 bach "^1.0.0"
6312 collection-map "^1.0.0"
6313 es6-weak-map "^2.0.1"
6314 last-run "^1.1.0"
6315 object.defaults "^1.0.0"
6316 object.reduce "^1.0.0"
6317 undertaker-registry "^1.0.0"
6318
6319unique-stream@^2.0.2:
6320 version "2.2.1"
6321 resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369"
6322 dependencies:
6323 json-stable-stringify "^1.0.0"
6324 through2-filter "^2.0.0"
6325
6326unique-string@^1.0.0:
6327 version "1.0.0"
6328 resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
6329 dependencies:
6330 crypto-random-string "^1.0.0"
6331
6332universalify@^0.1.0:
6333 version "0.1.1"
6334 resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
6335
6336unpipe@1.0.0, unpipe@~1.0.0:
6337 version "1.0.0"
6338 resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
6339
6340unzip-response@^2.0.1:
6341 version "2.0.1"
6342 resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
6343
6344update-notifier@^2.2.0:
6345 version "2.2.0"
6346 resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.2.0.tgz#1b5837cf90c0736d88627732b661c138f86de72f"
6347 dependencies:
6348 boxen "^1.0.0"
6349 chalk "^1.0.0"
6350 configstore "^3.0.0"
6351 import-lazy "^2.1.0"
6352 is-npm "^1.0.0"
6353 latest-version "^3.0.0"
6354 semver-diff "^2.0.0"
6355 xdg-basedir "^3.0.0"
6356
6357url-parse-lax@^1.0.0:
6358 version "1.0.0"
6359 resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
6360 dependencies:
6361 prepend-http "^1.0.1"
6362
6363use-strict@^1.0.1:
6364 version "1.0.1"
6365 resolved "https://registry.yarnpkg.com/use-strict/-/use-strict-1.0.1.tgz#0bb80d94f49a4a05192b84a8c7d34e95f1a7e3a0"
6366
6367user-home@^1.1.1:
6368 version "1.1.1"
6369 resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190"
6370
6371utf8-byte-length@^1.0.1:
6372 version "1.0.4"
6373 resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61"
6374
6375util-deprecate@~1.0.1:
6376 version "1.0.2"
6377 resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
6378
6379utils-merge@1.0.0:
6380 version "1.0.0"
6381 resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8"
6382
6383utils-merge@1.0.1:
6384 version "1.0.1"
6385 resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
6386
6387uuid-1345@^0.99.6:
6388 version "0.99.6"
6389 resolved "https://registry.yarnpkg.com/uuid-1345/-/uuid-1345-0.99.6.tgz#b1270ae015a7721c7adec6c46ec169c6098aed40"
6390 dependencies:
6391 macaddress "^0.2.7"
6392
6393uuid@^2.0.1:
6394 version "2.0.3"
6395 resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
6396
6397uuid@^3.0.0, uuid@^3.0.1:
6398 version "3.1.0"
6399 resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
6400
6401v8flags@^2.0.9:
6402 version "2.1.1"
6403 resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
6404 dependencies:
6405 user-home "^1.1.1"
6406
6407vali-date@^1.0.0:
6408 version "1.0.0"
6409 resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6"
6410
6411validate-npm-package-license@^3.0.1:
6412 version "3.0.1"
6413 resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
6414 dependencies:
6415 spdx-correct "~1.0.0"
6416 spdx-expression-parse "~1.0.0"
6417
6418vary@~1.1.2:
6419 version "1.1.2"
6420 resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
6421
6422verror@1.10.0:
6423 version "1.10.0"
6424 resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
6425 dependencies:
6426 assert-plus "^1.0.0"
6427 core-util-is "1.0.2"
6428 extsprintf "^1.2.0"
6429
6430vinyl-fs@^1.0.0:
6431 version "1.0.0"
6432 resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-1.0.0.tgz#d15752e68c2dad74364e7e853473735354692edf"
6433 dependencies:
6434 duplexify "^3.2.0"
6435 glob-stream "^4.0.1"
6436 glob-watcher "^0.0.8"
6437 graceful-fs "^3.0.0"
6438 merge-stream "^0.1.7"
6439 mkdirp "^0.5.0"
6440 object-assign "^2.0.0"
6441 strip-bom "^1.0.0"
6442 through2 "^0.6.1"
6443 vinyl "^0.4.0"
6444
6445vinyl-fs@^2.0.0:
6446 version "2.4.4"
6447 resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239"
6448 dependencies:
6449 duplexify "^3.2.0"
6450 glob-stream "^5.3.2"
6451 graceful-fs "^4.0.0"
6452 gulp-sourcemaps "1.6.0"
6453 is-valid-glob "^0.3.0"
6454 lazystream "^1.0.0"
6455 lodash.isequal "^4.0.0"
6456 merge-stream "^1.0.0"
6457 mkdirp "^0.5.0"
6458 object-assign "^4.0.0"
6459 readable-stream "^2.0.4"
6460 strip-bom "^2.0.0"
6461 strip-bom-stream "^1.0.0"
6462 through2 "^2.0.0"
6463 through2-filter "^2.0.0"
6464 vali-date "^1.0.0"
6465 vinyl "^1.0.0"
6466
6467vinyl-sourcemaps-apply@^0.2.0:
6468 version "0.2.1"
6469 resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705"
6470 dependencies:
6471 source-map "^0.5.1"
6472
6473vinyl@^0.4.0:
6474 version "0.4.6"
6475 resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847"
6476 dependencies:
6477 clone "^0.2.0"
6478 clone-stats "^0.0.1"
6479
6480vinyl@^0.5.0:
6481 version "0.5.3"
6482 resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde"
6483 dependencies:
6484 clone "^1.0.0"
6485 clone-stats "^0.0.1"
6486 replace-ext "0.0.1"
6487
6488vinyl@^1.0.0:
6489 version "1.2.0"
6490 resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884"
6491 dependencies:
6492 clone "^1.0.0"
6493 clone-stats "^0.0.1"
6494 replace-ext "0.0.1"
6495
6496warning@^3.0.0:
6497 version "3.0.0"
6498 resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
6499 dependencies:
6500 loose-envify "^1.0.0"
6501
6502wcwidth@^1.0.1:
6503 version "1.0.1"
6504 resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
6505 dependencies:
6506 defaults "^1.0.3"
6507
6508whatwg-fetch@>=0.10.0:
6509 version "2.0.3"
6510 resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
6511
6512which-module@^1.0.0:
6513 version "1.0.0"
6514 resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
6515
6516which-module@^2.0.0:
6517 version "2.0.0"
6518 resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
6519
6520which@1, which@^1.2.12, which@^1.2.9:
6521 version "1.3.0"
6522 resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
6523 dependencies:
6524 isexe "^2.0.0"
6525
6526wide-align@^1.1.0:
6527 version "1.1.2"
6528 resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
6529 dependencies:
6530 string-width "^1.0.2"
6531
6532widest-line@^1.0.0:
6533 version "1.0.0"
6534 resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c"
6535 dependencies:
6536 string-width "^1.0.1"
6537
6538window-size@^0.1.4:
6539 version "0.1.4"
6540 resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876"
6541
6542word-wrap@^1.0.3:
6543 version "1.2.3"
6544 resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
6545
6546wordwrap@~1.0.0:
6547 version "1.0.0"
6548 resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
6549
6550wrap-ansi@^2.0.0:
6551 version "2.1.0"
6552 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
6553 dependencies:
6554 string-width "^1.0.1"
6555 strip-ansi "^3.0.1"
6556
6557wrap-ansi@^3.0.1:
6558 version "3.0.1"
6559 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba"
6560 dependencies:
6561 string-width "^2.1.1"
6562 strip-ansi "^4.0.0"
6563
6564wrappy@1:
6565 version "1.0.2"
6566 resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
6567
6568wreck@^6.3.0:
6569 version "6.3.0"
6570 resolved "https://registry.yarnpkg.com/wreck/-/wreck-6.3.0.tgz#a1369769f07bbb62d6a378336a7871fc773c740b"
6571 dependencies:
6572 boom "2.x.x"
6573 hoek "2.x.x"
6574
6575write-file-atomic@^2.0.0:
6576 version "2.3.0"
6577 resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab"
6578 dependencies:
6579 graceful-fs "^4.1.11"
6580 imurmurhash "^0.1.4"
6581 signal-exit "^3.0.2"
6582
6583write@^0.2.1:
6584 version "0.2.1"
6585 resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
6586 dependencies:
6587 mkdirp "^0.5.1"
6588
6589ws@1.1.2:
6590 version "1.1.2"
6591 resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f"
6592 dependencies:
6593 options ">=0.0.5"
6594 ultron "1.0.x"
6595
6596ws@1.1.4:
6597 version "1.1.4"
6598 resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.4.tgz#57f40d036832e5f5055662a397c4de76ed66bf61"
6599 dependencies:
6600 options ">=0.0.5"
6601 ultron "1.0.x"
6602
6603ws@^3.0.0, ws@^3.2.0:
6604 version "3.2.0"
6605 resolved "https://registry.yarnpkg.com/ws/-/ws-3.2.0.tgz#d5d3d6b11aff71e73f808f40cc69d52bb6d4a185"
6606 dependencies:
6607 async-limiter "~1.0.0"
6608 safe-buffer "~5.1.0"
6609 ultron "~1.1.0"
6610
6611wtf-8@1.0.0:
6612 version "1.0.0"
6613 resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a"
6614
6615xdg-basedir@^3.0.0:
6616 version "3.0.0"
6617 resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
6618
6619xelement@^1.0.16:
6620 version "1.0.16"
6621 resolved "https://registry.yarnpkg.com/xelement/-/xelement-1.0.16.tgz#900bb46c20fc2dffadff778a9d2dc36699d0ff7e"
6622 dependencies:
6623 sax "^1.2.1"
6624
6625xmlbuilder@8.2.2:
6626 version "8.2.2"
6627 resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773"
6628
6629xmldom@0.1.x:
6630 version "0.1.27"
6631 resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
6632
6633xmlhttprequest-ssl@1.5.3:
6634 version "1.5.3"
6635 resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"
6636
6637xmlhttprequest@^1.8.0:
6638 version "1.8.0"
6639 resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
6640
6641"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1:
6642 version "4.0.1"
6643 resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
6644
6645xtend@~2.1.1:
6646 version "2.1.2"
6647 resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b"
6648 dependencies:
6649 object-keys "~0.4.0"
6650
6651xtend@~3.0.0:
6652 version "3.0.0"
6653 resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a"
6654
6655y18n@^3.2.0, y18n@^3.2.1:
6656 version "3.2.1"
6657 resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
6658
6659yallist@^2.1.2:
6660 version "2.1.2"
6661 resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
6662
6663yargs-parser@^5.0.0:
6664 version "5.0.0"
6665 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
6666 dependencies:
6667 camelcase "^3.0.0"
6668
6669yargs-parser@^7.0.0:
6670 version "7.0.0"
6671 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
6672 dependencies:
6673 camelcase "^4.1.0"
6674
6675yargs@^3.28.0:
6676 version "3.32.0"
6677 resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"
6678 dependencies:
6679 camelcase "^2.0.1"
6680 cliui "^3.0.3"
6681 decamelize "^1.1.1"
6682 os-locale "^1.4.0"
6683 string-width "^1.0.1"
6684 window-size "^0.1.4"
6685 y18n "^3.2.0"
6686
6687yargs@^7.0.0, yargs@^7.0.2:
6688 version "7.1.0"
6689 resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
6690 dependencies:
6691 camelcase "^3.0.0"
6692 cliui "^3.2.0"
6693 decamelize "^1.1.1"
6694 get-caller-file "^1.0.1"
6695 os-locale "^1.4.0"
6696 read-pkg-up "^1.0.1"
6697 require-directory "^2.1.1"
6698 require-main-filename "^1.0.1"
6699 set-blocking "^2.0.0"
6700 string-width "^1.0.2"
6701 which-module "^1.0.0"
6702 y18n "^3.2.1"
6703 yargs-parser "^5.0.0"
6704
6705yargs@^8.0.1, yargs@^8.0.2:
6706 version "8.0.2"
6707 resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
6708 dependencies:
6709 camelcase "^4.1.0"
6710 cliui "^3.2.0"
6711 decamelize "^1.1.1"
6712 get-caller-file "^1.0.1"
6713 os-locale "^2.0.0"
6714 read-pkg-up "^2.0.0"
6715 require-directory "^2.1.1"
6716 require-main-filename "^1.0.1"
6717 set-blocking "^2.0.0"
6718 string-width "^2.0.0"
6719 which-module "^2.0.0"
6720 y18n "^3.2.1"
6721 yargs-parser "^7.0.0"
6722
6723yauzl@2.4.1:
6724 version "2.4.1"
6725 resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
6726 dependencies:
6727 fd-slicer "~1.0.1"
6728
6729yeast@0.1.2:
6730 version "0.1.2"
6731 resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"