aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Dominik Guzei <dominik.guzei@gmail.com>2019-04-11 16:54:01 +0200
committerLibravatar Stefan Malzner <stefan@adlk.io>2019-04-11 16:54:01 +0200
commit47c1c99d893517efc679ab29d675cc0bf44be8be (patch)
tree9cab9697096bef0ce56d8ee8709bc1c2c3a42deb
parenttest package order (diff)
downloadferdium-app-47c1c99d893517efc679ab29d675cc0bf44be8be.tar.gz
ferdium-app-47c1c99d893517efc679ab29d675cc0bf44be8be.tar.zst
ferdium-app-47c1c99d893517efc679ab29d675cc0bf44be8be.zip
feat(App): Added Workspaces for all your daily routines 🥳
* merge default and fetched feature configs * ignore intellij project files * basic setup for workspaces feature * define workspaces as premium feature * add workspaces menu item in settings dialog * basic setup of workspaces settings screen * fix eslint error * assign react key prop to workspace items * add styles for workspace table * setup logic to display workspace edit page * consolidate workspace feature for further development * prepare basic workspace edit form * add on enter key handler for form input component * add form for creating workspaces * small fixes * adds flow for deleting workspaces * stop tracking google analytics in components * pin gulp-sass-variables version to 1.1.1 * fix merge conflict * fix bug in form input library * improve workspace form setup * finish basic workspace settings * finish workspaces mvp * fix eslint issues * remove dev logs * detach service when underlying webview unmounts * disable no-param-reassign eslint rule * add workspace drawer * change workspace switch shortcuts to start with zero * add workspace drawer toggle menu item and shortcut * improve workspace switching ux * style add workspace icon in drawer like the sidebar icons * improve workspace drawer layout * add i18n messages for service loading and workspace switching * small fixes * add tooltip to add workspace button in drawer * add workspaces count badge in settings navigation * fix merge conflicts with latest develop * refactor state management for workspace feature * reset api requests when workspace feature is stopped * hide workspace feature if it is disabled * handle get workspaces request errors in the ui * show infobox when updating workspaces * indicate any server interaction with spinners and infoboxes * add analytic events for workspace actions * improve styling of workspace switch indicator * add workspace premium notice to dashboard * add workspace feature info in drawer for free users * add workspace premium badge in settings nav * fix premium workspace badge in settings menu for light theme * fix active workspaces settings premium badge in light theme * give upgrade account button a bit more padding * add open last used workspace logic * use mobx-localstorage directly in the store * fix wrong workspace tooltip shortcut in sidebar * fix bug in workspace feature initialization * show workspaces intro in drawer when user has none yet * fix issues for users that have workspace but downgraded to free * border radius for premium intro in workspace settings * close workspace drawer after clicking on a workspace * add hover effect for drawer workspace items * ensure drawer is open on workspace settings routes * add small text label for adding new workspace to drawer * make workspace settings list items taller * refactor workspace table css away from legacy styles * render workspace service list like services + toggle * change plus icon in workspace drawer to settings icon * autofocus create workspace input field * add css transition to drawer workspace item hover * fix drawer add workspace label styles * refactors workspace theme vars into object structure * improve contrast of workspace switching indicator * added generic pro badge component for settings nav * add premium badge to workspace drawer headline * add context menu for workspace drawer items * handle deleted services that are attached to workspaces
-rw-r--r--.eslintrc3
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG.md1
-rw-r--r--package-lock.json322
-rw-r--r--packages/forms/src/input/index.tsx12
-rw-r--r--packages/forms/src/input/styles.ts5
-rw-r--r--packages/forms/src/label/styles.ts4
-rw-r--r--packages/forms/src/select/index.tsx6
-rw-r--r--packages/forms/src/toggle/index.tsx4
-rw-r--r--packages/theme/src/themes/dark/index.ts67
-rw-r--r--packages/theme/src/themes/default/index.ts78
-rw-r--r--packages/ui/src/badge/ProBadge.tsx64
-rw-r--r--packages/ui/src/index.ts1
-rw-r--r--packages/ui/src/infobox/index.tsx7
-rw-r--r--packages/ui/src/loader/index.tsx4
-rw-r--r--src/actions/index.js6
-rw-r--r--src/actions/lib/actions.js4
-rw-r--r--src/app.js5
-rw-r--r--src/components/layout/AppLayout.js26
-rw-r--r--src/components/layout/Sidebar.js49
-rw-r--r--src/components/services/content/ServiceView.js5
-rw-r--r--src/components/services/tabs/Tabbar.js4
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js29
-rw-r--r--src/components/settings/services/EditServiceForm.js10
-rw-r--r--src/components/settings/services/ServicesDashboard.js2
-rw-r--r--src/components/settings/settings/EditSettingsForm.js1
-rw-r--r--src/components/ui/AppLoader/index.js4
-rw-r--r--src/components/ui/FullscreenLoader/index.js4
-rw-r--r--src/components/ui/Infobox.js17
-rw-r--r--src/components/ui/PremiumFeatureContainer/index.js21
-rw-r--r--src/components/ui/PremiumFeatureContainer/styles.js5
-rw-r--r--src/components/ui/ServiceIcon.js67
-rw-r--r--src/components/ui/WebviewLoader/index.js18
-rw-r--r--src/config.js2
-rw-r--r--src/containers/layout/AppLayoutContainer.js15
-rw-r--r--src/containers/settings/SettingsWindow.js2
-rw-r--r--src/environment.js1
-rw-r--r--src/features/delayApp/Component.js2
-rw-r--r--src/features/delayApp/index.js2
-rw-r--r--src/features/utils/FeatureStore.js21
-rw-r--r--src/features/workspaces/actions.js26
-rw-r--r--src/features/workspaces/api.js66
-rw-r--r--src/features/workspaces/components/CreateWorkspaceForm.js100
-rw-r--r--src/features/workspaces/components/EditWorkspaceForm.js189
-rw-r--r--src/features/workspaces/components/WorkspaceDrawer.js246
-rw-r--r--src/features/workspaces/components/WorkspaceDrawerItem.js137
-rw-r--r--src/features/workspaces/components/WorkspaceItem.js45
-rw-r--r--src/features/workspaces/components/WorkspaceServiceListItem.js75
-rw-r--r--src/features/workspaces/components/WorkspaceSwitchingIndicator.js91
-rw-r--r--src/features/workspaces/components/WorkspacesDashboard.js195
-rw-r--r--src/features/workspaces/containers/EditWorkspaceScreen.js60
-rw-r--r--src/features/workspaces/containers/WorkspacesScreen.js42
-rw-r--r--src/features/workspaces/index.js37
-rw-r--r--src/features/workspaces/models/Workspace.js25
-rw-r--r--src/features/workspaces/store.js276
-rw-r--r--src/i18n/locales/de.json12
-rw-r--r--src/i18n/locales/defaultMessages.json748
-rw-r--r--src/i18n/locales/en-US.json39
-rw-r--r--src/i18n/messages/src/components/layout/AppLayout.json24
-rw-r--r--src/i18n/messages/src/components/layout/Sidebar.json42
-rw-r--r--src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json37
-rw-r--r--src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json4
-rw-r--r--src/i18n/messages/src/components/ui/WebviewLoader/index.json15
-rw-r--r--src/i18n/messages/src/features/workspaces/components/CreateWorkspaceForm.json28
-rw-r--r--src/i18n/messages/src/features/workspaces/components/EditWorkspaceForm.json67
-rw-r--r--src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json106
-rw-r--r--src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json28
-rw-r--r--src/i18n/messages/src/features/workspaces/components/WorkspaceSwitchingIndicator.json15
-rw-r--r--src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json106
-rw-r--r--src/i18n/messages/src/lib/Menu.json253
-rw-r--r--src/lib/Menu.js94
-rw-r--r--src/lib/analytics.js4
-rw-r--r--src/stores/FeaturesStore.js29
-rw-r--r--src/stores/ServicesStore.js9
-rw-r--r--src/stores/UIStore.js9
-rw-r--r--src/stores/UserStore.js4
-rw-r--r--src/stores/lib/Request.js6
-rw-r--r--src/styles/layout.scss13
-rw-r--r--src/styles/settings.scss9
-rw-r--r--uidev/src/stories/badge.stories.tsx12
80 files changed, 3742 insertions, 482 deletions
diff --git a/.eslintrc b/.eslintrc
index 743946d35..e61d99c20 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -2,6 +2,7 @@
2 "parser": "babel-eslint", 2 "parser": "babel-eslint",
3 "extends": "eslint-config-airbnb", 3 "extends": "eslint-config-airbnb",
4 "rules": { 4 "rules": {
5 "consistent-return": 0,
5 "no-param-reassign": 0, 6 "no-param-reassign": 0,
6 "import/extensions": 0, 7 "import/extensions": 0,
7 "import/no-extraneous-dependencies": 0, 8 "import/no-extraneous-dependencies": 0,
@@ -14,7 +15,7 @@
14 "react/jsx-filename-extension": [1, { 15 "react/jsx-filename-extension": [1, {
15 "extensions": [".js", ".jsx"] 16 "extensions": [".js", ".jsx"]
16 }], 17 }],
17 "react/forbid-prop-types": 1, 18 "react/forbid-prop-types": 0,
18 "react/destructuring-assignment": 1, 19 "react/destructuring-assignment": 1,
19 "prefer-destructuring": 1, 20 "prefer-destructuring": 1,
20 "no-underscore-dangle": 0, 21 "no-underscore-dangle": 0,
diff --git a/.gitignore b/.gitignore
index a5677f0b8..d38c475bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
1.idea
1node_modules 2node_modules
2flow-typed 3flow-typed
3out 4out
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32f758097..175ce06d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,7 +27,6 @@
27 27
28[See 5.0.1 changelog.](#5-0-1--2019-03-25-) 28[See 5.0.1 changelog.](#5-0-1--2019-03-25-)
29 29
30
31# [5.0.0](https://github.com/meetfranz/franz/compare/5.0.0-beta.24...5.0.0) (2019-02-15) 30# [5.0.0](https://github.com/meetfranz/franz/compare/5.0.0-beta.24...5.0.0) (2019-02-15)
32 31
33### General 32### General
diff --git a/package-lock.json b/package-lock.json
index e9b2cdcf2..6b87d2432 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1442,7 +1442,7 @@
1442 "@lerna/get-packed": { 1442 "@lerna/get-packed": {
1443 "version": "3.7.0", 1443 "version": "3.7.0",
1444 "resolved": "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-3.7.0.tgz", 1444 "resolved": "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-3.7.0.tgz",
1445 "integrity": "sha512-yuFtjsUZIHjeIvIYQ/QuytC+FQcHwo3peB+yGBST2uWCLUCR5rx6knoQcPzbxdFDCuUb5IFccFGd3B1fHFg3RQ==", 1445 "integrity": "sha1-VJx3OPe+XjsUM+gu2c2pEjvNHtU=",
1446 "dev": true, 1446 "dev": true,
1447 "requires": { 1447 "requires": {
1448 "fs-extra": "^7.0.0", 1448 "fs-extra": "^7.0.0",
@@ -1567,7 +1567,7 @@
1567 "@lerna/npm-conf": { 1567 "@lerna/npm-conf": {
1568 "version": "3.7.0", 1568 "version": "3.7.0",
1569 "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.7.0.tgz", 1569 "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.7.0.tgz",
1570 "integrity": "sha512-+WSMDfPKcKzMfqq283ydz9RRpOU6p9wfx0wy4hVSUY/6YUpsyuk8SShjcRtY8zTM5AOrxvFBuuV90H4YpZ5+Ng==", 1570 "integrity": "sha1-8QHU/fB8788RYbz688DxBbQgpFA=",
1571 "dev": true, 1571 "dev": true,
1572 "requires": { 1572 "requires": {
1573 "config-chain": "^1.1.11", 1573 "config-chain": "^1.1.11",
@@ -1823,7 +1823,7 @@
1823 "@lerna/run-parallel-batches": { 1823 "@lerna/run-parallel-batches": {
1824 "version": "3.0.0", 1824 "version": "3.0.0",
1825 "resolved": "https://registry.npmjs.org/@lerna/run-parallel-batches/-/run-parallel-batches-3.0.0.tgz", 1825 "resolved": "https://registry.npmjs.org/@lerna/run-parallel-batches/-/run-parallel-batches-3.0.0.tgz",
1826 "integrity": "sha512-Mj1ravlXF7AkkewKd9YFq9BtVrsStNrvVLedD/b2wIVbNqcxp8lS68vehXVOzoL/VWNEDotvqCQtyDBilCodGw==", 1826 "integrity": "sha1-RocEk0CEx0mR0xJNgGB4V9TfqEA=",
1827 "dev": true, 1827 "dev": true,
1828 "requires": { 1828 "requires": {
1829 "p-map": "^1.2.0", 1829 "p-map": "^1.2.0",
@@ -1948,15 +1948,6 @@
1948 "@meetfranz/theme": "^1.0.7", 1948 "@meetfranz/theme": "^1.0.7",
1949 "react-html-attributes": "^1.4.3", 1949 "react-html-attributes": "^1.4.3",
1950 "react-loader": "^2.4.5" 1950 "react-loader": "^2.4.5"
1951 },
1952 "dependencies": {
1953 "@meetfranz/theme": {
1954 "version": "1.0.9",
1955 "bundled": true,
1956 "requires": {
1957 "color": "^3.1.0"
1958 }
1959 }
1960 } 1951 }
1961 }, 1952 },
1962 "@meetfranz/theme": { 1953 "@meetfranz/theme": {
@@ -1972,21 +1963,12 @@
1972 "@mdi/react": "^1.1.0", 1963 "@mdi/react": "^1.1.0",
1973 "@meetfranz/theme": "^1.0.7", 1964 "@meetfranz/theme": "^1.0.7",
1974 "react-loader": "^2.4.5" 1965 "react-loader": "^2.4.5"
1975 },
1976 "dependencies": {
1977 "@meetfranz/theme": {
1978 "version": "1.0.9",
1979 "bundled": true,
1980 "requires": {
1981 "color": "^3.1.0"
1982 }
1983 }
1984 } 1966 }
1985 }, 1967 },
1986 "@mrmlnc/readdir-enhanced": { 1968 "@mrmlnc/readdir-enhanced": {
1987 "version": "2.2.1", 1969 "version": "2.2.1",
1988 "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", 1970 "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
1989 "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", 1971 "integrity": "sha1-UkryQNGjYFJ7cwR17PoTRKpUDd4=",
1990 "dev": true, 1972 "dev": true,
1991 "requires": { 1973 "requires": {
1992 "call-me-maybe": "^1.0.1", 1974 "call-me-maybe": "^1.0.1",
@@ -1996,7 +1978,7 @@
1996 "@nodelib/fs.stat": { 1978 "@nodelib/fs.stat": {
1997 "version": "1.1.3", 1979 "version": "1.1.3",
1998 "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", 1980 "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
1999 "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", 1981 "integrity": "sha1-K1o6s/kYzKSKjHVMCBaOPwPrphs=",
2000 "dev": true 1982 "dev": true
2001 }, 1983 },
2002 "@octokit/endpoint": { 1984 "@octokit/endpoint": {
@@ -2373,7 +2355,7 @@
2373 "dependencies": { 2355 "dependencies": {
2374 "mime-types": { 2356 "mime-types": {
2375 "version": "1.0.2", 2357 "version": "1.0.2",
2376 "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", 2358 "resolved": "http://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz",
2377 "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", 2359 "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=",
2378 "dev": true 2360 "dev": true
2379 } 2361 }
@@ -2431,7 +2413,7 @@
2431 "agent-base": { 2413 "agent-base": {
2432 "version": "4.2.1", 2414 "version": "4.2.1",
2433 "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", 2415 "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
2434 "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", 2416 "integrity": "sha1-2J5ZmfeXh1Z0wH2H8mD8Qeg+jKk=",
2435 "dev": true, 2417 "dev": true,
2436 "requires": { 2418 "requires": {
2437 "es6-promisify": "^5.0.0" 2419 "es6-promisify": "^5.0.0"
@@ -2440,7 +2422,7 @@
2440 "agentkeepalive": { 2422 "agentkeepalive": {
2441 "version": "3.5.2", 2423 "version": "3.5.2",
2442 "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", 2424 "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz",
2443 "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", 2425 "integrity": "sha1-oROSTdP6JKC8O3gQjEUMKr7gD2c=",
2444 "dev": true, 2426 "dev": true,
2445 "requires": { 2427 "requires": {
2446 "humanize-ms": "^1.2.1" 2428 "humanize-ms": "^1.2.1"
@@ -2487,7 +2469,7 @@
2487 }, 2469 },
2488 "ansi-colors": { 2470 "ansi-colors": {
2489 "version": "1.1.0", 2471 "version": "1.1.0",
2490 "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", 2472 "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
2491 "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", 2473 "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==",
2492 "dev": true, 2474 "dev": true,
2493 "requires": { 2475 "requires": {
@@ -2918,7 +2900,7 @@
2918 }, 2900 },
2919 "util": { 2901 "util": {
2920 "version": "0.10.3", 2902 "version": "0.10.3",
2921 "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", 2903 "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
2922 "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", 2904 "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
2923 "dev": true, 2905 "dev": true,
2924 "requires": { 2906 "requires": {
@@ -2953,7 +2935,7 @@
2953 }, 2935 },
2954 "async": { 2936 "async": {
2955 "version": "0.1.22", 2937 "version": "0.1.22",
2956 "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", 2938 "resolved": "http://registry.npmjs.org/async/-/async-0.1.22.tgz",
2957 "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=" 2939 "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE="
2958 }, 2940 },
2959 "async-done": { 2941 "async-done": {
@@ -3776,7 +3758,7 @@
3776 "byte-size": { 3758 "byte-size": {
3777 "version": "4.0.4", 3759 "version": "4.0.4",
3778 "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-4.0.4.tgz", 3760 "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-4.0.4.tgz",
3779 "integrity": "sha512-82RPeneC6nqCdSwCX2hZUz3JPOvN5at/nTEw/CMf05Smu3Hrpo9Psb7LjN+k+XndNArG1EY8L4+BM3aTM4BCvw==", 3761 "integrity": "sha1-KdOBcJ9BquDYnGMfHIGuyIzUCyM=",
3780 "dev": true 3762 "dev": true
3781 }, 3763 },
3782 "bytes": { 3764 "bytes": {
@@ -3788,7 +3770,7 @@
3788 "cacache": { 3770 "cacache": {
3789 "version": "11.3.2", 3771 "version": "11.3.2",
3790 "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", 3772 "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz",
3791 "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", 3773 "integrity": "sha1-LYHjCOPSWMo4Eltna5iyrJzmm/o=",
3792 "dev": true, 3774 "dev": true,
3793 "requires": { 3775 "requires": {
3794 "bluebird": "^3.5.3", 3776 "bluebird": "^3.5.3",
@@ -3810,7 +3792,7 @@
3810 "lru-cache": { 3792 "lru-cache": {
3811 "version": "5.1.1", 3793 "version": "5.1.1",
3812 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 3794 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
3813 "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 3795 "integrity": "sha1-HaJ+ZxAnGUdpXa9oSOhH8B2EuSA=",
3814 "dev": true, 3796 "dev": true,
3815 "requires": { 3797 "requires": {
3816 "yallist": "^3.0.2" 3798 "yallist": "^3.0.2"
@@ -4507,7 +4489,7 @@
4507 "config-chain": { 4489 "config-chain": {
4508 "version": "1.1.12", 4490 "version": "1.1.12",
4509 "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", 4491 "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
4510 "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", 4492 "integrity": "sha1-D96NCRIA616AjK8l/mGMAvSOTvo=",
4511 "dev": true, 4493 "dev": true,
4512 "requires": { 4494 "requires": {
4513 "ini": "^1.3.4", 4495 "ini": "^1.3.4",
@@ -4553,7 +4535,7 @@
4553 "dependencies": { 4535 "dependencies": {
4554 "debug": { 4536 "debug": {
4555 "version": "1.0.4", 4537 "version": "1.0.4",
4556 "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", 4538 "resolved": "http://registry.npmjs.org/debug/-/debug-1.0.4.tgz",
4557 "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", 4539 "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=",
4558 "dev": true, 4540 "dev": true,
4559 "requires": { 4541 "requires": {
@@ -4823,7 +4805,7 @@
4823 "conventional-recommended-bump": { 4805 "conventional-recommended-bump": {
4824 "version": "4.0.4", 4806 "version": "4.0.4",
4825 "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-4.0.4.tgz", 4807 "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-4.0.4.tgz",
4826 "integrity": "sha512-9mY5Yoblq+ZMqJpBzgS+RpSq+SUfP2miOR3H/NR9drGf08WCrY9B6HAGJZEm6+ThsVP917VHAahSOjM6k1vhPg==", 4808 "integrity": "sha1-BVQFhGQdPadYyIY8CXiPyutYaHI=",
4827 "dev": true, 4809 "dev": true,
4828 "requires": { 4810 "requires": {
4829 "concat-stream": "^1.6.0", 4811 "concat-stream": "^1.6.0",
@@ -4914,7 +4896,7 @@
4914 "copy-concurrently": { 4896 "copy-concurrently": {
4915 "version": "1.0.5", 4897 "version": "1.0.5",
4916 "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", 4898 "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
4917 "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", 4899 "integrity": "sha1-kilzmMrjSTf8r9bsgTnBgFHwteA=",
4918 "dev": true, 4900 "dev": true,
4919 "requires": { 4901 "requires": {
4920 "aproba": "^1.1.1", 4902 "aproba": "^1.1.1",
@@ -5495,7 +5477,7 @@
5495 "diff": { 5477 "diff": {
5496 "version": "3.5.0", 5478 "version": "3.5.0",
5497 "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 5479 "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
5498 "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 5480 "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=",
5499 "dev": true 5481 "dev": true
5500 }, 5482 },
5501 "diffie-hellman": { 5483 "diffie-hellman": {
@@ -5512,7 +5494,7 @@
5512 "dir-glob": { 5494 "dir-glob": {
5513 "version": "2.0.0", 5495 "version": "2.0.0",
5514 "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", 5496 "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz",
5515 "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", 5497 "integrity": "sha1-CyBdK2rvmCOMooZZioIE0p0KADQ=",
5516 "dev": true, 5498 "dev": true,
5517 "requires": { 5499 "requires": {
5518 "arrify": "^1.0.1", 5500 "arrify": "^1.0.1",
@@ -5597,7 +5579,7 @@
5597 "dependencies": { 5579 "dependencies": {
5598 "domelementtype": { 5580 "domelementtype": {
5599 "version": "1.1.3", 5581 "version": "1.1.3",
5600 "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", 5582 "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
5601 "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" 5583 "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
5602 } 5584 }
5603 } 5585 }
@@ -5641,7 +5623,7 @@
5641 }, 5623 },
5642 "dotenv": { 5624 "dotenv": {
5643 "version": "4.0.0", 5625 "version": "4.0.0",
5644 "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", 5626 "resolved": "http://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz",
5645 "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=", 5627 "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=",
5646 "dev": true 5628 "dev": true
5647 }, 5629 },
@@ -5661,7 +5643,7 @@
5661 }, 5643 },
5662 "duplexer": { 5644 "duplexer": {
5663 "version": "0.1.1", 5645 "version": "0.1.1",
5664 "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", 5646 "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
5665 "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", 5647 "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
5666 "dev": true 5648 "dev": true
5667 }, 5649 },
@@ -5995,7 +5977,7 @@
5995 }, 5977 },
5996 "readable-stream": { 5978 "readable-stream": {
5997 "version": "1.1.14", 5979 "version": "1.1.14",
5998 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 5980 "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
5999 "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 5981 "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
6000 "dev": true, 5982 "dev": true,
6001 "requires": { 5983 "requires": {
@@ -6525,7 +6507,7 @@
6525 }, 6507 },
6526 "debug": { 6508 "debug": {
6527 "version": "2.3.3", 6509 "version": "2.3.3",
6528 "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", 6510 "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
6529 "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", 6511 "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
6530 "dev": true, 6512 "dev": true,
6531 "requires": { 6513 "requires": {
@@ -6534,7 +6516,7 @@
6534 }, 6516 },
6535 "ms": { 6517 "ms": {
6536 "version": "0.7.2", 6518 "version": "0.7.2",
6537 "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 6519 "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
6538 "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", 6520 "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
6539 "dev": true 6521 "dev": true
6540 }, 6522 },
@@ -6568,7 +6550,7 @@
6568 "dependencies": { 6550 "dependencies": {
6569 "debug": { 6551 "debug": {
6570 "version": "2.3.3", 6552 "version": "2.3.3",
6571 "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", 6553 "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
6572 "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", 6554 "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
6573 "dev": true, 6555 "dev": true,
6574 "requires": { 6556 "requires": {
@@ -6577,7 +6559,7 @@
6577 }, 6559 },
6578 "ms": { 6560 "ms": {
6579 "version": "0.7.2", 6561 "version": "0.7.2",
6580 "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 6562 "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
6581 "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", 6563 "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
6582 "dev": true 6564 "dev": true
6583 } 6565 }
@@ -6701,12 +6683,12 @@
6701 "es6-promise": { 6683 "es6-promise": {
6702 "version": "4.2.5", 6684 "version": "4.2.5",
6703 "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", 6685 "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz",
6704 "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", 6686 "integrity": "sha1-2m0NVpLvtGHggsFIF/4kJ9j10FQ=",
6705 "dev": true 6687 "dev": true
6706 }, 6688 },
6707 "es6-promisify": { 6689 "es6-promisify": {
6708 "version": "5.0.0", 6690 "version": "5.0.0",
6709 "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 6691 "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
6710 "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 6692 "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
6711 "dev": true, 6693 "dev": true,
6712 "requires": { 6694 "requires": {
@@ -6913,7 +6895,7 @@
6913 }, 6895 },
6914 "load-json-file": { 6896 "load-json-file": {
6915 "version": "2.0.0", 6897 "version": "2.0.0",
6916 "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", 6898 "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
6917 "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", 6899 "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
6918 "dev": true, 6900 "dev": true,
6919 "requires": { 6901 "requires": {
@@ -7396,7 +7378,7 @@
7396 }, 7378 },
7397 "finalhandler": { 7379 "finalhandler": {
7398 "version": "1.1.1", 7380 "version": "1.1.1",
7399 "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 7381 "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
7400 "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 7382 "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
7401 "dev": true, 7383 "dev": true,
7402 "requires": { 7384 "requires": {
@@ -7724,7 +7706,7 @@
7724 "figgy-pudding": { 7706 "figgy-pudding": {
7725 "version": "3.5.1", 7707 "version": "3.5.1",
7726 "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", 7708 "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
7727 "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", 7709 "integrity": "sha1-hiRwESkBxyeg5JWoB0S9W6odZ5A=",
7728 "dev": true 7710 "dev": true
7729 }, 7711 },
7730 "figures": { 7712 "figures": {
@@ -7782,7 +7764,7 @@
7782 }, 7764 },
7783 "finalhandler": { 7765 "finalhandler": {
7784 "version": "0.1.0", 7766 "version": "0.1.0",
7785 "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.1.0.tgz", 7767 "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-0.1.0.tgz",
7786 "integrity": "sha1-2gW7xPX0owyEzh2R88FUAHxOnao=", 7768 "integrity": "sha1-2gW7xPX0owyEzh2R88FUAHxOnao=",
7787 "dev": true, 7769 "dev": true,
7788 "requires": { 7770 "requires": {
@@ -7792,7 +7774,7 @@
7792 "dependencies": { 7774 "dependencies": {
7793 "debug": { 7775 "debug": {
7794 "version": "1.0.4", 7776 "version": "1.0.4",
7795 "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", 7777 "resolved": "http://registry.npmjs.org/debug/-/debug-1.0.4.tgz",
7796 "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", 7778 "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=",
7797 "dev": true, 7779 "dev": true,
7798 "requires": { 7780 "requires": {
@@ -7801,7 +7783,7 @@
7801 }, 7783 },
7802 "ms": { 7784 "ms": {
7803 "version": "0.6.2", 7785 "version": "0.6.2",
7804 "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", 7786 "resolved": "http://registry.npmjs.org/ms/-/ms-0.6.2.tgz",
7805 "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=", 7787 "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=",
7806 "dev": true 7788 "dev": true
7807 } 7789 }
@@ -8146,30 +8128,36 @@
8146 "dependencies": { 8128 "dependencies": {
8147 "abbrev": { 8129 "abbrev": {
8148 "version": "1.1.1", 8130 "version": "1.1.1",
8149 "bundled": true 8131 "resolved": false,
8132 "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
8150 }, 8133 },
8151 "ansi-regex": { 8134 "ansi-regex": {
8152 "version": "2.1.1", 8135 "version": "2.1.1",
8153 "bundled": true 8136 "resolved": false,
8137 "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
8154 }, 8138 },
8155 "aproba": { 8139 "aproba": {
8156 "version": "1.2.0", 8140 "version": "1.2.0",
8157 "bundled": true 8141 "resolved": false,
8142 "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
8158 }, 8143 },
8159 "are-we-there-yet": { 8144 "are-we-there-yet": {
8160 "version": "1.1.5", 8145 "version": "1.1.5",
8161 "bundled": true, 8146 "resolved": false,
8147 "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
8162 "requires": { 8148 "requires": {
8163 "delegates": "^1.0.0" 8149 "delegates": "^1.0.0"
8164 } 8150 }
8165 }, 8151 },
8166 "balanced-match": { 8152 "balanced-match": {
8167 "version": "1.0.0", 8153 "version": "1.0.0",
8168 "bundled": true 8154 "resolved": false,
8155 "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
8169 }, 8156 },
8170 "brace-expansion": { 8157 "brace-expansion": {
8171 "version": "1.1.11", 8158 "version": "1.1.11",
8172 "bundled": true, 8159 "resolved": false,
8160 "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
8173 "requires": { 8161 "requires": {
8174 "balanced-match": "^1.0.0", 8162 "balanced-match": "^1.0.0",
8175 "concat-map": "0.0.1" 8163 "concat-map": "0.0.1"
@@ -8177,54 +8165,66 @@
8177 }, 8165 },
8178 "chownr": { 8166 "chownr": {
8179 "version": "1.1.1", 8167 "version": "1.1.1",
8180 "bundled": true 8168 "resolved": false,
8169 "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
8181 }, 8170 },
8182 "code-point-at": { 8171 "code-point-at": {
8183 "version": "1.1.0", 8172 "version": "1.1.0",
8184 "bundled": true 8173 "resolved": false,
8174 "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
8185 }, 8175 },
8186 "concat-map": { 8176 "concat-map": {
8187 "version": "0.0.1", 8177 "version": "0.0.1",
8188 "bundled": true 8178 "resolved": false,
8179 "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
8189 }, 8180 },
8190 "console-control-strings": { 8181 "console-control-strings": {
8191 "version": "1.1.0", 8182 "version": "1.1.0",
8192 "bundled": true 8183 "resolved": false,
8184 "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
8193 }, 8185 },
8194 "core-util-is": { 8186 "core-util-is": {
8195 "version": "1.0.2", 8187 "version": "1.0.2",
8196 "bundled": true 8188 "resolved": false,
8189 "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
8197 }, 8190 },
8198 "debug": { 8191 "debug": {
8199 "version": "2.6.9", 8192 "version": "2.6.9",
8200 "bundled": true 8193 "resolved": false,
8194 "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="
8201 }, 8195 },
8202 "deep-extend": { 8196 "deep-extend": {
8203 "version": "0.6.0", 8197 "version": "0.6.0",
8204 "bundled": true 8198 "resolved": false,
8199 "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
8205 }, 8200 },
8206 "delegates": { 8201 "delegates": {
8207 "version": "1.0.0", 8202 "version": "1.0.0",
8208 "bundled": true 8203 "resolved": false,
8204 "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
8209 }, 8205 },
8210 "detect-libc": { 8206 "detect-libc": {
8211 "version": "1.0.3", 8207 "version": "1.0.3",
8212 "bundled": true 8208 "resolved": false,
8209 "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
8213 }, 8210 },
8214 "fs-minipass": { 8211 "fs-minipass": {
8215 "version": "1.2.5", 8212 "version": "1.2.5",
8216 "bundled": true, 8213 "resolved": false,
8214 "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
8217 "requires": { 8215 "requires": {
8218 "minipass": "^2.2.1" 8216 "minipass": "^2.2.1"
8219 } 8217 }
8220 }, 8218 },
8221 "fs.realpath": { 8219 "fs.realpath": {
8222 "version": "1.0.0", 8220 "version": "1.0.0",
8223 "bundled": true 8221 "resolved": false,
8222 "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
8224 }, 8223 },
8225 "gauge": { 8224 "gauge": {
8226 "version": "2.7.4", 8225 "version": "2.7.4",
8227 "bundled": true, 8226 "resolved": false,
8227 "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
8228 "requires": { 8228 "requires": {
8229 "aproba": "^1.0.3", 8229 "aproba": "^1.0.3",
8230 "console-control-strings": "^1.0.0", 8230 "console-control-strings": "^1.0.0",
@@ -8236,7 +8236,8 @@
8236 }, 8236 },
8237 "glob": { 8237 "glob": {
8238 "version": "7.1.3", 8238 "version": "7.1.3",
8239 "bundled": true, 8239 "resolved": false,
8240 "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
8240 "requires": { 8241 "requires": {
8241 "fs.realpath": "^1.0.0", 8242 "fs.realpath": "^1.0.0",
8242 "inflight": "^1.0.4", 8243 "inflight": "^1.0.4",
@@ -8248,25 +8249,29 @@
8248 }, 8249 },
8249 "has-unicode": { 8250 "has-unicode": {
8250 "version": "2.0.1", 8251 "version": "2.0.1",
8251 "bundled": true 8252 "resolved": false,
8253 "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
8252 }, 8254 },
8253 "iconv-lite": { 8255 "iconv-lite": {
8254 "version": "0.4.24", 8256 "version": "0.4.24",
8255 "bundled": true, 8257 "resolved": false,
8258 "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
8256 "requires": { 8259 "requires": {
8257 "safer-buffer": ">= 2.1.2 < 3" 8260 "safer-buffer": ">= 2.1.2 < 3"
8258 } 8261 }
8259 }, 8262 },
8260 "ignore-walk": { 8263 "ignore-walk": {
8261 "version": "3.0.1", 8264 "version": "3.0.1",
8262 "bundled": true, 8265 "resolved": false,
8266 "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
8263 "requires": { 8267 "requires": {
8264 "minimatch": "^3.0.4" 8268 "minimatch": "^3.0.4"
8265 } 8269 }
8266 }, 8270 },
8267 "inflight": { 8271 "inflight": {
8268 "version": "1.0.6", 8272 "version": "1.0.6",
8269 "bundled": true, 8273 "resolved": false,
8274 "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
8270 "requires": { 8275 "requires": {
8271 "once": "^1.3.0", 8276 "once": "^1.3.0",
8272 "wrappy": "1" 8277 "wrappy": "1"
@@ -8274,15 +8279,18 @@
8274 }, 8279 },
8275 "inherits": { 8280 "inherits": {
8276 "version": "2.0.3", 8281 "version": "2.0.3",
8277 "bundled": true 8282 "resolved": false,
8283 "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
8278 }, 8284 },
8279 "ini": { 8285 "ini": {
8280 "version": "1.3.5", 8286 "version": "1.3.5",
8281 "bundled": true 8287 "resolved": false,
8288 "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
8282 }, 8289 },
8283 "is-fullwidth-code-point": { 8290 "is-fullwidth-code-point": {
8284 "version": "1.0.0", 8291 "version": "1.0.0",
8285 "bundled": true, 8292 "resolved": false,
8293 "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
8286 "requires": { 8294 "requires": {
8287 "number-is-nan": "^1.0.0" 8295 "number-is-nan": "^1.0.0"
8288 } 8296 }
@@ -8378,7 +8386,7 @@
8378 "genfun": { 8386 "genfun": {
8379 "version": "5.0.0", 8387 "version": "5.0.0",
8380 "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", 8388 "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz",
8381 "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", 8389 "integrity": "sha1-ndlxCgaQClxKW/V6yl2k5S/nZTc=",
8382 "dev": true 8390 "dev": true
8383 }, 8391 },
8384 "get-caller-file": { 8392 "get-caller-file": {
@@ -8917,7 +8925,7 @@
8917 }, 8925 },
8918 "got": { 8926 "got": {
8919 "version": "6.7.1", 8927 "version": "6.7.1",
8920 "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", 8928 "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz",
8921 "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", 8929 "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
8922 "dev": true, 8930 "dev": true,
8923 "requires": { 8931 "requires": {
@@ -9499,7 +9507,7 @@
9499 }, 9507 },
9500 "yargs": { 9508 "yargs": {
9501 "version": "3.32.0", 9509 "version": "3.32.0",
9502 "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", 9510 "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz",
9503 "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", 9511 "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=",
9504 "dev": true, 9512 "dev": true,
9505 "requires": { 9513 "requires": {
@@ -9674,7 +9682,7 @@
9674 }, 9682 },
9675 "lodash": { 9683 "lodash": {
9676 "version": "1.0.2", 9684 "version": "1.0.2",
9677 "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", 9685 "resolved": "http://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz",
9678 "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", 9686 "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=",
9679 "dev": true 9687 "dev": true
9680 }, 9688 },
@@ -9734,7 +9742,7 @@
9734 }, 9742 },
9735 "readable-stream": { 9743 "readable-stream": {
9736 "version": "1.0.34", 9744 "version": "1.0.34",
9737 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", 9745 "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
9738 "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", 9746 "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
9739 "dev": true, 9747 "dev": true,
9740 "requires": { 9748 "requires": {
@@ -10315,7 +10323,7 @@
10315 "http-cache-semantics": { 10323 "http-cache-semantics": {
10316 "version": "3.8.1", 10324 "version": "3.8.1",
10317 "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", 10325 "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz",
10318 "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", 10326 "integrity": "sha1-ObDhat2bYFvwqe89nar0hDtMrNI=",
10319 "dev": true 10327 "dev": true
10320 }, 10328 },
10321 "http-deceiver": { 10329 "http-deceiver": {
@@ -10326,7 +10334,7 @@
10326 }, 10334 },
10327 "http-errors": { 10335 "http-errors": {
10328 "version": "1.6.3", 10336 "version": "1.6.3",
10329 "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 10337 "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
10330 "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 10338 "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
10331 "dev": true, 10339 "dev": true,
10332 "requires": { 10340 "requires": {
@@ -10364,7 +10372,7 @@
10364 "http-proxy-agent": { 10372 "http-proxy-agent": {
10365 "version": "2.1.0", 10373 "version": "2.1.0",
10366 "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", 10374 "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
10367 "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", 10375 "integrity": "sha1-5IIb7vWyFCogJr1zkm/lN2McVAU=",
10368 "dev": true, 10376 "dev": true,
10369 "requires": { 10377 "requires": {
10370 "agent-base": "4", 10378 "agent-base": "4",
@@ -10374,7 +10382,7 @@
10374 "debug": { 10382 "debug": {
10375 "version": "3.1.0", 10383 "version": "3.1.0",
10376 "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 10384 "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
10377 "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 10385 "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
10378 "dev": true, 10386 "dev": true,
10379 "requires": { 10387 "requires": {
10380 "ms": "2.0.0" 10388 "ms": "2.0.0"
@@ -10422,7 +10430,7 @@
10422 "https-proxy-agent": { 10430 "https-proxy-agent": {
10423 "version": "2.2.1", 10431 "version": "2.2.1",
10424 "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", 10432 "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
10425 "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", 10433 "integrity": "sha1-UVUpcPoE1yPgTFbQQXjD+SWSu8A=",
10426 "dev": true, 10434 "dev": true,
10427 "requires": { 10435 "requires": {
10428 "agent-base": "^4.1.0", 10436 "agent-base": "^4.1.0",
@@ -10432,7 +10440,7 @@
10432 "debug": { 10440 "debug": {
10433 "version": "3.2.6", 10441 "version": "3.2.6",
10434 "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 10442 "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
10435 "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 10443 "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=",
10436 "dev": true, 10444 "dev": true,
10437 "requires": { 10445 "requires": {
10438 "ms": "^2.1.1" 10446 "ms": "^2.1.1"
@@ -10441,7 +10449,7 @@
10441 "ms": { 10449 "ms": {
10442 "version": "2.1.1", 10450 "version": "2.1.1",
10443 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 10451 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
10444 "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 10452 "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=",
10445 "dev": true 10453 "dev": true
10446 } 10454 }
10447 } 10455 }
@@ -10457,7 +10465,7 @@
10457 }, 10465 },
10458 "hunspell-asm": { 10466 "hunspell-asm": {
10459 "version": "1.0.2", 10467 "version": "1.0.2",
10460 "resolved": "https://registry.npmjs.org/hunspell-asm/-/hunspell-asm-1.0.2.tgz", 10468 "resolved": "http://registry.npmjs.org/hunspell-asm/-/hunspell-asm-1.0.2.tgz",
10461 "integrity": "sha512-UTLBvc0yZiIcHl9qrgxnFTZbX3zF4CprzEY+u+N0iXlUKZnUJRIgvgppTdgiQTsucm5b0aN/rHsgXz2q/0kBRA==", 10469 "integrity": "sha512-UTLBvc0yZiIcHl9qrgxnFTZbX3zF4CprzEY+u+N0iXlUKZnUJRIgvgppTdgiQTsucm5b0aN/rHsgXz2q/0kBRA==",
10462 "requires": { 10470 "requires": {
10463 "emscripten-wasm-loader": "^1.0.0", 10471 "emscripten-wasm-loader": "^1.0.0",
@@ -10535,7 +10543,7 @@
10535 "ignore-walk": { 10543 "ignore-walk": {
10536 "version": "3.0.1", 10544 "version": "3.0.1",
10537 "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", 10545 "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
10538 "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", 10546 "integrity": "sha1-qD5i59JyrA47VRqqgoMaGbafgvg=",
10539 "dev": true, 10547 "dev": true,
10540 "requires": { 10548 "requires": {
10541 "minimatch": "^3.0.4" 10549 "minimatch": "^3.0.4"
@@ -10577,7 +10585,7 @@
10577 "import-local": { 10585 "import-local": {
10578 "version": "1.0.0", 10586 "version": "1.0.0",
10579 "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", 10587 "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz",
10580 "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", 10588 "integrity": "sha1-Xk/9wD9P5sAJxnKb6yljHC+CJ7w=",
10581 "dev": true, 10589 "dev": true,
10582 "requires": { 10590 "requires": {
10583 "pkg-dir": "^2.0.0", 10591 "pkg-dir": "^2.0.0",
@@ -10641,7 +10649,7 @@
10641 "init-package-json": { 10649 "init-package-json": {
10642 "version": "1.10.3", 10650 "version": "1.10.3",
10643 "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", 10651 "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz",
10644 "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", 10652 "integrity": "sha1-Rf/i9hCoyhNPK9HbVjeyNQcPbL4=",
10645 "dev": true, 10653 "dev": true,
10646 "requires": { 10654 "requires": {
10647 "glob": "^7.1.1", 10655 "glob": "^7.1.1",
@@ -10745,7 +10753,7 @@
10745 "inversify": { 10753 "inversify": {
10746 "version": "5.0.1", 10754 "version": "5.0.1",
10747 "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", 10755 "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz",
10748 "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==", 10756 "integrity": "sha1-UA1wmxQ0iWzloNWJFcSkIQ40+24=",
10749 "dev": true 10757 "dev": true
10750 }, 10758 },
10751 "invert-kv": { 10759 "invert-kv": {
@@ -10774,7 +10782,7 @@
10774 }, 10782 },
10775 "is": { 10783 "is": {
10776 "version": "0.3.0", 10784 "version": "0.3.0",
10777 "resolved": "https://registry.npmjs.org/is/-/is-0.3.0.tgz", 10785 "resolved": "http://registry.npmjs.org/is/-/is-0.3.0.tgz",
10778 "integrity": "sha1-qPcd/IpuKDcWJ/JskpCYxvTV1dc=", 10786 "integrity": "sha1-qPcd/IpuKDcWJ/JskpCYxvTV1dc=",
10779 "dev": true 10787 "dev": true
10780 }, 10788 },
@@ -10998,7 +11006,7 @@
10998 }, 11006 },
10999 "is-obj": { 11007 "is-obj": {
11000 "version": "1.0.1", 11008 "version": "1.0.1",
11001 "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", 11009 "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
11002 "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", 11010 "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
11003 "dev": true 11011 "dev": true
11004 }, 11012 },
@@ -11180,7 +11188,7 @@
11180 }, 11188 },
11181 "isemail": { 11189 "isemail": {
11182 "version": "1.2.0", 11190 "version": "1.2.0",
11183 "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", 11191 "resolved": "http://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz",
11184 "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=" 11192 "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo="
11185 }, 11193 },
11186 "isexe": { 11194 "isexe": {
@@ -11211,7 +11219,7 @@
11211 }, 11219 },
11212 "joi": { 11220 "joi": {
11213 "version": "6.10.1", 11221 "version": "6.10.1",
11214 "resolved": "https://registry.npmjs.org/joi/-/joi-6.10.1.tgz", 11222 "resolved": "http://registry.npmjs.org/joi/-/joi-6.10.1.tgz",
11215 "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=", 11223 "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=",
11216 "requires": { 11224 "requires": {
11217 "hoek": "2.x.x", 11225 "hoek": "2.x.x",
@@ -11618,7 +11626,7 @@
11618 "libnpmaccess": { 11626 "libnpmaccess": {
11619 "version": "3.0.1", 11627 "version": "3.0.1",
11620 "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.1.tgz", 11628 "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.1.tgz",
11621 "integrity": "sha512-RlZ7PNarCBt+XbnP7R6PoVgOq9t+kou5rvhaInoNibhPO7eMlRfS0B8yjatgn2yaHIwWNyoJDolC/6Lc5L/IQA==", 11629 "integrity": "sha1-Wzqd5iHyk9QlGRqi53kQL4QWf6g=",
11622 "dev": true, 11630 "dev": true,
11623 "requires": { 11631 "requires": {
11624 "aproba": "^2.0.0", 11632 "aproba": "^2.0.0",
@@ -11630,7 +11638,7 @@
11630 "aproba": { 11638 "aproba": {
11631 "version": "2.0.0", 11639 "version": "2.0.0",
11632 "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", 11640 "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
11633 "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", 11641 "integrity": "sha1-UlILiuW1aSFbNU78DKo/4eRaitw=",
11634 "dev": true 11642 "dev": true
11635 } 11643 }
11636 } 11644 }
@@ -12076,7 +12084,7 @@
12076 "make-fetch-happen": { 12084 "make-fetch-happen": {
12077 "version": "4.0.1", 12085 "version": "4.0.1",
12078 "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz", 12086 "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz",
12079 "integrity": "sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==", 12087 "integrity": "sha1-FBSXy4ePJDupMTbIPYq6EsIWwIM=",
12080 "dev": true, 12088 "dev": true,
12081 "requires": { 12089 "requires": {
12082 "agentkeepalive": "^3.4.1", 12090 "agentkeepalive": "^3.4.1",
@@ -12167,7 +12175,7 @@
12167 }, 12175 },
12168 "media-typer": { 12176 "media-typer": {
12169 "version": "0.3.0", 12177 "version": "0.3.0",
12170 "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 12178 "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
12171 "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 12179 "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
12172 "dev": true 12180 "dev": true
12173 }, 12181 },
@@ -12258,7 +12266,7 @@
12258 }, 12266 },
12259 "readable-stream": { 12267 "readable-stream": {
12260 "version": "1.0.34", 12268 "version": "1.0.34",
12261 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", 12269 "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
12262 "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", 12270 "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
12263 "dev": true, 12271 "dev": true,
12264 "requires": { 12272 "requires": {
@@ -12289,7 +12297,7 @@
12289 "merge2": { 12297 "merge2": {
12290 "version": "1.2.3", 12298 "version": "1.2.3",
12291 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", 12299 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz",
12292 "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", 12300 "integrity": "sha1-fumdvWm7ZIFoklPwGEiKG5ArDtU=",
12293 "dev": true 12301 "dev": true
12294 }, 12302 },
12295 "methods": { 12303 "methods": {
@@ -12383,7 +12391,7 @@
12383 }, 12391 },
12384 "minimist": { 12392 "minimist": {
12385 "version": "1.2.0", 12393 "version": "1.2.0",
12386 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 12394 "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
12387 "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 12395 "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
12388 "dev": true 12396 "dev": true
12389 }, 12397 },
@@ -12417,7 +12425,7 @@
12417 "mississippi": { 12425 "mississippi": {
12418 "version": "3.0.0", 12426 "version": "3.0.0",
12419 "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", 12427 "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
12420 "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", 12428 "integrity": "sha1-6goykfl+C16HdrNj1fChLZTGcCI=",
12421 "dev": true, 12429 "dev": true,
12422 "requires": { 12430 "requires": {
12423 "concat-stream": "^1.5.0", 12431 "concat-stream": "^1.5.0",
@@ -12455,7 +12463,7 @@
12455 }, 12463 },
12456 "mkdirp": { 12464 "mkdirp": {
12457 "version": "0.5.1", 12465 "version": "0.5.1",
12458 "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 12466 "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
12459 "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 12467 "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
12460 "requires": { 12468 "requires": {
12461 "minimist": "0.0.8" 12469 "minimist": "0.0.8"
@@ -12463,7 +12471,7 @@
12463 "dependencies": { 12471 "dependencies": {
12464 "minimist": { 12472 "minimist": {
12465 "version": "0.0.8", 12473 "version": "0.0.8",
12466 "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 12474 "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
12467 "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 12475 "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
12468 } 12476 }
12469 } 12477 }
@@ -12635,7 +12643,7 @@
12635 }, 12643 },
12636 "multimatch": { 12644 "multimatch": {
12637 "version": "2.1.0", 12645 "version": "2.1.0",
12638 "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", 12646 "resolved": "http://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz",
12639 "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", 12647 "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=",
12640 "dev": true, 12648 "dev": true,
12641 "requires": { 12649 "requires": {
@@ -12773,7 +12781,7 @@
12773 "node-fetch-npm": { 12781 "node-fetch-npm": {
12774 "version": "2.0.2", 12782 "version": "2.0.2",
12775 "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", 12783 "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz",
12776 "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", 12784 "integrity": "sha1-cljJBGGC3KNFtCCO2pGNrzNpf/c=",
12777 "dev": true, 12785 "dev": true,
12778 "requires": { 12786 "requires": {
12779 "encoding": "^0.1.11", 12787 "encoding": "^0.1.11",
@@ -12809,7 +12817,7 @@
12809 "dependencies": { 12817 "dependencies": {
12810 "semver": { 12818 "semver": {
12811 "version": "5.3.0", 12819 "version": "5.3.0",
12812 "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", 12820 "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
12813 "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", 12821 "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
12814 "dev": true 12822 "dev": true
12815 }, 12823 },
@@ -13987,7 +13995,7 @@
13987 "p-map": { 13995 "p-map": {
13988 "version": "1.2.0", 13996 "version": "1.2.0",
13989 "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", 13997 "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
13990 "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", 13998 "integrity": "sha1-5OlPMR6rvIYzoeeZCBZfyiYkG2s=",
13991 "dev": true 13999 "dev": true
13992 }, 14000 },
13993 "p-map-series": { 14001 "p-map-series": {
@@ -14160,7 +14168,7 @@
14160 "dependencies": { 14168 "dependencies": {
14161 "color-convert": { 14169 "color-convert": {
14162 "version": "0.5.3", 14170 "version": "0.5.3",
14163 "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", 14171 "resolved": "http://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
14164 "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=", 14172 "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=",
14165 "dev": true 14173 "dev": true
14166 } 14174 }
@@ -14305,7 +14313,7 @@
14305 }, 14313 },
14306 "path-browserify": { 14314 "path-browserify": {
14307 "version": "0.0.0", 14315 "version": "0.0.0",
14308 "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", 14316 "resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
14309 "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", 14317 "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=",
14310 "dev": true 14318 "dev": true
14311 }, 14319 },
@@ -14584,7 +14592,7 @@
14584 }, 14592 },
14585 "pretty-hrtime": { 14593 "pretty-hrtime": {
14586 "version": "1.0.3", 14594 "version": "1.0.3",
14587 "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", 14595 "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
14588 "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", 14596 "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=",
14589 "dev": true 14597 "dev": true
14590 }, 14598 },
@@ -14730,7 +14738,7 @@
14730 "protoduck": { 14738 "protoduck": {
14731 "version": "5.0.1", 14739 "version": "5.0.1",
14732 "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", 14740 "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz",
14733 "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", 14741 "integrity": "sha1-A8NlnKGAB7aaUP2Cp+vMUWJhFR8=",
14734 "dev": true, 14742 "dev": true,
14735 "requires": { 14743 "requires": {
14736 "genfun": "^5.0.0" 14744 "genfun": "^5.0.0"
@@ -15090,7 +15098,7 @@
15090 }, 15098 },
15091 "react-router": { 15099 "react-router": {
15092 "version": "3.2.1", 15100 "version": "3.2.1",
15093 "resolved": "https://registry.npmjs.org/react-router/-/react-router-3.2.1.tgz", 15101 "resolved": "http://registry.npmjs.org/react-router/-/react-router-3.2.1.tgz",
15094 "integrity": "sha512-SXkhC0nr3G0ltzVU07IN8jYl0bB6FsrDIqlLC9dK3SITXqyTJyM7yhXlUqs89w3Nqi5OkXsfRUeHX+P874HQrg==", 15102 "integrity": "sha512-SXkhC0nr3G0ltzVU07IN8jYl0bB6FsrDIqlLC9dK3SITXqyTJyM7yhXlUqs89w3Nqi5OkXsfRUeHX+P874HQrg==",
15095 "requires": { 15103 "requires": {
15096 "create-react-class": "^15.5.1", 15104 "create-react-class": "^15.5.1",
@@ -15205,7 +15213,7 @@
15205 "read-package-json": { 15213 "read-package-json": {
15206 "version": "2.0.13", 15214 "version": "2.0.13",
15207 "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.13.tgz", 15215 "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.13.tgz",
15208 "integrity": "sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg==", 15216 "integrity": "sha1-LoLr2fYTuqbS6+Oqcs7+P2jkH0o=",
15209 "dev": true, 15217 "dev": true,
15210 "requires": { 15218 "requires": {
15211 "glob": "^7.1.1", 15219 "glob": "^7.1.1",
@@ -15226,7 +15234,7 @@
15226 "read-package-tree": { 15234 "read-package-tree": {
15227 "version": "5.2.1", 15235 "version": "5.2.1",
15228 "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.1.tgz", 15236 "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.1.tgz",
15229 "integrity": "sha512-2CNoRoh95LxY47LvqrehIAfUVda2JbuFE/HaGYs42bNrGG+ojbw1h3zOcPcQ+1GQ3+rkzNndZn85u1XyZ3UsIA==", 15237 "integrity": "sha1-Yhixh9b6yCKJzkOHu7r47vU2rWM=",
15230 "dev": true, 15238 "dev": true,
15231 "requires": { 15239 "requires": {
15232 "debuglog": "^1.0.1", 15240 "debuglog": "^1.0.1",
@@ -15594,7 +15602,7 @@
15594 }, 15602 },
15595 "htmlparser2": { 15603 "htmlparser2": {
15596 "version": "3.3.0", 15604 "version": "3.3.0",
15597 "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", 15605 "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz",
15598 "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", 15606 "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=",
15599 "dev": true, 15607 "dev": true,
15600 "requires": { 15608 "requires": {
@@ -15612,7 +15620,7 @@
15612 }, 15620 },
15613 "readable-stream": { 15621 "readable-stream": {
15614 "version": "1.0.34", 15622 "version": "1.0.34",
15615 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", 15623 "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
15616 "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", 15624 "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
15617 "dev": true, 15625 "dev": true,
15618 "requires": { 15626 "requires": {
@@ -15624,7 +15632,7 @@
15624 }, 15632 },
15625 "string_decoder": { 15633 "string_decoder": {
15626 "version": "0.10.31", 15634 "version": "0.10.31",
15627 "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 15635 "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
15628 "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 15636 "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
15629 "dev": true 15637 "dev": true
15630 }, 15638 },
@@ -16247,7 +16255,7 @@
16247 "dependencies": { 16255 "dependencies": {
16248 "debug": { 16256 "debug": {
16249 "version": "1.0.4", 16257 "version": "1.0.4",
16250 "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", 16258 "resolved": "http://registry.npmjs.org/debug/-/debug-1.0.4.tgz",
16251 "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", 16259 "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=",
16252 "dev": true, 16260 "dev": true,
16253 "requires": { 16261 "requires": {
@@ -16262,7 +16270,7 @@
16262 }, 16270 },
16263 "ms": { 16271 "ms": {
16264 "version": "0.6.2", 16272 "version": "0.6.2",
16265 "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", 16273 "resolved": "http://registry.npmjs.org/ms/-/ms-0.6.2.tgz",
16266 "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=", 16274 "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=",
16267 "dev": true 16275 "dev": true
16268 } 16276 }
@@ -16339,7 +16347,7 @@
16339 }, 16347 },
16340 "sha.js": { 16348 "sha.js": {
16341 "version": "2.4.11", 16349 "version": "2.4.11",
16342 "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 16350 "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
16343 "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 16351 "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
16344 "dev": true, 16352 "dev": true,
16345 "requires": { 16353 "requires": {
@@ -16590,7 +16598,7 @@
16590 "dependencies": { 16598 "dependencies": {
16591 "debug": { 16599 "debug": {
16592 "version": "2.3.3", 16600 "version": "2.3.3",
16593 "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", 16601 "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
16594 "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", 16602 "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
16595 "dev": true, 16603 "dev": true,
16596 "requires": { 16604 "requires": {
@@ -16599,7 +16607,7 @@
16599 }, 16607 },
16600 "ms": { 16608 "ms": {
16601 "version": "0.7.2", 16609 "version": "0.7.2",
16602 "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 16610 "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
16603 "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", 16611 "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
16604 "dev": true 16612 "dev": true
16605 }, 16613 },
@@ -16623,7 +16631,7 @@
16623 "dependencies": { 16631 "dependencies": {
16624 "debug": { 16632 "debug": {
16625 "version": "2.3.3", 16633 "version": "2.3.3",
16626 "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", 16634 "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
16627 "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", 16635 "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
16628 "dev": true, 16636 "dev": true,
16629 "requires": { 16637 "requires": {
@@ -16632,7 +16640,7 @@
16632 }, 16640 },
16633 "ms": { 16641 "ms": {
16634 "version": "0.7.2", 16642 "version": "0.7.2",
16635 "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 16643 "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
16636 "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", 16644 "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
16637 "dev": true 16645 "dev": true
16638 } 16646 }
@@ -16659,7 +16667,7 @@
16659 "dependencies": { 16667 "dependencies": {
16660 "debug": { 16668 "debug": {
16661 "version": "2.3.3", 16669 "version": "2.3.3",
16662 "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", 16670 "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
16663 "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", 16671 "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
16664 "dev": true, 16672 "dev": true,
16665 "requires": { 16673 "requires": {
@@ -16668,7 +16676,7 @@
16668 }, 16676 },
16669 "ms": { 16677 "ms": {
16670 "version": "0.7.2", 16678 "version": "0.7.2",
16671 "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 16679 "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
16672 "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", 16680 "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
16673 "dev": true 16681 "dev": true
16674 } 16682 }
@@ -16694,7 +16702,7 @@
16694 }, 16702 },
16695 "debug": { 16703 "debug": {
16696 "version": "2.2.0", 16704 "version": "2.2.0",
16697 "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 16705 "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
16698 "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 16706 "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
16699 "dev": true, 16707 "dev": true,
16700 "requires": { 16708 "requires": {
@@ -16709,7 +16717,7 @@
16709 }, 16717 },
16710 "ms": { 16718 "ms": {
16711 "version": "0.7.1", 16719 "version": "0.7.1",
16712 "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 16720 "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
16713 "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", 16721 "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
16714 "dev": true 16722 "dev": true
16715 } 16723 }
@@ -16785,7 +16793,7 @@
16785 "socks-proxy-agent": { 16793 "socks-proxy-agent": {
16786 "version": "4.0.1", 16794 "version": "4.0.1",
16787 "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", 16795 "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz",
16788 "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", 16796 "integrity": "sha1-WTa/i3B6mTB5xvN9sgkYIb/6ZHM=",
16789 "dev": true, 16797 "dev": true,
16790 "requires": { 16798 "requires": {
16791 "agent-base": "~4.2.0", 16799 "agent-base": "~4.2.0",
@@ -17022,7 +17030,7 @@
17022 "ssri": { 17030 "ssri": {
17023 "version": "6.0.1", 17031 "version": "6.0.1",
17024 "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", 17032 "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
17025 "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", 17033 "integrity": "sha1-KjxBso3UW2K2Nnbst0ABJlrp7dg=",
17026 "dev": true, 17034 "dev": true,
17027 "requires": { 17035 "requires": {
17028 "figgy-pudding": "^3.5.1" 17036 "figgy-pudding": "^3.5.1"
@@ -17141,7 +17149,7 @@
17141 "stream-each": { 17149 "stream-each": {
17142 "version": "1.2.3", 17150 "version": "1.2.3",
17143 "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", 17151 "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
17144 "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", 17152 "integrity": "sha1-6+J6DDibBPvMIzZClS4Qcxr6m64=",
17145 "dev": true, 17153 "dev": true,
17146 "requires": { 17154 "requires": {
17147 "end-of-stream": "^1.1.0", 17155 "end-of-stream": "^1.1.0",
@@ -17537,7 +17545,7 @@
17537 }, 17545 },
17538 "through": { 17546 "through": {
17539 "version": "2.3.8", 17547 "version": "2.3.8",
17540 "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 17548 "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
17541 "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 17549 "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
17542 "dev": true 17550 "dev": true
17543 }, 17551 },
@@ -17809,7 +17817,7 @@
17809 "tslint": { 17817 "tslint": {
17810 "version": "5.12.0", 17818 "version": "5.12.0",
17811 "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.0.tgz", 17819 "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.0.tgz",
17812 "integrity": "sha512-CKEcH1MHUBhoV43SA/Jmy1l24HJJgI0eyLbBNSRyFlsQvb9v6Zdq+Nz2vEOH00nC5SUx4SneJ59PZUS/ARcokQ==", 17820 "integrity": "sha1-R/LbopHtPVgHUtEJhm+2QHaPyjY=",
17813 "dev": true, 17821 "dev": true,
17814 "requires": { 17822 "requires": {
17815 "babel-code-frame": "^6.22.0", 17823 "babel-code-frame": "^6.22.0",
@@ -17829,7 +17837,7 @@
17829 "tslint-config-airbnb": { 17837 "tslint-config-airbnb": {
17830 "version": "5.11.1", 17838 "version": "5.11.1",
17831 "resolved": "https://registry.npmjs.org/tslint-config-airbnb/-/tslint-config-airbnb-5.11.1.tgz", 17839 "resolved": "https://registry.npmjs.org/tslint-config-airbnb/-/tslint-config-airbnb-5.11.1.tgz",
17832 "integrity": "sha512-hkaittm2607vVMe8eotANGN1CimD5tor7uoY3ypg2VTtEcDB/KGWYbJOz58t8LI4cWSyWtgqYQ5F0HwKxxhlkQ==", 17840 "integrity": "sha1-UaJ/u4vyTBRNBkonSnHaR+fs5hc=",
17833 "dev": true, 17841 "dev": true,
17834 "requires": { 17842 "requires": {
17835 "tslint-consistent-codestyle": "^1.14.1", 17843 "tslint-consistent-codestyle": "^1.14.1",
@@ -17851,7 +17859,7 @@
17851 "tslint-eslint-rules": { 17859 "tslint-eslint-rules": {
17852 "version": "5.4.0", 17860 "version": "5.4.0",
17853 "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz", 17861 "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz",
17854 "integrity": "sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==", 17862 "integrity": "sha1-5IjMkYG/GT/lzXv8ohOnaV8XN7U=",
17855 "dev": true, 17863 "dev": true,
17856 "requires": { 17864 "requires": {
17857 "doctrine": "0.7.2", 17865 "doctrine": "0.7.2",
@@ -17861,7 +17869,7 @@
17861 "dependencies": { 17869 "dependencies": {
17862 "doctrine": { 17870 "doctrine": {
17863 "version": "0.7.2", 17871 "version": "0.7.2",
17864 "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", 17872 "resolved": "http://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz",
17865 "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", 17873 "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=",
17866 "dev": true, 17874 "dev": true,
17867 "requires": { 17875 "requires": {
@@ -17884,7 +17892,7 @@
17884 "tslib": { 17892 "tslib": {
17885 "version": "1.9.0", 17893 "version": "1.9.0",
17886 "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", 17894 "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz",
17887 "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", 17895 "integrity": "sha1-43qG/ajLuvI6BX9HPJ9Nxk5fwug=",
17888 "dev": true 17896 "dev": true
17889 }, 17897 },
17890 "tsutils": { 17898 "tsutils": {
@@ -17901,7 +17909,7 @@
17901 "tslint-microsoft-contrib": { 17909 "tslint-microsoft-contrib": {
17902 "version": "5.2.1", 17910 "version": "5.2.1",
17903 "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.2.1.tgz", 17911 "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.2.1.tgz",
17904 "integrity": "sha512-PDYjvpo0gN9IfMULwKk0KpVOPMhU6cNoT9VwCOLeDl/QS8v8W2yspRpFFuUS7/c5EIH/n8ApMi8TxJAz1tfFUA==", 17912 "integrity": "sha1-pihoOfgA4lkdBB6igAx3SHhErYE=",
17905 "dev": true, 17913 "dev": true,
17906 "requires": { 17914 "requires": {
17907 "tsutils": "^2.27.2 <2.29.0" 17915 "tsutils": "^2.27.2 <2.29.0"
@@ -17910,7 +17918,7 @@
17910 "tsutils": { 17918 "tsutils": {
17911 "version": "2.28.0", 17919 "version": "2.28.0",
17912 "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz", 17920 "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz",
17913 "integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==", 17921 "integrity": "sha1-a9ceFggo+dAZtvToRHQiKPhRaaE=",
17914 "dev": true, 17922 "dev": true,
17915 "requires": { 17923 "requires": {
17916 "tslib": "^1.8.1" 17924 "tslib": "^1.8.1"
@@ -17921,7 +17929,7 @@
17921 "tsutils": { 17929 "tsutils": {
17922 "version": "2.29.0", 17930 "version": "2.29.0",
17923 "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", 17931 "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
17924 "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", 17932 "integrity": "sha1-MrSIUBRnrL7dS4VJhnOggSrKC5k=",
17925 "dev": true, 17933 "dev": true,
17926 "requires": { 17934 "requires": {
17927 "tslib": "^1.8.1" 17935 "tslib": "^1.8.1"
@@ -17929,7 +17937,7 @@
17929 }, 17937 },
17930 "tty-browserify": { 17938 "tty-browserify": {
17931 "version": "0.0.0", 17939 "version": "0.0.0",
17932 "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", 17940 "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
17933 "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", 17941 "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
17934 "dev": true 17942 "dev": true
17935 }, 17943 },
@@ -17976,7 +17984,7 @@
17976 "typescript": { 17984 "typescript": {
17977 "version": "3.2.2", 17985 "version": "3.2.2",
17978 "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", 17986 "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz",
17979 "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", 17987 "integrity": "sha1-/oEBxGqhI/g1NSPr3PVzDCrkk+U=",
17980 "dev": true 17988 "dev": true
17981 }, 17989 },
17982 "ua-parser-js": { 17990 "ua-parser-js": {
@@ -18115,7 +18123,7 @@
18115 "unique-filename": { 18123 "unique-filename": {
18116 "version": "1.1.1", 18124 "version": "1.1.1",
18117 "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", 18125 "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
18118 "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", 18126 "integrity": "sha1-HWl2k2mtoFgxA6HmrodoG1ZXMjA=",
18119 "dev": true, 18127 "dev": true,
18120 "requires": { 18128 "requires": {
18121 "unique-slug": "^2.0.0" 18129 "unique-slug": "^2.0.0"
@@ -18124,7 +18132,7 @@
18124 "unique-slug": { 18132 "unique-slug": {
18125 "version": "2.0.1", 18133 "version": "2.0.1",
18126 "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", 18134 "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz",
18127 "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", 18135 "integrity": "sha1-Xp7cbRzo+yZNsYpQfvm9hURFHKY=",
18128 "dev": true, 18136 "dev": true,
18129 "requires": { 18137 "requires": {
18130 "imurmurhash": "^0.1.4" 18138 "imurmurhash": "^0.1.4"
@@ -18569,7 +18577,7 @@
18569 }, 18577 },
18570 "vm-browserify": { 18578 "vm-browserify": {
18571 "version": "0.0.4", 18579 "version": "0.0.4",
18572 "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", 18580 "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
18573 "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", 18581 "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
18574 "dev": true, 18582 "dev": true,
18575 "requires": { 18583 "requires": {
@@ -18674,7 +18682,7 @@
18674 "webidl-conversions": { 18682 "webidl-conversions": {
18675 "version": "4.0.2", 18683 "version": "4.0.2",
18676 "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", 18684 "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
18677 "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", 18685 "integrity": "sha1-qFWYCx8LazWbodXZ+zmulB+qY60=",
18678 "dev": true 18686 "dev": true
18679 }, 18687 },
18680 "webpack": { 18688 "webpack": {
@@ -19165,7 +19173,7 @@
19165 }, 19173 },
19166 "wrap-ansi": { 19174 "wrap-ansi": {
19167 "version": "2.1.0", 19175 "version": "2.1.0",
19168 "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 19176 "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
19169 "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 19177 "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
19170 "dev": true, 19178 "dev": true,
19171 "requires": { 19179 "requires": {
@@ -19273,7 +19281,7 @@
19273 "write-pkg": { 19281 "write-pkg": {
19274 "version": "3.2.0", 19282 "version": "3.2.0",
19275 "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-3.2.0.tgz", 19283 "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-3.2.0.tgz",
19276 "integrity": "sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw==", 19284 "integrity": "sha1-DheP6Xgg04mokovHlTXb5ows/yE=",
19277 "dev": true, 19285 "dev": true,
19278 "requires": { 19286 "requires": {
19279 "sort-keys": "^2.0.0", 19287 "sort-keys": "^2.0.0",
@@ -19315,7 +19323,7 @@
19315 }, 19323 },
19316 "xmlbuilder": { 19324 "xmlbuilder": {
19317 "version": "9.0.7", 19325 "version": "9.0.7",
19318 "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 19326 "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
19319 "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", 19327 "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
19320 "dev": true 19328 "dev": true
19321 }, 19329 },
diff --git a/packages/forms/src/input/index.tsx b/packages/forms/src/input/index.tsx
index 5178904d3..a2d7c62d5 100644
--- a/packages/forms/src/input/index.tsx
+++ b/packages/forms/src/input/index.tsx
@@ -25,6 +25,7 @@ interface IProps extends React.InputHTMLAttributes<HTMLInputElement>, IFormField
25 showPasswordToggle?: boolean; 25 showPasswordToggle?: boolean;
26 data: IData; 26 data: IData;
27 inputClassName?: string; 27 inputClassName?: string;
28 onEnterKey?: Function;
28} 29}
29 30
30interface IState { 31interface IState {
@@ -33,7 +34,7 @@ interface IState {
33} 34}
34 35
35class InputComponent extends Component<IProps, IState> { 36class InputComponent extends Component<IProps, IState> {
36 public static defaultProps = { 37 static defaultProps = {
37 focus: false, 38 focus: false,
38 onChange: () => {}, 39 onChange: () => {},
39 onBlur: () => {}, 40 onBlur: () => {},
@@ -81,6 +82,13 @@ class InputComponent extends Component<IProps, IState> {
81 } 82 }
82 } 83 }
83 84
85 onInputKeyPress(e: React.KeyboardEvent) {
86 if (e.key === "Enter") {
87 const { onEnterKey } = this.props;
88 onEnterKey && onEnterKey();
89 }
90 }
91
84 render() { 92 render() {
85 const { 93 const {
86 classes, 94 classes,
@@ -124,6 +132,7 @@ class InputComponent extends Component<IProps, IState> {
124 title={label} 132 title={label}
125 showLabel={showLabel} 133 showLabel={showLabel}
126 htmlFor={id} 134 htmlFor={id}
135 className={classes.label}
127 isRequired={required} 136 isRequired={required}
128 > 137 >
129 <div 138 <div
@@ -152,6 +161,7 @@ class InputComponent extends Component<IProps, IState> {
152 onFocus={onFocus} 161 onFocus={onFocus}
153 onBlur={onBlur} 162 onBlur={onBlur}
154 disabled={disabled} 163 disabled={disabled}
164 onKeyPress={this.onInputKeyPress.bind(this)}
155 min={min} 165 min={min}
156 max={max} 166 max={max}
157 step={step} 167 step={step}
diff --git a/packages/forms/src/input/styles.ts b/packages/forms/src/input/styles.ts
index c038295cd..e2ab30a4f 100644
--- a/packages/forms/src/input/styles.ts
+++ b/packages/forms/src/input/styles.ts
@@ -10,6 +10,11 @@ const prefixStyles = (theme: Theme) => ({
10}); 10});
11 11
12export default (theme: Theme) => ({ 12export default (theme: Theme) => ({
13 label: {
14 '& > div': {
15 marginTop: 5,
16 }
17 },
13 disabled: { 18 disabled: {
14 opacity: theme.inputDisabledOpacity, 19 opacity: theme.inputDisabledOpacity,
15 }, 20 },
diff --git a/packages/forms/src/label/styles.ts b/packages/forms/src/label/styles.ts
index f3998de04..c64c9b285 100644
--- a/packages/forms/src/label/styles.ts
+++ b/packages/forms/src/label/styles.ts
@@ -1,9 +1,7 @@
1import { Theme } from '../../../theme/lib'; 1import { Theme } from '../../../theme/lib';
2 2
3export default (theme: Theme) => ({ 3export default (theme: Theme) => ({
4 content: { 4 content: {},
5 marginTop: 5,
6 },
7 label: { 5 label: {
8 color: theme.labelColor, 6 color: theme.labelColor,
9 fontSize: theme.uiFontSize, 7 fontSize: theme.uiFontSize,
diff --git a/packages/forms/src/select/index.tsx b/packages/forms/src/select/index.tsx
index f419d0351..0e5ded176 100644
--- a/packages/forms/src/select/index.tsx
+++ b/packages/forms/src/select/index.tsx
@@ -56,6 +56,11 @@ const styles = (theme: Theme) => ({
56 textAlign: 'left', 56 textAlign: 'left',
57 color: theme.selectColor, 57 color: theme.selectColor,
58 }, 58 },
59 label: {
60 '& > div': {
61 marginTop: 5,
62 }
63 },
59 popup: { 64 popup: {
60 opacity: 0, 65 opacity: 0,
61 height: 0, 66 height: 0,
@@ -335,6 +340,7 @@ class SelectComponent extends Component<IProps> {
335 title={label} 340 title={label}
336 showLabel={showLabel} 341 showLabel={showLabel}
337 htmlFor={id} 342 htmlFor={id}
343 className={classes.label}
338 isRequired={required} 344 isRequired={required}
339 > 345 >
340 <div 346 <div
diff --git a/packages/forms/src/toggle/index.tsx b/packages/forms/src/toggle/index.tsx
index 6487f1d07..d84508a5f 100644
--- a/packages/forms/src/toggle/index.tsx
+++ b/packages/forms/src/toggle/index.tsx
@@ -1,7 +1,7 @@
1import { Theme } from '@meetfranz/theme'; 1import { Theme } from '@meetfranz/theme';
2import classnames from 'classnames'; 2import classnames from 'classnames';
3import CSS from 'csstype'; 3import CSS from 'csstype';
4import React, { Component, createRef } from 'react'; 4import React, { Component } from 'react';
5import injectStyle from 'react-jss'; 5import injectStyle from 'react-jss';
6 6
7import { IFormField, IWithStyle, Omit } from '../typings/generic'; 7import { IFormField, IWithStyle, Omit } from '../typings/generic';
@@ -45,11 +45,11 @@ const styles = (theme: Theme) => ({
45 }, 45 },
46 toggleLabel: { 46 toggleLabel: {
47 display: 'flex', 47 display: 'flex',
48 alignItems: 'center',
48 49
49 '& > span': { 50 '& > span': {
50 order: 1, 51 order: 1,
51 marginLeft: 15, 52 marginLeft: 15,
52 marginTop: 2,
53 }, 53 },
54 }, 54 },
55}); 55});
diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts
index 3a56719b2..fd04b106c 100644
--- a/packages/theme/src/themes/dark/index.ts
+++ b/packages/theme/src/themes/dark/index.ts
@@ -1,4 +1,5 @@
1import color from 'color'; 1import color from 'color';
2import { merge, cloneDeep } from 'lodash';
2 3
3import * as defaultStyles from '../default'; 4import * as defaultStyles from '../default';
4import * as legacyStyles from '../legacy'; 5import * as legacyStyles from '../legacy';
@@ -63,3 +64,69 @@ export const selectSearchColor = inputBackground;
63 64
64// Modal 65// Modal
65export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.8).rgb().string(); 66export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.8).rgb().string();
67
68// Services
69export const services = merge({}, defaultStyles.services, {
70 listItems: {
71 borderColor: legacyStyles.darkThemeGrayDarker,
72 hoverBgColor: legacyStyles.darkThemeGrayDarker,
73 disabled: {
74 color: legacyStyles.darkThemeGray,
75 },
76 },
77});
78
79// Service Icon
80export const serviceIcon = merge({}, defaultStyles.serviceIcon, {
81 isCustom: {
82 border: `1px solid ${legacyStyles.darkThemeGrayDark}`,
83 },
84});
85
86// Workspaces
87const drawerBg = color(colorBackground).lighten(0.3).hex();
88
89export const workspaces = merge({}, defaultStyles.workspaces, {
90 settings: {
91 listItems: cloneDeep(services.listItems),
92 },
93 drawer: {
94 background: drawerBg,
95 addButton: {
96 color: legacyStyles.darkThemeGrayLighter,
97 hoverColor: legacyStyles.darkThemeGraySmoke,
98 },
99 listItem: {
100 border: color(drawerBg).lighten(0.2).hex(),
101 hoverBackground: color(drawerBg).lighten(0.2).hex(),
102 activeBackground: defaultStyles.brandPrimary,
103 name: {
104 color: colorText,
105 activeColor: 'white',
106 },
107 services: {
108 color: color(colorText).darken(0.5).hex(),
109 active: color(defaultStyles.brandPrimary).lighten(0.5).hex(),
110 },
111 },
112 },
113});
114
115// // Workspace settings
116// export const workspaceSettings = merge({}, defaultStyles.workspaceSettings, {
117// listItemBorderColor: legacyStyles.darkThemeGrayDarker,
118// listItemHoverBgColor: legacyStyles.darkThemeGrayDarker,
119// });
120//
121// // Workspace Drawer
122// export const workspaceDrawerBackground = color(colorBackground).lighten(0.3).hex();
123// export const workspaceDrawerAddButtonColor = legacyStyles.darkThemeGrayLighter;
124// export const workspaceDrawerAddButtonHoverColor = legacyStyles.darkThemeGraySmoke;
125// export const workspaceDrawerItemBorder = color(workspaceDrawerBackground).lighten(0.2).hex();
126// export const workspaceDrawerItemHoverBackground = color(workspaceDrawerBackground).lighten(0.2).hex();
127// export const workspaceDrawerItemActiveBackground = defaultStyles.brandPrimary;
128// export const workspaceDrawerItemNameColor = colorText;
129// export const workspaceDrawerItemNameActiveColor = 'white';
130// export const workspaceDrawerServicesColor = color(colorText).darken(0.5).hex();
131// export const workspaceDrawerServicesActiveColor = color(defaultStyles.brandPrimary).lighten(0.5).hex();
132//
diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts
index 273792d46..d0493b82f 100644
--- a/packages/theme/src/themes/default/index.ts
+++ b/packages/theme/src/themes/default/index.ts
@@ -1,4 +1,5 @@
1import color from 'color'; 1import color from 'color';
2import { cloneDeep } from 'lodash';
2 3
3import * as legacyStyles from '../legacy'; 4import * as legacyStyles from '../legacy';
4 5
@@ -142,3 +143,80 @@ export const badgeBorderRadius = 50;
142 143
143// Modal 144// Modal
144export const colorModalOverlayBackground = color('#000').alpha(0.5).rgb().string(); 145export const colorModalOverlayBackground = color('#000').alpha(0.5).rgb().string();
146
147// Services
148export const services = {
149 listItems: {
150 padding: 10,
151 height: 57,
152 borderColor: legacyStyles.themeGrayLightest,
153 hoverBgColor: legacyStyles.themeGrayLightest,
154 disabled: {
155 color: legacyStyles.themeGrayLight,
156 },
157 },
158};
159
160// Service Icon
161export const serviceIcon = {
162 width: 35,
163 isCustom: {
164 border: `1px solid ${legacyStyles.themeGrayLighter}`,
165 borderRadius: legacyStyles.themeBorderRadius,
166 width: 37,
167 },
168};
169
170// Workspaces
171const drawerBg = color(colorBackground).lighten(0.1).hex();
172
173export const workspaces = {
174 settings: {
175 listItems: cloneDeep(services.listItems),
176 },
177 drawer: {
178 width: 300,
179 padding: 20,
180 background: drawerBg,
181 buttons: {
182 color: color(legacyStyles.themeGrayLight).lighten(0.1).hex(),
183 hoverColor: legacyStyles.themeGrayLight,
184 },
185 listItem: {
186 hoverBackground: color(drawerBg).darken(0.01).hex(),
187 activeBackground: legacyStyles.themeGrayLightest,
188 border: color(drawerBg).darken(0.05).hex(),
189 name: {
190 color: colorText,
191 activeColor: colorText,
192 },
193 services: {
194 color: color(colorText).lighten(1.5).hex(),
195 active: color(colorText).lighten(1.5).hex(),
196 },
197 },
198 },
199 switchingIndicator: {
200 spinnerColor: 'white',
201 },
202};
203
204// export const workspaceSettings = {
205// listItemHeight: 57,
206// listItemBorderColor: legacyStyles.themeGrayLightest,
207// listItemHoverBgColor: legacyStyles.themeGrayLightest,
208// };
209//
210// // Workspace Drawer
211// export const workspaceDrawerWidth = 300;
212// export const workspaceDrawerPadding = 20;
213// export const workspaceDrawerBackground = color(colorBackground).lighten(0.1).hex();
214// export const workspaceDrawerAddButtonColor = legacyStyles.themeGrayLight;
215// export const workspaceDrawerAddButtonHoverColor = color(legacyStyles.themeGrayLight).lighten(0.1).hex();
216// export const workspaceDrawerItemHoverBackground = color(workspaceDrawerBackground).darken(0.01).hex();
217// export const workspaceDrawerItemActiveBackground = legacyStyles.themeGrayLightest;
218// export const workspaceDrawerItemBorder = color(workspaceDrawerBackground).darken(0.05).hex();
219// export const workspaceDrawerItemNameColor = colorText;
220// export const workspaceDrawerItemNameActiveColor = colorText;
221// export const workspaceDrawerServicesColor = color(colorText).lighten(1.5).hex();
222// export const workspaceDrawerServicesActiveColor = workspaceDrawerServicesColor;
diff --git a/packages/ui/src/badge/ProBadge.tsx b/packages/ui/src/badge/ProBadge.tsx
new file mode 100644
index 000000000..612e23210
--- /dev/null
+++ b/packages/ui/src/badge/ProBadge.tsx
@@ -0,0 +1,64 @@
1import { Theme } from '@meetfranz/theme';
2import classnames from 'classnames';
3import React, { Component } from 'react';
4import injectStyle from 'react-jss';
5
6import { Icon, Badge } from '../';
7import { IWithStyle } from '../typings/generic';
8
9interface IProps extends IWithStyle {
10 badgeClasses?: string;
11 iconClasses?: string;
12 inverted?: boolean;
13}
14
15const styles = (theme: Theme) => ({
16 badge: {
17 height: 'auto',
18 padding: [4, 6, 2, 7],
19 borderRadius: theme.borderRadiusSmall,
20 },
21 invertedBadge: {
22 background: theme.styleTypes.primary.contrast,
23 color: theme.styleTypes.primary.accent,
24 },
25 icon: {
26 fill: theme.styleTypes.primary.contrast,
27 },
28 invertedIcon: {
29 fill: theme.styleTypes.primary.accent,
30 },
31});
32
33class ProBadgeComponent extends Component<IProps> {
34 render() {
35 const {
36 classes,
37 badgeClasses,
38 iconClasses,
39 inverted,
40 } = this.props;
41
42 return (
43 <Badge
44 type="primary"
45 className={classnames([
46 classes.badge,
47 inverted && classes.invertedBadge,
48 badgeClasses,
49 ])}
50 >
51 <Icon
52 icon="mdiStar"
53 className={classnames([
54 classes.icon,
55 inverted && classes.invertedIcon,
56 iconClasses,
57 ])}
58 />
59 </Badge>
60 );
61 }
62}
63
64export const ProBadge = injectStyle(styles)(ProBadgeComponent);
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 1eeec5144..666495ce9 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -3,3 +3,4 @@ export { Infobox } from './infobox';
3export * from './headline'; 3export * from './headline';
4export { Loader } from './loader'; 4export { Loader } from './loader';
5export { Badge } from './badge'; 5export { Badge } from './badge';
6export { ProBadge } from './badge/ProBadge';
diff --git a/packages/ui/src/infobox/index.tsx b/packages/ui/src/infobox/index.tsx
index 1a442a733..9066a623e 100644
--- a/packages/ui/src/infobox/index.tsx
+++ b/packages/ui/src/infobox/index.tsx
@@ -11,6 +11,7 @@ interface IProps extends IWithStyle {
11 type?: string; 11 type?: string;
12 dismissable?: boolean; 12 dismissable?: boolean;
13 onDismiss?: () => void; 13 onDismiss?: () => void;
14 onUnmount?: () => void;
14 ctaOnClick?: () => void; 15 ctaOnClick?: () => void;
15 ctaLabel?: string; 16 ctaLabel?: string;
16 ctaLoading?: boolean; 17 ctaLoading?: boolean;
@@ -46,6 +47,7 @@ const styles = (theme: Theme) => ({
46 wrapper: { 47 wrapper: {
47 position: 'relative', 48 position: 'relative',
48 overflow: 'hidden', 49 overflow: 'hidden',
50 height: 'auto',
49 }, 51 },
50 infobox: { 52 infobox: {
51 alignItems: 'center', 53 alignItems: 'center',
@@ -129,6 +131,11 @@ class InfoboxComponent extends Component<IProps, IState> {
129 }, 3000); 131 }, 3000);
130 } 132 }
131 133
134 componentWillUnmount(): void {
135 const { onUnmount } = this.props;
136 if (onUnmount) onUnmount();
137 }
138
132 render() { 139 render() {
133 const { 140 const {
134 classes, 141 classes,
diff --git a/packages/ui/src/loader/index.tsx b/packages/ui/src/loader/index.tsx
index 46545c786..4a3c8274f 100644
--- a/packages/ui/src/loader/index.tsx
+++ b/packages/ui/src/loader/index.tsx
@@ -8,6 +8,7 @@ import { IWithStyle } from '../typings/generic';
8 8
9interface IProps extends IWithStyle { 9interface IProps extends IWithStyle {
10 className?: string; 10 className?: string;
11 color?: string;
11} 12}
12 13
13const styles = (theme: Theme) => ({ 14const styles = (theme: Theme) => ({
@@ -22,6 +23,7 @@ class LoaderComponent extends Component<IProps> {
22 const { 23 const {
23 classes, 24 classes,
24 className, 25 className,
26 color,
25 theme, 27 theme,
26 } = this.props; 28 } = this.props;
27 29
@@ -37,7 +39,7 @@ class LoaderComponent extends Component<IProps> {
37 loaded={false} 39 loaded={false}
38 width={4} 40 width={4}
39 scale={0.75} 41 scale={0.75}
40 color={theme.colorText} 42 color={color || theme.colorText}
41 parentClassName={classes.loader} 43 parentClassName={classes.loader}
42 /> 44 />
43 </div> 45 </div>
diff --git a/src/actions/index.js b/src/actions/index.js
index 59acabb0b..00f843cd6 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -11,6 +11,7 @@ import payment from './payment';
11import news from './news'; 11import news from './news';
12import settings from './settings'; 12import settings from './settings';
13import requests from './requests'; 13import requests from './requests';
14import workspaces from '../features/workspaces/actions';
14 15
15const actions = Object.assign({}, { 16const actions = Object.assign({}, {
16 service, 17 service,
@@ -25,4 +26,7 @@ const actions = Object.assign({}, {
25 requests, 26 requests,
26}); 27});
27 28
28export default defineActions(actions, PropTypes.checkPropTypes); 29export default Object.assign(
30 defineActions(actions, PropTypes.checkPropTypes),
31 { workspaces },
32);
diff --git a/src/actions/lib/actions.js b/src/actions/lib/actions.js
index 6571e9441..2bc7d2711 100644
--- a/src/actions/lib/actions.js
+++ b/src/actions/lib/actions.js
@@ -9,6 +9,10 @@ export const createActionsFromDefinitions = (actionDefinitions, validate) => {
9 actions[actionName] = action; 9 actions[actionName] = action;
10 action.listeners = []; 10 action.listeners = [];
11 action.listen = listener => action.listeners.push(listener); 11 action.listen = listener => action.listeners.push(listener);
12 action.off = (listener) => {
13 const { listeners } = action;
14 listeners.splice(listeners.indexOf(listener), 1);
15 };
12 action.notify = params => action.listeners.forEach(listener => listener(params)); 16 action.notify = params => action.listeners.forEach(listener => listener(params));
13 }); 17 });
14 return actions; 18 return actions;
diff --git a/src/app.js b/src/app.js
index 6660feb46..fb9f1c6ab 100644
--- a/src/app.js
+++ b/src/app.js
@@ -39,6 +39,9 @@ import PricingScreen from './containers/auth/PricingScreen';
39import InviteScreen from './containers/auth/InviteScreen'; 39import InviteScreen from './containers/auth/InviteScreen';
40import AuthLayoutContainer from './containers/auth/AuthLayoutContainer'; 40import AuthLayoutContainer from './containers/auth/AuthLayoutContainer';
41import SubscriptionPopupScreen from './containers/subscription/SubscriptionPopupScreen'; 41import SubscriptionPopupScreen from './containers/subscription/SubscriptionPopupScreen';
42import WorkspacesScreen from './features/workspaces/containers/WorkspacesScreen';
43import EditWorkspaceScreen from './features/workspaces/containers/EditWorkspaceScreen';
44import { WORKSPACES_ROUTES } from './features/workspaces';
42 45
43// Add Polyfills 46// Add Polyfills
44smoothScroll.polyfill(); 47smoothScroll.polyfill();
@@ -75,6 +78,8 @@ window.addEventListener('load', () => {
75 <Route path="/settings/recipes/:filter" component={RecipesScreen} /> 78 <Route path="/settings/recipes/:filter" component={RecipesScreen} />
76 <Route path="/settings/services" component={ServicesScreen} /> 79 <Route path="/settings/services" component={ServicesScreen} />
77 <Route path="/settings/services/:action/:id" component={EditServiceScreen} /> 80 <Route path="/settings/services/:action/:id" component={EditServiceScreen} />
81 <Route path={WORKSPACES_ROUTES.ROOT} component={WorkspacesScreen} />
82 <Route path={WORKSPACES_ROUTES.EDIT} component={EditWorkspaceScreen} />
78 <Route path="/settings/user" component={AccountScreen} /> 83 <Route path="/settings/user" component={AccountScreen} />
79 <Route path="/settings/user/edit" component={EditUserScreen} /> 84 <Route path="/settings/user/edit" component={EditUserScreen} />
80 <Route path="/settings/app" component={EditSettingsScreen} /> 85 <Route path="/settings/app" component={EditSettingsScreen} />
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index 593149e72..b7f7722dd 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import { TitleBar } from 'electron-react-titlebar'; 5import { TitleBar } from 'electron-react-titlebar';
6import injectSheet from 'react-jss';
6 7
7import InfoBar from '../ui/InfoBar'; 8import InfoBar from '../ui/InfoBar';
8import { Component as DelayApp } from '../../features/delayApp'; 9import { Component as DelayApp } from '../../features/delayApp';
@@ -13,6 +14,8 @@ import ErrorBoundary from '../util/ErrorBoundary';
13// import globalMessages from '../../i18n/globalMessages'; 14// import globalMessages from '../../i18n/globalMessages';
14 15
15import { isWindows } from '../../environment'; 16import { isWindows } from '../../environment';
17import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator';
18import { workspaceStore } from '../../features/workspaces';
16 19
17function createMarkup(HTMLString) { 20function createMarkup(HTMLString) {
18 return { __html: HTMLString }; 21 return { __html: HTMLString };
@@ -45,10 +48,23 @@ const messages = defineMessages({
45 }, 48 },
46}); 49});
47 50
48export default @observer class AppLayout extends Component { 51const styles = theme => ({
52 appContent: {
53 width: `calc(100% + ${theme.workspaces.drawer.width}px)`,
54 transition: 'transform 0.5s ease',
55 transform() {
56 return workspaceStore.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaces.drawer.width}px)`;
57 },
58 },
59});
60
61@injectSheet(styles) @observer
62class AppLayout extends Component {
49 static propTypes = { 63 static propTypes = {
64 classes: PropTypes.object.isRequired,
50 isFullScreen: PropTypes.bool.isRequired, 65 isFullScreen: PropTypes.bool.isRequired,
51 sidebar: PropTypes.element.isRequired, 66 sidebar: PropTypes.element.isRequired,
67 workspacesDrawer: PropTypes.element.isRequired,
52 services: PropTypes.element.isRequired, 68 services: PropTypes.element.isRequired,
53 children: PropTypes.element, 69 children: PropTypes.element,
54 news: MobxPropTypes.arrayOrObservableArray.isRequired, 70 news: MobxPropTypes.arrayOrObservableArray.isRequired,
@@ -76,7 +92,9 @@ export default @observer class AppLayout extends Component {
76 92
77 render() { 93 render() {
78 const { 94 const {
95 classes,
79 isFullScreen, 96 isFullScreen,
97 workspacesDrawer,
80 sidebar, 98 sidebar,
81 services, 99 services,
82 children, 100 children,
@@ -102,9 +120,11 @@ export default @observer class AppLayout extends Component {
102 <div className={(darkMode ? 'theme__dark' : '')}> 120 <div className={(darkMode ? 'theme__dark' : '')}>
103 <div className="app"> 121 <div className="app">
104 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />} 122 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />}
105 <div className="app__content"> 123 <div className={`app__content ${classes.appContent}`}>
124 {workspacesDrawer}
106 {sidebar} 125 {sidebar}
107 <div className="app__service"> 126 <div className="app__service">
127 <WorkspaceSwitchingIndicator />
108 {news.length > 0 && news.map(item => ( 128 {news.length > 0 && news.map(item => (
109 <InfoBar 129 <InfoBar
110 key={item.id} 130 key={item.id}
@@ -176,3 +196,5 @@ export default @observer class AppLayout extends Component {
176 ); 196 );
177 } 197 }
178} 198}
199
200export default AppLayout;
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js
index 609a3b604..36c1f2e39 100644
--- a/src/components/layout/Sidebar.js
+++ b/src/components/layout/Sidebar.js
@@ -6,6 +6,8 @@ import { observer } from 'mobx-react';
6 6
7import Tabbar from '../services/tabs/Tabbar'; 7import Tabbar from '../services/tabs/Tabbar';
8import { ctrlKey } from '../../environment'; 8import { ctrlKey } from '../../environment';
9import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../../features/workspaces';
10import { gaEvent } from '../../lib/analytics';
9 11
10const messages = defineMessages({ 12const messages = defineMessages({
11 settings: { 13 settings: {
@@ -24,6 +26,14 @@ const messages = defineMessages({
24 id: 'sidebar.unmuteApp', 26 id: 'sidebar.unmuteApp',
25 defaultMessage: '!!!Enable notifications & audio', 27 defaultMessage: '!!!Enable notifications & audio',
26 }, 28 },
29 openWorkspaceDrawer: {
30 id: 'sidebar.openWorkspaceDrawer',
31 defaultMessage: '!!!Open workspace drawer',
32 },
33 closeWorkspaceDrawer: {
34 id: 'sidebar.closeWorkspaceDrawer',
35 defaultMessage: '!!!Close workspace drawer',
36 },
27}); 37});
28 38
29export default @observer class Sidebar extends Component { 39export default @observer class Sidebar extends Component {
@@ -31,7 +41,9 @@ export default @observer class Sidebar extends Component {
31 openSettings: PropTypes.func.isRequired, 41 openSettings: PropTypes.func.isRequired,
32 toggleMuteApp: PropTypes.func.isRequired, 42 toggleMuteApp: PropTypes.func.isRequired,
33 isAppMuted: PropTypes.bool.isRequired, 43 isAppMuted: PropTypes.bool.isRequired,
34 } 44 isWorkspaceDrawerOpen: PropTypes.bool.isRequired,
45 toggleWorkspaceDrawer: PropTypes.func.isRequired,
46 };
35 47
36 static contextTypes = { 48 static contextTypes = {
37 intl: intlShape, 49 intl: intlShape,
@@ -53,9 +65,23 @@ export default @observer class Sidebar extends Component {
53 this.setState({ tooltipEnabled: false }); 65 this.setState({ tooltipEnabled: false });
54 } 66 }
55 67
68 updateToolTip() {
69 this.disableToolTip();
70 setTimeout(this.enableToolTip.bind(this));
71 }
72
56 render() { 73 render() {
57 const { openSettings, toggleMuteApp, isAppMuted } = this.props; 74 const {
75 openSettings,
76 toggleMuteApp,
77 isAppMuted,
78 isWorkspaceDrawerOpen,
79 toggleWorkspaceDrawer,
80 } = this.props;
58 const { intl } = this.context; 81 const { intl } = this.context;
82 const workspaceToggleMessage = (
83 isWorkspaceDrawerOpen ? messages.closeWorkspaceDrawer : messages.openWorkspaceDrawer
84 );
59 85
60 return ( 86 return (
61 <div className="sidebar"> 87 <div className="sidebar">
@@ -64,9 +90,26 @@ export default @observer class Sidebar extends Component {
64 enableToolTip={() => this.enableToolTip()} 90 enableToolTip={() => this.enableToolTip()}
65 disableToolTip={() => this.disableToolTip()} 91 disableToolTip={() => this.disableToolTip()}
66 /> 92 />
93 {workspaceStore.isFeatureEnabled ? (
94 <button
95 type="button"
96 onClick={() => {
97 toggleWorkspaceDrawer();
98 this.updateToolTip();
99 gaEvent(GA_CATEGORY_WORKSPACES, 'toggleDrawer', 'sidebar');
100 }}
101 className={`sidebar__button sidebar__button--workspaces ${isWorkspaceDrawerOpen ? 'is-active' : ''}`}
102 data-tip={`${intl.formatMessage(workspaceToggleMessage)} (${ctrlKey}+D)`}
103 >
104 <i className="mdi mdi-view-grid" />
105 </button>
106 ) : null}
67 <button 107 <button
68 type="button" 108 type="button"
69 onClick={toggleMuteApp} 109 onClick={() => {
110 toggleMuteApp();
111 this.updateToolTip();
112 }}
70 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`} 113 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`}
71 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`} 114 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`}
72 > 115 >
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index 5afc54f9d..13148b9b3 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -35,11 +35,13 @@ export default @observer class ServiceView extends Component {
35 35
36 autorunDisposer = null; 36 autorunDisposer = null;
37 37
38 forceRepaintTimeout = null;
39
38 componentDidMount() { 40 componentDidMount() {
39 this.autorunDisposer = autorun(() => { 41 this.autorunDisposer = autorun(() => {
40 if (this.props.service.isActive) { 42 if (this.props.service.isActive) {
41 this.setState({ forceRepaint: true }); 43 this.setState({ forceRepaint: true });
42 setTimeout(() => { 44 this.forceRepaintTimeout = setTimeout(() => {
43 this.setState({ forceRepaint: false }); 45 this.setState({ forceRepaint: false });
44 }, 100); 46 }, 100);
45 } 47 }
@@ -48,6 +50,7 @@ export default @observer class ServiceView extends Component {
48 50
49 componentWillUnmount() { 51 componentWillUnmount() {
50 this.autorunDisposer(); 52 this.autorunDisposer();
53 clearTimeout(this.forceRepaintTimeout);
51 } 54 }
52 55
53 updateTargetUrl = (event) => { 56 updateTargetUrl = (event) => {
diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js
index dd5c2140f..5e8260ad0 100644
--- a/src/components/services/tabs/Tabbar.js
+++ b/src/components/services/tabs/Tabbar.js
@@ -19,7 +19,7 @@ export default @observer class TabBar extends Component {
19 updateService: PropTypes.func.isRequired, 19 updateService: PropTypes.func.isRequired,
20 showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, 20 showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired,
21 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, 21 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired,
22 } 22 };
23 23
24 onSortEnd = ({ oldIndex, newIndex }) => { 24 onSortEnd = ({ oldIndex, newIndex }) => {
25 const { 25 const {
@@ -45,7 +45,7 @@ export default @observer class TabBar extends Component {
45 redirect: false, 45 redirect: false,
46 }); 46 });
47 } 47 }
48 } 48 };
49 49
50 disableService({ serviceId }) { 50 disableService({ serviceId }) {
51 this.toggleService({ serviceId, isEnabled: false }); 51 this.toggleService({ serviceId, isEnabled: false });
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index 953f702f8..993b0a44a 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -2,8 +2,11 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, intlShape } from 'react-intl';
4import { inject, observer } from 'mobx-react'; 4import { inject, observer } from 'mobx-react';
5import { ProBadge } from '@meetfranz/ui';
5 6
6import Link from '../../ui/Link'; 7import Link from '../../ui/Link';
8import { workspaceStore } from '../../../features/workspaces';
9import UIStore from '../../../stores/UIStore';
7 10
8const messages = defineMessages({ 11const messages = defineMessages({
9 availableServices: { 12 availableServices: {
@@ -14,6 +17,10 @@ const messages = defineMessages({
14 id: 'settings.navigation.yourServices', 17 id: 'settings.navigation.yourServices',
15 defaultMessage: '!!!Your services', 18 defaultMessage: '!!!Your services',
16 }, 19 },
20 yourWorkspaces: {
21 id: 'settings.navigation.yourWorkspaces',
22 defaultMessage: '!!!Your workspaces',
23 },
17 account: { 24 account: {
18 id: 'settings.navigation.account', 25 id: 'settings.navigation.account',
19 defaultMessage: '!!!Account', 26 defaultMessage: '!!!Account',
@@ -34,7 +41,11 @@ const messages = defineMessages({
34 41
35export default @inject('stores') @observer class SettingsNavigation extends Component { 42export default @inject('stores') @observer class SettingsNavigation extends Component {
36 static propTypes = { 43 static propTypes = {
44 stores: PropTypes.shape({
45 ui: PropTypes.instanceOf(UIStore).isRequired,
46 }).isRequired,
37 serviceCount: PropTypes.number.isRequired, 47 serviceCount: PropTypes.number.isRequired,
48 workspaceCount: PropTypes.number.isRequired,
38 }; 49 };
39 50
40 static contextTypes = { 51 static contextTypes = {
@@ -42,7 +53,8 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
42 }; 53 };
43 54
44 render() { 55 render() {
45 const { serviceCount } = this.props; 56 const { serviceCount, workspaceCount, stores } = this.props;
57 const { isDarkThemeActive } = stores.ui;
46 const { intl } = this.context; 58 const { intl } = this.context;
47 59
48 return ( 60 return (
@@ -63,6 +75,21 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
63 {' '} 75 {' '}
64 <span className="badge">{serviceCount}</span> 76 <span className="badge">{serviceCount}</span>
65 </Link> 77 </Link>
78 {workspaceStore.isFeatureEnabled ? (
79 <Link
80 to="/settings/workspaces"
81 className="settings-navigation__link"
82 activeClassName="is-active"
83 >
84 {intl.formatMessage(messages.yourWorkspaces)}
85 {' '}
86 {workspaceStore.isPremiumUpgradeRequired ? (
87 <ProBadge inverted={!isDarkThemeActive && workspaceStore.isSettingsRouteActive} />
88 ) : (
89 <span className="badge">{workspaceCount}</span>
90 )}
91 </Link>
92 ) : null}
66 <Link 93 <Link
67 to="/settings/user" 94 to="/settings/user"
68 className="settings-navigation__link" 95 className="settings-navigation__link"
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 21616b5de..4ba2eb844 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -341,14 +341,20 @@ export default @observer class EditServiceForm extends Component {
341 </div> 341 </div>
342 </div> 342 </div>
343 343
344 <PremiumFeatureContainer condition={isSpellcheckerPremiumFeature}> 344 <PremiumFeatureContainer
345 condition={isSpellcheckerPremiumFeature}
346 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }}
347 >
345 <div className="settings__settings-group"> 348 <div className="settings__settings-group">
346 <Select field={form.$('spellcheckerLanguage')} /> 349 <Select field={form.$('spellcheckerLanguage')} />
347 </div> 350 </div>
348 </PremiumFeatureContainer> 351 </PremiumFeatureContainer>
349 352
350 {isProxyFeatureEnabled && ( 353 {isProxyFeatureEnabled && (
351 <PremiumFeatureContainer condition={isProxyPremiumFeature}> 354 <PremiumFeatureContainer
355 condition={isProxyPremiumFeature}
356 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'proxy' }}
357 >
352 <div className="settings__settings-group"> 358 <div className="settings__settings-group">
353 <h3> 359 <h3>
354 {intl.formatMessage(messages.headlineProxy)} 360 {intl.formatMessage(messages.headlineProxy)}
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
index a12df7372..53bae12df 100644
--- a/src/components/settings/services/ServicesDashboard.js
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -65,7 +65,7 @@ export default @observer class ServicesDashboard extends Component {
65 65
66 static defaultProps = { 66 static defaultProps = {
67 searchNeedle: '', 67 searchNeedle: '',
68 } 68 };
69 69
70 static contextTypes = { 70 static contextTypes = {
71 intl: intlShape, 71 intl: intlShape,
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index a92e559f3..8429d0ecb 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -170,6 +170,7 @@ export default @observer class EditSettingsForm extends Component {
170 <Select field={form.$('locale')} showLabel={false} /> 170 <Select field={form.$('locale')} showLabel={false} />
171 <PremiumFeatureContainer 171 <PremiumFeatureContainer
172 condition={isSpellcheckerPremiumFeature} 172 condition={isSpellcheckerPremiumFeature}
173 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }}
173 > 174 >
174 <Fragment> 175 <Fragment>
175 <Toggle 176 <Toggle
diff --git a/src/components/ui/AppLoader/index.js b/src/components/ui/AppLoader/index.js
index 61053f6d1..b0c7fed7b 100644
--- a/src/components/ui/AppLoader/index.js
+++ b/src/components/ui/AppLoader/index.js
@@ -23,11 +23,11 @@ export default @injectSheet(styles) @withTheme class AppLoader extends Component
23 static propTypes = { 23 static propTypes = {
24 classes: PropTypes.object.isRequired, 24 classes: PropTypes.object.isRequired,
25 theme: PropTypes.object.isRequired, 25 theme: PropTypes.object.isRequired,
26 } 26 };
27 27
28 state = { 28 state = {
29 step: 0, 29 step: 0,
30 } 30 };
31 31
32 interval = null; 32 interval = null;
33 33
diff --git a/src/components/ui/FullscreenLoader/index.js b/src/components/ui/FullscreenLoader/index.js
index 6ecf4d395..06dab1eb6 100644
--- a/src/components/ui/FullscreenLoader/index.js
+++ b/src/components/ui/FullscreenLoader/index.js
@@ -16,13 +16,13 @@ export default @observer @withTheme @injectSheet(styles) class FullscreenLoader
16 theme: PropTypes.object.isRequired, 16 theme: PropTypes.object.isRequired,
17 spinnerColor: PropTypes.string, 17 spinnerColor: PropTypes.string,
18 children: PropTypes.node, 18 children: PropTypes.node,
19 } 19 };
20 20
21 static defaultProps = { 21 static defaultProps = {
22 className: null, 22 className: null,
23 spinnerColor: null, 23 spinnerColor: null,
24 children: null, 24 children: null,
25 } 25 };
26 26
27 render() { 27 render() {
28 const { 28 const {
diff --git a/src/components/ui/Infobox.js b/src/components/ui/Infobox.js
index a33c6474a..0917ee9f0 100644
--- a/src/components/ui/Infobox.js
+++ b/src/components/ui/Infobox.js
@@ -13,6 +13,8 @@ export default @observer class Infobox extends Component {
13 ctaLabel: PropTypes.string, 13 ctaLabel: PropTypes.string,
14 ctaLoading: PropTypes.bool, 14 ctaLoading: PropTypes.bool,
15 dismissable: PropTypes.bool, 15 dismissable: PropTypes.bool,
16 onDismiss: PropTypes.func,
17 onSeen: PropTypes.func,
16 }; 18 };
17 19
18 static defaultProps = { 20 static defaultProps = {
@@ -22,12 +24,19 @@ export default @observer class Infobox extends Component {
22 ctaOnClick: () => null, 24 ctaOnClick: () => null,
23 ctaLabel: '', 25 ctaLabel: '',
24 ctaLoading: false, 26 ctaLoading: false,
27 onDismiss: () => null,
28 onSeen: () => null,
25 }; 29 };
26 30
27 state = { 31 state = {
28 dismissed: false, 32 dismissed: false,
29 }; 33 };
30 34
35 componentDidMount() {
36 const { onSeen } = this.props;
37 if (onSeen) onSeen();
38 }
39
31 render() { 40 render() {
32 const { 41 const {
33 children, 42 children,
@@ -37,6 +46,7 @@ export default @observer class Infobox extends Component {
37 ctaLoading, 46 ctaLoading,
38 ctaOnClick, 47 ctaOnClick,
39 dismissable, 48 dismissable,
49 onDismiss,
40 } = this.props; 50 } = this.props;
41 51
42 if (this.state.dismissed) { 52 if (this.state.dismissed) {
@@ -76,9 +86,10 @@ export default @observer class Infobox extends Component {
76 {dismissable && ( 86 {dismissable && (
77 <button 87 <button
78 type="button" 88 type="button"
79 onClick={() => this.setState({ 89 onClick={() => {
80 dismissed: true, 90 this.setState({ dismissed: true });
81 })} 91 if (onDismiss) onDismiss();
92 }}
82 className="infobox__delete mdi mdi-close" 93 className="infobox__delete mdi mdi-close"
83 /> 94 />
84 )} 95 )}
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js
index 67cd6af0b..3c1e0fac3 100644
--- a/src/components/ui/PremiumFeatureContainer/index.js
+++ b/src/components/ui/PremiumFeatureContainer/index.js
@@ -9,6 +9,7 @@ import { oneOrManyChildElements } from '../../../prop-types';
9import UserStore from '../../../stores/UserStore'; 9import UserStore from '../../../stores/UserStore';
10 10
11import styles from './styles'; 11import styles from './styles';
12import { gaEvent } from '../../../lib/analytics';
12 13
13const messages = defineMessages({ 14const messages = defineMessages({
14 action: { 15 action: {
@@ -17,14 +18,21 @@ const messages = defineMessages({
17 }, 18 },
18}); 19});
19 20
20export default @inject('stores', 'actions') @injectSheet(styles) @observer class PremiumFeatureContainer extends Component { 21@inject('stores', 'actions') @injectSheet(styles) @observer
22class PremiumFeatureContainer extends Component {
21 static propTypes = { 23 static propTypes = {
22 classes: PropTypes.object.isRequired, 24 classes: PropTypes.object.isRequired,
23 condition: PropTypes.bool, 25 condition: PropTypes.bool,
26 gaEventInfo: PropTypes.shape({
27 category: PropTypes.string.isRequired,
28 event: PropTypes.string.isRequired,
29 label: PropTypes.string,
30 }),
24 }; 31 };
25 32
26 static defaultProps = { 33 static defaultProps = {
27 condition: true, 34 condition: true,
35 gaEventInfo: null,
28 }; 36 };
29 37
30 static contextTypes = { 38 static contextTypes = {
@@ -38,6 +46,7 @@ export default @inject('stores', 'actions') @injectSheet(styles) @observer class
38 actions, 46 actions,
39 condition, 47 condition,
40 stores, 48 stores,
49 gaEventInfo,
41 } = this.props; 50 } = this.props;
42 51
43 const { intl } = this.context; 52 const { intl } = this.context;
@@ -49,7 +58,13 @@ export default @inject('stores', 'actions') @injectSheet(styles) @observer class
49 <button 58 <button
50 className={classes.actionButton} 59 className={classes.actionButton}
51 type="button" 60 type="button"
52 onClick={() => actions.ui.openSettings({ path: 'user' })} 61 onClick={() => {
62 actions.ui.openSettings({ path: 'user' });
63 if (gaEventInfo) {
64 const { category, event, label } = gaEventInfo;
65 gaEvent(category, event, label);
66 }
67 }}
53 > 68 >
54 {intl.formatMessage(messages.action)} 69 {intl.formatMessage(messages.action)}
55 </button> 70 </button>
@@ -73,3 +88,5 @@ PremiumFeatureContainer.wrappedComponent.propTypes = {
73 }).isRequired, 88 }).isRequired,
74 }).isRequired, 89 }).isRequired,
75}; 90};
91
92export default PremiumFeatureContainer;
diff --git a/src/components/ui/PremiumFeatureContainer/styles.js b/src/components/ui/PremiumFeatureContainer/styles.js
index 81d6666c6..41881e044 100644
--- a/src/components/ui/PremiumFeatureContainer/styles.js
+++ b/src/components/ui/PremiumFeatureContainer/styles.js
@@ -6,6 +6,7 @@ export default theme => ({
6 padding: 20, 6 padding: 20,
7 'border-radius': theme.borderRadius, 7 'border-radius': theme.borderRadius,
8 pointerEvents: 'none', 8 pointerEvents: 'none',
9 height: 'auto',
9 }, 10 },
10 titleContainer: { 11 titleContainer: {
11 display: 'flex', 12 display: 'flex',
@@ -19,14 +20,14 @@ export default theme => ({
19 color: theme.colorSubscriptionContainerActionButtonColor, 20 color: theme.colorSubscriptionContainerActionButtonColor,
20 'margin-left': 'auto', 21 'margin-left': 'auto',
21 'border-radius': theme.borderRadiusSmall, 22 'border-radius': theme.borderRadiusSmall,
22 padding: [2, 4], 23 padding: [4, 8],
23 'font-size': 12, 24 'font-size': 12,
24 pointerEvents: 'initial', 25 pointerEvents: 'initial',
25 }, 26 },
26 content: { 27 content: {
27 opacity: 0.5, 28 opacity: 0.5,
28 'margin-top': 20, 29 'margin-top': 20,
29 '& :last-child': { 30 '& > :last-child': {
30 'margin-bottom': 0, 31 'margin-bottom': 0,
31 }, 32 },
32 }, 33 },
diff --git a/src/components/ui/ServiceIcon.js b/src/components/ui/ServiceIcon.js
new file mode 100644
index 000000000..0b9155a4e
--- /dev/null
+++ b/src/components/ui/ServiceIcon.js
@@ -0,0 +1,67 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import classnames from 'classnames';
6
7import ServiceModel from '../../models/Service';
8
9const styles = theme => ({
10 root: {
11 height: 'auto',
12 },
13 icon: {
14 width: theme.serviceIcon.width,
15 },
16 isCustomIcon: {
17 width: theme.serviceIcon.isCustom.width,
18 border: theme.serviceIcon.isCustom.border,
19 borderRadius: theme.serviceIcon.isCustom.borderRadius,
20 },
21 isDisabled: {
22 filter: 'grayscale(100%)',
23 opacity: '.5',
24 },
25});
26
27@injectSheet(styles) @observer
28class ServiceIcon extends Component {
29 static propTypes = {
30 classes: PropTypes.object.isRequired,
31 service: PropTypes.instanceOf(ServiceModel).isRequired,
32 className: PropTypes.string,
33 };
34
35 static defaultProps = {
36 className: '',
37 };
38
39 render() {
40 const {
41 classes,
42 className,
43 service,
44 } = this.props;
45
46 return (
47 <div
48 className={classnames([
49 classes.root,
50 className,
51 ])}
52 >
53 <img
54 src={service.icon}
55 className={classnames([
56 classes.icon,
57 service.isEnabled ? null : classes.isDisabled,
58 service.hasCustomIcon ? classes.isCustomIcon : null,
59 ])}
60 alt=""
61 />
62 </div>
63 );
64 }
65}
66
67export default ServiceIcon;
diff --git a/src/components/ui/WebviewLoader/index.js b/src/components/ui/WebviewLoader/index.js
index 3a3dbbe49..58b6b6f1b 100644
--- a/src/components/ui/WebviewLoader/index.js
+++ b/src/components/ui/WebviewLoader/index.js
@@ -2,23 +2,35 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss'; 4import injectSheet from 'react-jss';
5import { defineMessages, intlShape } from 'react-intl';
5 6
6import FullscreenLoader from '../FullscreenLoader'; 7import FullscreenLoader from '../FullscreenLoader';
7
8import styles from './styles'; 8import styles from './styles';
9 9
10const messages = defineMessages({
11 loading: {
12 id: 'service.webviewLoader.loading',
13 defaultMessage: '!!!Loading',
14 },
15});
16
10export default @observer @injectSheet(styles) class WebviewLoader extends Component { 17export default @observer @injectSheet(styles) class WebviewLoader extends Component {
11 static propTypes = { 18 static propTypes = {
12 name: PropTypes.string.isRequired, 19 name: PropTypes.string.isRequired,
13 classes: PropTypes.object.isRequired, 20 classes: PropTypes.object.isRequired,
14 } 21 };
22
23 static contextTypes = {
24 intl: intlShape,
25 };
15 26
16 render() { 27 render() {
17 const { classes, name } = this.props; 28 const { classes, name } = this.props;
29 const { intl } = this.context;
18 return ( 30 return (
19 <FullscreenLoader 31 <FullscreenLoader
20 className={classes.component} 32 className={classes.component}
21 title={`Loading ${name}`} 33 title={`${intl.formatMessage(messages.loading)} ${name}`}
22 /> 34 />
23 ); 35 );
24 } 36 }
diff --git a/src/config.js b/src/config.js
index 479572edb..242675762 100644
--- a/src/config.js
+++ b/src/config.js
@@ -41,6 +41,8 @@ export const DEFAULT_FEATURES_CONFIG = {
41 }, 41 },
42 isServiceProxyEnabled: false, 42 isServiceProxyEnabled: false,
43 isServiceProxyPremiumFeature: true, 43 isServiceProxyPremiumFeature: true,
44 isWorkspacePremiumFeature: true,
45 isWorkspaceEnabled: false,
44}; 46};
45 47
46export const DEFAULT_WINDOW_OPTIONS = { 48export const DEFAULT_WINDOW_OPTIONS = {
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index 5a05ce431..2d855c78f 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -20,6 +20,9 @@ import Services from '../../components/services/content/Services';
20import AppLoader from '../../components/ui/AppLoader'; 20import AppLoader from '../../components/ui/AppLoader';
21 21
22import { state as delayAppState } from '../../features/delayApp'; 22import { state as delayAppState } from '../../features/delayApp';
23import { workspaceActions } from '../../features/workspaces/actions';
24import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer';
25import { workspaceStore } from '../../features/workspaces';
23 26
24export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { 27export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component {
25 static defaultProps = { 28 static defaultProps = {
@@ -82,6 +85,15 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
82 ); 85 );
83 } 86 }
84 87
88 const workspacesDrawer = (
89 <WorkspaceDrawer
90 getServicesForWorkspace={workspace => (
91 workspace ? workspaceStore.getWorkspaceServices(workspace).map(s => s.name) : services.all.map(s => s.name)
92 )}
93 onUpgradeAccountClick={() => openSettings({ path: 'user' })}
94 />
95 );
96
85 const sidebar = ( 97 const sidebar = (
86 <Sidebar 98 <Sidebar
87 services={services.allDisplayed} 99 services={services.allDisplayed}
@@ -96,6 +108,8 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
96 deleteService={deleteService} 108 deleteService={deleteService}
97 updateService={updateService} 109 updateService={updateService}
98 toggleMuteApp={toggleMuteApp} 110 toggleMuteApp={toggleMuteApp}
111 toggleWorkspaceDrawer={workspaceActions.toggleWorkspaceDrawer}
112 isWorkspaceDrawerOpen={workspaceStore.isWorkspaceDrawerOpen}
99 showMessageBadgeWhenMutedSetting={settings.all.app.showMessageBadgeWhenMuted} 113 showMessageBadgeWhenMutedSetting={settings.all.app.showMessageBadgeWhenMuted}
100 showMessageBadgesEvenWhenMuted={ui.showMessageBadgesEvenWhenMuted} 114 showMessageBadgesEvenWhenMuted={ui.showMessageBadgesEvenWhenMuted}
101 /> 115 />
@@ -122,6 +136,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
122 showServicesUpdatedInfoBar={ui.showServicesUpdatedInfoBar} 136 showServicesUpdatedInfoBar={ui.showServicesUpdatedInfoBar}
123 appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED} 137 appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED}
124 sidebar={sidebar} 138 sidebar={sidebar}
139 workspacesDrawer={workspacesDrawer}
125 services={servicesContainer} 140 services={servicesContainer}
126 news={news.latest} 141 news={news.latest}
127 removeNewsItem={hide} 142 removeNewsItem={hide}
diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js
index 6d9e0ee77..663b9e2e4 100644
--- a/src/containers/settings/SettingsWindow.js
+++ b/src/containers/settings/SettingsWindow.js
@@ -7,6 +7,7 @@ import ServicesStore from '../../stores/ServicesStore';
7import Layout from '../../components/settings/SettingsLayout'; 7import Layout from '../../components/settings/SettingsLayout';
8import Navigation from '../../components/settings/navigation/SettingsNavigation'; 8import Navigation from '../../components/settings/navigation/SettingsNavigation';
9import ErrorBoundary from '../../components/util/ErrorBoundary'; 9import ErrorBoundary from '../../components/util/ErrorBoundary';
10import { workspaceStore } from '../../features/workspaces';
10 11
11export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { 12export default @inject('stores', 'actions') @observer class SettingsContainer extends Component {
12 render() { 13 render() {
@@ -16,6 +17,7 @@ export default @inject('stores', 'actions') @observer class SettingsContainer ex
16 const navigation = ( 17 const navigation = (
17 <Navigation 18 <Navigation
18 serviceCount={stores.services.all.length} 19 serviceCount={stores.services.all.length}
20 workspaceCount={workspaceStore.workspaces.length}
19 /> 21 />
20 ); 22 );
21 23
diff --git a/src/environment.js b/src/environment.js
index 73b1c7ab2..d67fd6adb 100644
--- a/src/environment.js
+++ b/src/environment.js
@@ -28,3 +28,4 @@ if (!isDevMode || (isDevMode && useLiveAPI)) {
28} 28}
29 29
30export const API = api; 30export const API = api;
31export const API_VERSION = 'v1';
diff --git a/src/features/delayApp/Component.js b/src/features/delayApp/Component.js
index ff84510e8..ff0f1f2f8 100644
--- a/src/features/delayApp/Component.js
+++ b/src/features/delayApp/Component.js
@@ -38,7 +38,7 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp
38 38
39 state = { 39 state = {
40 countdown: config.delayDuration, 40 countdown: config.delayDuration,
41 } 41 };
42 42
43 countdownInterval = null; 43 countdownInterval = null;
44 44
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js
index abc8274cf..67f0fc5e6 100644
--- a/src/features/delayApp/index.js
+++ b/src/features/delayApp/index.js
@@ -55,7 +55,7 @@ export default function init(stores) {
55 55
56 setVisibility(true); 56 setVisibility(true);
57 gaPage('/delayApp'); 57 gaPage('/delayApp');
58 gaEvent('delayApp', 'show', 'Delay App Feature'); 58 gaEvent('DelayApp', 'show', 'Delay App Feature');
59 59
60 timeLastDelay = moment(); 60 timeLastDelay = moment();
61 shownAfterLaunch = true; 61 shownAfterLaunch = true;
diff --git a/src/features/utils/FeatureStore.js b/src/features/utils/FeatureStore.js
new file mode 100644
index 000000000..66b66a104
--- /dev/null
+++ b/src/features/utils/FeatureStore.js
@@ -0,0 +1,21 @@
1import Reaction from '../../stores/lib/Reaction';
2
3export class FeatureStore {
4 _actions = null;
5
6 _reactions = null;
7
8 _listenToActions(actions) {
9 if (this._actions) this._actions.forEach(a => a[0].off(a[1]));
10 this._actions = [];
11 actions.forEach(a => this._actions.push(a));
12 this._actions.forEach(a => a[0].listen(a[1]));
13 }
14
15 _startReactions(reactions) {
16 if (this._reactions) this._reactions.forEach(r => r.stop());
17 this._reactions = [];
18 reactions.forEach(r => this._reactions.push(new Reaction(r)));
19 this._reactions.forEach(r => r.start());
20 }
21}
diff --git a/src/features/workspaces/actions.js b/src/features/workspaces/actions.js
new file mode 100644
index 000000000..a85f8f57f
--- /dev/null
+++ b/src/features/workspaces/actions.js
@@ -0,0 +1,26 @@
1import PropTypes from 'prop-types';
2import Workspace from './models/Workspace';
3import { createActionsFromDefinitions } from '../../actions/lib/actions';
4
5export const workspaceActions = createActionsFromDefinitions({
6 edit: {
7 workspace: PropTypes.instanceOf(Workspace).isRequired,
8 },
9 create: {
10 name: PropTypes.string.isRequired,
11 },
12 delete: {
13 workspace: PropTypes.instanceOf(Workspace).isRequired,
14 },
15 update: {
16 workspace: PropTypes.instanceOf(Workspace).isRequired,
17 },
18 activate: {
19 workspace: PropTypes.instanceOf(Workspace).isRequired,
20 },
21 deactivate: {},
22 toggleWorkspaceDrawer: {},
23 openWorkspaceSettings: {},
24}, PropTypes.checkPropTypes);
25
26export default workspaceActions;
diff --git a/src/features/workspaces/api.js b/src/features/workspaces/api.js
new file mode 100644
index 000000000..0ec20c9ea
--- /dev/null
+++ b/src/features/workspaces/api.js
@@ -0,0 +1,66 @@
1import { pick } from 'lodash';
2import { sendAuthRequest } from '../../api/utils/auth';
3import { API, API_VERSION } from '../../environment';
4import Request from '../../stores/lib/Request';
5import Workspace from './models/Workspace';
6
7const debug = require('debug')('Franz:feature:workspaces:api');
8
9export const workspaceApi = {
10 getUserWorkspaces: async () => {
11 const url = `${API}/${API_VERSION}/workspace`;
12 debug('getUserWorkspaces GET', url);
13 const result = await sendAuthRequest(url, { method: 'GET' });
14 debug('getUserWorkspaces RESULT', result);
15 if (!result.ok) throw result;
16 const workspaces = await result.json();
17 return workspaces.map(data => new Workspace(data));
18 },
19
20 createWorkspace: async (name) => {
21 const url = `${API}/${API_VERSION}/workspace`;
22 const options = {
23 method: 'POST',
24 body: JSON.stringify({ name }),
25 };
26 debug('createWorkspace POST', url, options);
27 const result = await sendAuthRequest(url, options);
28 debug('createWorkspace RESULT', result);
29 if (!result.ok) throw result;
30 return new Workspace(await result.json());
31 },
32
33 deleteWorkspace: async (workspace) => {
34 const url = `${API}/${API_VERSION}/workspace/${workspace.id}`;
35 debug('deleteWorkspace DELETE', url);
36 const result = await sendAuthRequest(url, { method: 'DELETE' });
37 debug('deleteWorkspace RESULT', result);
38 if (!result.ok) throw result;
39 return true;
40 },
41
42 updateWorkspace: async (workspace) => {
43 const url = `${API}/${API_VERSION}/workspace/${workspace.id}`;
44 const options = {
45 method: 'PUT',
46 body: JSON.stringify(pick(workspace, ['name', 'services'])),
47 };
48 debug('updateWorkspace UPDATE', url, options);
49 const result = await sendAuthRequest(url, options);
50 debug('updateWorkspace RESULT', result);
51 if (!result.ok) throw result;
52 return new Workspace(await result.json());
53 },
54};
55
56export const getUserWorkspacesRequest = new Request(workspaceApi, 'getUserWorkspaces');
57export const createWorkspaceRequest = new Request(workspaceApi, 'createWorkspace');
58export const deleteWorkspaceRequest = new Request(workspaceApi, 'deleteWorkspace');
59export const updateWorkspaceRequest = new Request(workspaceApi, 'updateWorkspace');
60
61export const resetApiRequests = () => {
62 getUserWorkspacesRequest.reset();
63 createWorkspaceRequest.reset();
64 deleteWorkspaceRequest.reset();
65 updateWorkspaceRequest.reset();
66};
diff --git a/src/features/workspaces/components/CreateWorkspaceForm.js b/src/features/workspaces/components/CreateWorkspaceForm.js
new file mode 100644
index 000000000..2c00ea63c
--- /dev/null
+++ b/src/features/workspaces/components/CreateWorkspaceForm.js
@@ -0,0 +1,100 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import { Input, Button } from '@meetfranz/forms';
6import injectSheet from 'react-jss';
7import Form from '../../../lib/Form';
8import { required } from '../../../helpers/validation-helpers';
9import { gaEvent } from '../../../lib/analytics';
10import { GA_CATEGORY_WORKSPACES } from '../index';
11
12const messages = defineMessages({
13 submitButton: {
14 id: 'settings.workspace.add.form.submitButton',
15 defaultMessage: '!!!Create workspace',
16 },
17 name: {
18 id: 'settings.workspace.add.form.name',
19 defaultMessage: '!!!Name',
20 },
21});
22
23const styles = () => ({
24 form: {
25 display: 'flex',
26 },
27 input: {
28 flexGrow: 1,
29 marginRight: '10px',
30 },
31 submitButton: {
32 height: 'inherit',
33 },
34});
35
36@injectSheet(styles) @observer
37class CreateWorkspaceForm extends Component {
38 static contextTypes = {
39 intl: intlShape,
40 };
41
42 static propTypes = {
43 classes: PropTypes.object.isRequired,
44 isSubmitting: PropTypes.bool.isRequired,
45 onSubmit: PropTypes.func.isRequired,
46 };
47
48 form = (() => {
49 const { intl } = this.context;
50 return new Form({
51 fields: {
52 name: {
53 label: intl.formatMessage(messages.name),
54 placeholder: intl.formatMessage(messages.name),
55 value: '',
56 validators: [required],
57 },
58 },
59 });
60 })();
61
62 submitForm() {
63 const { form } = this;
64 form.submit({
65 onSuccess: async (f) => {
66 const { onSubmit } = this.props;
67 const values = f.values();
68 onSubmit(values);
69 gaEvent(GA_CATEGORY_WORKSPACES, 'create', values.name);
70 },
71 });
72 }
73
74 render() {
75 const { intl } = this.context;
76 const { classes, isSubmitting } = this.props;
77 const { form } = this;
78 return (
79 <div className={classes.form}>
80 <Input
81 className={classes.input}
82 {...form.$('name').bind()}
83 showLabel={false}
84 onEnterKey={this.submitForm.bind(this, form)}
85 focus
86 />
87 <Button
88 className={classes.submitButton}
89 type="submit"
90 label={intl.formatMessage(messages.submitButton)}
91 onClick={this.submitForm.bind(this, form)}
92 busy={isSubmitting}
93 buttonType={isSubmitting ? 'secondary' : 'primary'}
94 />
95 </div>
96 );
97 }
98}
99
100export default CreateWorkspaceForm;
diff --git a/src/features/workspaces/components/EditWorkspaceForm.js b/src/features/workspaces/components/EditWorkspaceForm.js
new file mode 100644
index 000000000..bba4485ff
--- /dev/null
+++ b/src/features/workspaces/components/EditWorkspaceForm.js
@@ -0,0 +1,189 @@
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';
6import { Input, Button } from '@meetfranz/forms';
7import injectSheet from 'react-jss';
8
9import Workspace from '../models/Workspace';
10import Service from '../../../models/Service';
11import Form from '../../../lib/Form';
12import { required } from '../../../helpers/validation-helpers';
13import WorkspaceServiceListItem from './WorkspaceServiceListItem';
14import Request from '../../../stores/lib/Request';
15import { gaEvent } from '../../../lib/analytics';
16import { GA_CATEGORY_WORKSPACES } from '../index';
17
18const messages = defineMessages({
19 buttonDelete: {
20 id: 'settings.workspace.form.buttonDelete',
21 defaultMessage: '!!!Delete workspace',
22 },
23 buttonSave: {
24 id: 'settings.workspace.form.buttonSave',
25 defaultMessage: '!!!Save workspace',
26 },
27 name: {
28 id: 'settings.workspace.form.name',
29 defaultMessage: '!!!Name',
30 },
31 yourWorkspaces: {
32 id: 'settings.workspace.form.yourWorkspaces',
33 defaultMessage: '!!!Your workspaces',
34 },
35 servicesInWorkspaceHeadline: {
36 id: 'settings.workspace.form.servicesInWorkspaceHeadline',
37 defaultMessage: '!!!Services in this Workspace',
38 },
39});
40
41const styles = () => ({
42 nameInput: {
43 height: 'auto',
44 },
45 serviceList: {
46 height: 'auto',
47 },
48});
49
50@injectSheet(styles) @observer
51class EditWorkspaceForm extends Component {
52 static contextTypes = {
53 intl: intlShape,
54 };
55
56 static propTypes = {
57 classes: PropTypes.object.isRequired,
58 onDelete: PropTypes.func.isRequired,
59 onSave: PropTypes.func.isRequired,
60 services: PropTypes.arrayOf(PropTypes.instanceOf(Service)).isRequired,
61 workspace: PropTypes.instanceOf(Workspace).isRequired,
62 updateWorkspaceRequest: PropTypes.instanceOf(Request).isRequired,
63 deleteWorkspaceRequest: PropTypes.instanceOf(Request).isRequired,
64 };
65
66 form = this.prepareWorkspaceForm(this.props.workspace);
67
68 componentWillReceiveProps(nextProps) {
69 const { workspace } = this.props;
70 if (workspace.id !== nextProps.workspace.id) {
71 this.form = this.prepareWorkspaceForm(nextProps.workspace);
72 }
73 }
74
75 prepareWorkspaceForm(workspace) {
76 const { intl } = this.context;
77 return new Form({
78 fields: {
79 name: {
80 label: intl.formatMessage(messages.name),
81 placeholder: intl.formatMessage(messages.name),
82 value: workspace.name,
83 validators: [required],
84 },
85 services: {
86 value: workspace.services.slice(),
87 },
88 },
89 });
90 }
91
92 save(form) {
93 form.submit({
94 onSuccess: async (f) => {
95 const { onSave } = this.props;
96 const values = f.values();
97 onSave(values);
98 gaEvent(GA_CATEGORY_WORKSPACES, 'save');
99 },
100 onError: async () => {},
101 });
102 }
103
104 delete() {
105 const { onDelete } = this.props;
106 onDelete();
107 gaEvent(GA_CATEGORY_WORKSPACES, 'delete');
108 }
109
110 toggleService(service) {
111 const servicesField = this.form.$('services');
112 const serviceIds = servicesField.value;
113 if (serviceIds.includes(service.id)) {
114 serviceIds.splice(serviceIds.indexOf(service.id), 1);
115 } else {
116 serviceIds.push(service.id);
117 }
118 servicesField.set(serviceIds);
119 }
120
121 render() {
122 const { intl } = this.context;
123 const {
124 classes,
125 workspace,
126 services,
127 deleteWorkspaceRequest,
128 updateWorkspaceRequest,
129 } = this.props;
130 const { form } = this;
131 const workspaceServices = form.$('services').value;
132 const isDeleting = deleteWorkspaceRequest.isExecuting;
133 const isSaving = updateWorkspaceRequest.isExecuting;
134 return (
135 <div className="settings__main">
136 <div className="settings__header">
137 <span className="settings__header-item">
138 <Link to="/settings/workspaces">
139 {intl.formatMessage(messages.yourWorkspaces)}
140 </Link>
141 </span>
142 <span className="separator" />
143 <span className="settings__header-item">
144 {workspace.name}
145 </span>
146 </div>
147 <div className="settings__body">
148 <div className={classes.nameInput}>
149 <Input {...form.$('name').bind()} />
150 </div>
151 <h2>{intl.formatMessage(messages.servicesInWorkspaceHeadline)}</h2>
152 <div className={classes.serviceList}>
153 {services.map(s => (
154 <WorkspaceServiceListItem
155 key={s.id}
156 service={s}
157 isInWorkspace={workspaceServices.includes(s.id)}
158 onToggle={() => this.toggleService(s)}
159 />
160 ))}
161 </div>
162 </div>
163 <div className="settings__controls">
164 {/* ===== Delete Button ===== */}
165 <Button
166 label={intl.formatMessage(messages.buttonDelete)}
167 loaded={false}
168 busy={isDeleting}
169 buttonType={isDeleting ? 'secondary' : 'danger'}
170 className="settings__delete-button"
171 disabled={isDeleting}
172 onClick={this.delete.bind(this)}
173 />
174 {/* ===== Save Button ===== */}
175 <Button
176 type="submit"
177 label={intl.formatMessage(messages.buttonSave)}
178 busy={isSaving}
179 buttonType={isSaving ? 'secondary' : 'primary'}
180 onClick={this.save.bind(this, form)}
181 disabled={isSaving}
182 />
183 </div>
184 </div>
185 );
186 }
187}
188
189export default EditWorkspaceForm;
diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js
new file mode 100644
index 000000000..684e50dd0
--- /dev/null
+++ b/src/features/workspaces/components/WorkspaceDrawer.js
@@ -0,0 +1,246 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import { defineMessages, FormattedHTMLMessage, intlShape } from 'react-intl';
6import { H1, Icon, ProBadge } from '@meetfranz/ui';
7import { Button } from '@meetfranz/forms/lib';
8import ReactTooltip from 'react-tooltip';
9
10import WorkspaceDrawerItem from './WorkspaceDrawerItem';
11import { workspaceActions } from '../actions';
12import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../index';
13import { gaEvent } from '../../../lib/analytics';
14
15const messages = defineMessages({
16 headline: {
17 id: 'workspaceDrawer.headline',
18 defaultMessage: '!!!Workspaces',
19 },
20 allServices: {
21 id: 'workspaceDrawer.allServices',
22 defaultMessage: '!!!All services',
23 },
24 workspacesSettingsTooltip: {
25 id: 'workspaceDrawer.workspacesSettingsTooltip',
26 defaultMessage: '!!!Workspaces settings',
27 },
28 workspaceFeatureInfo: {
29 id: 'workspaceDrawer.workspaceFeatureInfo',
30 defaultMessage: '!!!Info about workspace feature',
31 },
32 premiumCtaButtonLabel: {
33 id: 'workspaceDrawer.premiumCtaButtonLabel',
34 defaultMessage: '!!!Create your first workspace',
35 },
36 reactivatePremiumAccount: {
37 id: 'workspaceDrawer.reactivatePremiumAccountLabel',
38 defaultMessage: '!!!Reactivate premium account',
39 },
40 addNewWorkspaceLabel: {
41 id: 'workspaceDrawer.addNewWorkspaceLabel',
42 defaultMessage: '!!!add new workspace',
43 },
44 premiumFeatureBadge: {
45 id: 'workspaceDrawer.proFeatureBadge',
46 defaultMessage: '!!!Premium feature',
47 },
48});
49
50const styles = theme => ({
51 drawer: {
52 background: theme.workspaces.drawer.background,
53 width: `${theme.workspaces.drawer.width}px`,
54 },
55 headline: {
56 fontSize: '24px',
57 marginTop: '38px',
58 marginBottom: '25px',
59 marginLeft: theme.workspaces.drawer.padding,
60 },
61 headlineProBadge: {
62 marginRight: 15,
63 },
64 workspacesSettingsButton: {
65 float: 'right',
66 marginRight: theme.workspaces.drawer.padding,
67 marginTop: '2px',
68 },
69 workspacesSettingsButtonIcon: {
70 fill: theme.workspaces.drawer.buttons.color,
71 '&:hover': {
72 fill: theme.workspaces.drawer.buttons.hoverColor,
73 },
74 },
75 workspaces: {
76 height: 'auto',
77 },
78 premiumAnnouncement: {
79 padding: '20px',
80 paddingTop: '0',
81 height: 'auto',
82 },
83 premiumCtaButton: {
84 marginTop: '20px',
85 width: '100%',
86 color: 'white !important',
87 },
88 addNewWorkspaceLabel: {
89 height: 'auto',
90 color: theme.workspaces.drawer.buttons.color,
91 marginTop: 40,
92 textAlign: 'center',
93 '& > svg': {
94 fill: theme.workspaces.drawer.buttons.color,
95 },
96 '& > span': {
97 fontSize: '13px',
98 marginLeft: 10,
99 position: 'relative',
100 top: -3,
101 },
102 '&:hover': {
103 color: theme.workspaces.drawer.buttons.hoverColor,
104 '& > svg': {
105 fill: theme.workspaces.drawer.buttons.hoverColor,
106 },
107 },
108 },
109});
110
111@injectSheet(styles) @observer
112class WorkspaceDrawer extends Component {
113 static propTypes = {
114 classes: PropTypes.object.isRequired,
115 getServicesForWorkspace: PropTypes.func.isRequired,
116 onUpgradeAccountClick: PropTypes.func.isRequired,
117 };
118
119 static contextTypes = {
120 intl: intlShape,
121 };
122
123 componentDidMount() {
124 ReactTooltip.rebuild();
125 }
126
127 render() {
128 const {
129 classes,
130 getServicesForWorkspace,
131 onUpgradeAccountClick,
132 } = this.props;
133 const { intl } = this.context;
134 const {
135 activeWorkspace,
136 isSwitchingWorkspace,
137 nextWorkspace,
138 workspaces,
139 } = workspaceStore;
140 const actualWorkspace = isSwitchingWorkspace ? nextWorkspace : activeWorkspace;
141 return (
142 <div className={classes.drawer}>
143 <H1 className={classes.headline}>
144 {workspaceStore.isPremiumUpgradeRequired && (
145 <span
146 className={classes.headlineProBadge}
147 data-tip={`${intl.formatMessage(messages.premiumFeatureBadge)}`}
148 >
149 <ProBadge />
150 </span>
151 )}
152 {intl.formatMessage(messages.headline)}
153 <span
154 className={classes.workspacesSettingsButton}
155 onClick={() => {
156 workspaceActions.openWorkspaceSettings();
157 gaEvent(GA_CATEGORY_WORKSPACES, 'settings', 'drawerHeadline');
158 }}
159 data-tip={`${intl.formatMessage(messages.workspacesSettingsTooltip)}`}
160 >
161 <Icon
162 icon="mdiSettings"
163 size={1.5}
164 className={classes.workspacesSettingsButtonIcon}
165 />
166 </span>
167 </H1>
168 {workspaceStore.isPremiumUpgradeRequired ? (
169 <div className={classes.premiumAnnouncement}>
170 <FormattedHTMLMessage {...messages.workspaceFeatureInfo} />
171 {workspaceStore.userHasWorkspaces ? (
172 <Button
173 className={classes.premiumCtaButton}
174 buttonType="primary"
175 label={intl.formatMessage(messages.reactivatePremiumAccount)}
176 icon="mdiStar"
177 onClick={() => {
178 onUpgradeAccountClick();
179 gaEvent('User', 'upgrade', 'workspaceDrawer');
180 }}
181 />
182 ) : (
183 <Button
184 className={classes.premiumCtaButton}
185 buttonType="primary"
186 label={intl.formatMessage(messages.premiumCtaButtonLabel)}
187 icon="mdiPlusBox"
188 onClick={() => {
189 workspaceActions.openWorkspaceSettings();
190 gaEvent(GA_CATEGORY_WORKSPACES, 'add', 'drawerPremiumCta');
191 }}
192 />
193 )}
194 </div>
195 ) : (
196 <div className={classes.workspaces}>
197 <WorkspaceDrawerItem
198 name={intl.formatMessage(messages.allServices)}
199 onClick={() => {
200 workspaceActions.deactivate();
201 workspaceActions.toggleWorkspaceDrawer();
202 gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'drawer');
203 }}
204 services={getServicesForWorkspace(null)}
205 isActive={actualWorkspace == null}
206 />
207 {workspaces.map(workspace => (
208 <WorkspaceDrawerItem
209 key={workspace.id}
210 name={workspace.name}
211 isActive={actualWorkspace === workspace}
212 onClick={() => {
213 if (actualWorkspace === workspace) return;
214 workspaceActions.activate({ workspace });
215 workspaceActions.toggleWorkspaceDrawer();
216 gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'drawer');
217 }}
218 onContextMenuEditClick={() => workspaceActions.edit({ workspace })}
219 services={getServicesForWorkspace(workspace)}
220 />
221 ))}
222 <div
223 className={classes.addNewWorkspaceLabel}
224 onClick={() => {
225 workspaceActions.openWorkspaceSettings();
226 gaEvent(GA_CATEGORY_WORKSPACES, 'add', 'drawerAddLabel');
227 }}
228 >
229 <Icon
230 icon="mdiPlusBox"
231 size={1}
232 className={classes.workspacesSettingsButtonIcon}
233 />
234 <span>
235 {intl.formatMessage(messages.addNewWorkspaceLabel)}
236 </span>
237 </div>
238 </div>
239 )}
240 <ReactTooltip place="right" type="dark" effect="solid" />
241 </div>
242 );
243 }
244}
245
246export default WorkspaceDrawer;
diff --git a/src/features/workspaces/components/WorkspaceDrawerItem.js b/src/features/workspaces/components/WorkspaceDrawerItem.js
new file mode 100644
index 000000000..59a2144d3
--- /dev/null
+++ b/src/features/workspaces/components/WorkspaceDrawerItem.js
@@ -0,0 +1,137 @@
1import { remote } from 'electron';
2import React, { Component } from 'react';
3import PropTypes from 'prop-types';
4import { observer } from 'mobx-react';
5import injectSheet from 'react-jss';
6import classnames from 'classnames';
7import { defineMessages, intlShape } from 'react-intl';
8
9const { Menu } = remote;
10
11const messages = defineMessages({
12 noServicesAddedYet: {
13 id: 'workspaceDrawer.item.noServicesAddedYet',
14 defaultMessage: '!!!No services added yet',
15 },
16 contextMenuEdit: {
17 id: 'workspaceDrawer.item.contextMenuEdit',
18 defaultMessage: '!!!edit',
19 },
20});
21
22const styles = theme => ({
23 item: {
24 height: '67px',
25 padding: `15px ${theme.workspaces.drawer.padding}px`,
26 borderBottom: `1px solid ${theme.workspaces.drawer.listItem.border}`,
27 transition: 'background-color 300ms ease-out',
28 '&:first-child': {
29 borderTop: `1px solid ${theme.workspaces.drawer.listItem.border}`,
30 },
31 '&:hover': {
32 backgroundColor: theme.workspaces.drawer.listItem.hoverBackground,
33 },
34 },
35 isActiveItem: {
36 backgroundColor: theme.workspaces.drawer.listItem.activeBackground,
37 '&:hover': {
38 backgroundColor: theme.workspaces.drawer.listItem.activeBackground,
39 },
40 },
41 name: {
42 marginTop: '4px',
43 color: theme.workspaces.drawer.listItem.name.color,
44 },
45 activeName: {
46 color: theme.workspaces.drawer.listItem.name.activeColor,
47 },
48 services: {
49 display: 'block',
50 fontSize: '11px',
51 marginTop: '5px',
52 color: theme.workspaces.drawer.listItem.services.color,
53 whiteSpace: 'nowrap',
54 textOverflow: 'ellipsis',
55 overflow: 'hidden',
56 lineHeight: '15px',
57 },
58 activeServices: {
59 color: theme.workspaces.drawer.listItem.services.active,
60 },
61});
62
63@injectSheet(styles) @observer
64class WorkspaceDrawerItem extends Component {
65 static propTypes = {
66 classes: PropTypes.object.isRequired,
67 isActive: PropTypes.bool.isRequired,
68 name: PropTypes.string.isRequired,
69 onClick: PropTypes.func.isRequired,
70 services: PropTypes.arrayOf(PropTypes.string).isRequired,
71 onContextMenuEditClick: PropTypes.func,
72 };
73
74 static defaultProps = {
75 onContextMenuEditClick: null,
76 };
77
78 static contextTypes = {
79 intl: intlShape,
80 };
81
82 render() {
83 const {
84 classes,
85 isActive,
86 name,
87 onClick,
88 onContextMenuEditClick,
89 services,
90 } = this.props;
91 const { intl } = this.context;
92
93 const contextMenuTemplate = [{
94 label: name,
95 enabled: false,
96 }, {
97 type: 'separator',
98 }, {
99 label: intl.formatMessage(messages.contextMenuEdit),
100 click: onContextMenuEditClick,
101 }];
102
103 const contextMenu = Menu.buildFromTemplate(contextMenuTemplate);
104
105 return (
106 <div
107 className={classnames([
108 classes.item,
109 isActive ? classes.isActiveItem : null,
110 ])}
111 onClick={onClick}
112 onContextMenu={() => (
113 onContextMenuEditClick && contextMenu.popup(remote.getCurrentWindow())
114 )}
115 >
116 <span
117 className={classnames([
118 classes.name,
119 isActive ? classes.activeName : null,
120 ])}
121 >
122 {name}
123 </span>
124 <span
125 className={classnames([
126 classes.services,
127 isActive ? classes.activeServices : null,
128 ])}
129 >
130 {services.length ? services.join(', ') : intl.formatMessage(messages.noServicesAddedYet)}
131 </span>
132 </div>
133 );
134 }
135}
136
137export default WorkspaceDrawerItem;
diff --git a/src/features/workspaces/components/WorkspaceItem.js b/src/features/workspaces/components/WorkspaceItem.js
new file mode 100644
index 000000000..cc4b1a3ba
--- /dev/null
+++ b/src/features/workspaces/components/WorkspaceItem.js
@@ -0,0 +1,45 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { intlShape } from 'react-intl';
4import { observer } from 'mobx-react';
5import injectSheet from 'react-jss';
6
7import Workspace from '../models/Workspace';
8
9const styles = theme => ({
10 row: {
11 height: theme.workspaces.settings.listItems.height,
12 borderBottom: `1px solid ${theme.workspaces.settings.listItems.borderColor}`,
13 '&:hover': {
14 background: theme.workspaces.settings.listItems.hoverBgColor,
15 },
16 },
17 columnName: {},
18});
19
20@injectSheet(styles) @observer
21class WorkspaceItem extends Component {
22 static propTypes = {
23 classes: PropTypes.object.isRequired,
24 workspace: PropTypes.instanceOf(Workspace).isRequired,
25 onItemClick: PropTypes.func.isRequired,
26 };
27
28 static contextTypes = {
29 intl: intlShape,
30 };
31
32 render() {
33 const { classes, workspace, onItemClick } = this.props;
34
35 return (
36 <tr className={classes.row}>
37 <td onClick={() => onItemClick(workspace)}>
38 {workspace.name}
39 </td>
40 </tr>
41 );
42 }
43}
44
45export default WorkspaceItem;
diff --git a/src/features/workspaces/components/WorkspaceServiceListItem.js b/src/features/workspaces/components/WorkspaceServiceListItem.js
new file mode 100644
index 000000000..e05b21440
--- /dev/null
+++ b/src/features/workspaces/components/WorkspaceServiceListItem.js
@@ -0,0 +1,75 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import classnames from 'classnames';
6import { Toggle } from '@meetfranz/forms';
7
8import Service from '../../../models/Service';
9import ServiceIcon from '../../../components/ui/ServiceIcon';
10
11const styles = theme => ({
12 listItem: {
13 height: theme.workspaces.settings.listItems.height,
14 borderBottom: `1px solid ${theme.workspaces.settings.listItems.borderColor}`,
15 display: 'flex',
16 alignItems: 'center',
17 },
18 serviceIcon: {
19 padding: theme.workspaces.settings.listItems.padding,
20 },
21 toggle: {
22 height: 'auto',
23 margin: 0,
24 },
25 label: {
26 padding: theme.workspaces.settings.listItems.padding,
27 flexGrow: 1,
28 },
29 disabledLabel: {
30 color: theme.workspaces.settings.listItems.disabled.color,
31 },
32});
33
34@injectSheet(styles) @observer
35class WorkspaceServiceListItem extends Component {
36 static propTypes = {
37 classes: PropTypes.object.isRequired,
38 isInWorkspace: PropTypes.bool.isRequired,
39 onToggle: PropTypes.func.isRequired,
40 service: PropTypes.instanceOf(Service).isRequired,
41 };
42
43 render() {
44 const {
45 classes,
46 isInWorkspace,
47 onToggle,
48 service,
49 } = this.props;
50
51 return (
52 <div className={classes.listItem}>
53 <ServiceIcon
54 className={classes.serviceIcon}
55 service={service}
56 />
57 <span
58 className={classnames([
59 classes.label,
60 service.isEnabled ? null : classes.disabledLabel,
61 ])}
62 >
63 {service.name}
64 </span>
65 <Toggle
66 className={classes.toggle}
67 checked={isInWorkspace}
68 onChange={onToggle}
69 />
70 </div>
71 );
72 }
73}
74
75export default WorkspaceServiceListItem;
diff --git a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js
new file mode 100644
index 000000000..c4a800a7b
--- /dev/null
+++ b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js
@@ -0,0 +1,91 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import classnames from 'classnames';
6import { Loader } from '@meetfranz/ui';
7import { defineMessages, intlShape } from 'react-intl';
8
9import { workspaceStore } from '../index';
10
11const messages = defineMessages({
12 switchingTo: {
13 id: 'workspaces.switchingIndicator.switchingTo',
14 defaultMessage: '!!!Switching to',
15 },
16});
17
18const styles = theme => ({
19 wrapper: {
20 display: 'flex',
21 alignItems: 'flex-start',
22 position: 'absolute',
23 transition: 'width 0.5s ease',
24 width: '100%',
25 marginTop: '20px',
26 },
27 wrapperWhenDrawerIsOpen: {
28 width: `calc(100% - ${theme.workspaces.drawer.width}px)`,
29 },
30 component: {
31 background: 'rgba(20, 20, 20, 0.4)',
32 padding: '10px 20px',
33 display: 'flex',
34 width: 'auto',
35 height: 'auto',
36 margin: [0, 'auto'],
37 borderRadius: 6,
38 alignItems: 'center',
39 zIndex: 200,
40 },
41 spinner: {
42 width: 40,
43 height: 40,
44 marginRight: 10,
45 },
46 message: {
47 fontSize: 16,
48 whiteSpace: 'nowrap',
49 color: theme.colorAppLoaderSpinner,
50 },
51});
52
53@injectSheet(styles) @observer
54class WorkspaceSwitchingIndicator extends Component {
55 static propTypes = {
56 classes: PropTypes.object.isRequired,
57 theme: PropTypes.object.isRequired,
58 };
59
60 static contextTypes = {
61 intl: intlShape,
62 };
63
64 render() {
65 const { classes, theme } = this.props;
66 const { intl } = this.context;
67 const { isSwitchingWorkspace, isWorkspaceDrawerOpen, nextWorkspace } = workspaceStore;
68 if (!isSwitchingWorkspace) return null;
69 const nextWorkspaceName = nextWorkspace ? nextWorkspace.name : 'All services';
70 return (
71 <div
72 className={classnames([
73 classes.wrapper,
74 isWorkspaceDrawerOpen ? classes.wrapperWhenDrawerIsOpen : null,
75 ])}
76 >
77 <div className={classes.component}>
78 <Loader
79 className={classes.spinner}
80 color={theme.workspaces.switchingIndicator.spinnerColor}
81 />
82 <p className={classes.message}>
83 {`${intl.formatMessage(messages.switchingTo)} ${nextWorkspaceName}`}
84 </p>
85 </div>
86 </div>
87 );
88 }
89}
90
91export default WorkspaceSwitchingIndicator;
diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js
new file mode 100644
index 000000000..dd4381a15
--- /dev/null
+++ b/src/features/workspaces/components/WorkspacesDashboard.js
@@ -0,0 +1,195 @@
1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6import { Infobox } from '@meetfranz/ui';
7
8import Loader from '../../../components/ui/Loader';
9import WorkspaceItem from './WorkspaceItem';
10import CreateWorkspaceForm from './CreateWorkspaceForm';
11import Request from '../../../stores/lib/Request';
12import Appear from '../../../components/ui/effects/Appear';
13import { workspaceStore } from '../index';
14import PremiumFeatureContainer from '../../../components/ui/PremiumFeatureContainer';
15
16const messages = defineMessages({
17 headline: {
18 id: 'settings.workspaces.headline',
19 defaultMessage: '!!!Your workspaces',
20 },
21 noServicesAdded: {
22 id: 'settings.workspaces.noWorkspacesAdded',
23 defaultMessage: '!!!You haven\'t added any workspaces yet.',
24 },
25 workspacesRequestFailed: {
26 id: 'settings.workspaces.workspacesRequestFailed',
27 defaultMessage: '!!!Could not load your workspaces',
28 },
29 tryReloadWorkspaces: {
30 id: 'settings.workspaces.tryReloadWorkspaces',
31 defaultMessage: '!!!Try again',
32 },
33 updatedInfo: {
34 id: 'settings.workspaces.updatedInfo',
35 defaultMessage: '!!!Your changes have been saved',
36 },
37 deletedInfo: {
38 id: 'settings.workspaces.deletedInfo',
39 defaultMessage: '!!!Workspace has been deleted',
40 },
41 workspaceFeatureInfo: {
42 id: 'settings.workspaces.workspaceFeatureInfo',
43 defaultMessage: '!!!Info about workspace feature',
44 },
45 workspaceFeatureHeadline: {
46 id: 'settings.workspaces.workspaceFeatureHeadline',
47 defaultMessage: '!!!Less is More: Introducing Franz Workspaces',
48 },
49});
50
51const styles = theme => ({
52 table: {
53 width: '100%',
54 '& td': {
55 padding: '10px',
56 },
57 },
58 createForm: {
59 height: 'auto',
60 },
61 appear: {
62 height: 'auto',
63 },
64 premiumAnnouncement: {
65 padding: '20px',
66 backgroundColor: '#3498db',
67 marginLeft: '-20px',
68 marginBottom: '20px',
69 height: 'auto',
70 color: 'white',
71 borderRadius: theme.borderRadius,
72 },
73});
74
75@injectSheet(styles) @observer
76class WorkspacesDashboard extends Component {
77 static propTypes = {
78 classes: PropTypes.object.isRequired,
79 getUserWorkspacesRequest: PropTypes.instanceOf(Request).isRequired,
80 createWorkspaceRequest: PropTypes.instanceOf(Request).isRequired,
81 deleteWorkspaceRequest: PropTypes.instanceOf(Request).isRequired,
82 updateWorkspaceRequest: PropTypes.instanceOf(Request).isRequired,
83 onCreateWorkspaceSubmit: PropTypes.func.isRequired,
84 onWorkspaceClick: PropTypes.func.isRequired,
85 workspaces: MobxPropTypes.arrayOrObservableArray.isRequired,
86 };
87
88 static contextTypes = {
89 intl: intlShape,
90 };
91
92 render() {
93 const {
94 classes,
95 getUserWorkspacesRequest,
96 createWorkspaceRequest,
97 deleteWorkspaceRequest,
98 updateWorkspaceRequest,
99 onCreateWorkspaceSubmit,
100 onWorkspaceClick,
101 workspaces,
102 } = this.props;
103 const { intl } = this.context;
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
111 {/* ===== Workspace updated info ===== */}
112 {updateWorkspaceRequest.wasExecuted && updateWorkspaceRequest.result && (
113 <Appear className={classes.appear}>
114 <Infobox
115 type="success"
116 icon="mdiCheckboxMarkedCircleOutline"
117 dismissable
118 onUnmount={updateWorkspaceRequest.reset}
119 >
120 {intl.formatMessage(messages.updatedInfo)}
121 </Infobox>
122 </Appear>
123 )}
124
125 {/* ===== Workspace deleted info ===== */}
126 {deleteWorkspaceRequest.wasExecuted && deleteWorkspaceRequest.result && (
127 <Appear className={classes.appear}>
128 <Infobox
129 type="success"
130 icon="mdiCheckboxMarkedCircleOutline"
131 dismissable
132 onUnmount={deleteWorkspaceRequest.reset}
133 >
134 {intl.formatMessage(messages.deletedInfo)}
135 </Infobox>
136 </Appear>
137 )}
138
139 {workspaceStore.isPremiumUpgradeRequired && (
140 <div className={classes.premiumAnnouncement}>
141 <h2>{intl.formatMessage(messages.workspaceFeatureHeadline)}</h2>
142 <p>{intl.formatMessage(messages.workspaceFeatureInfo)}</p>
143 </div>
144 )}
145
146 <PremiumFeatureContainer
147 condition={workspaceStore.isPremiumFeature}
148 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'workspaces' }}
149 >
150 {/* ===== Create workspace form ===== */}
151 <div className={classes.createForm}>
152 <CreateWorkspaceForm
153 isSubmitting={createWorkspaceRequest.isExecuting}
154 onSubmit={onCreateWorkspaceSubmit}
155 />
156 </div>
157 {getUserWorkspacesRequest.isExecuting ? (
158 <Loader />
159 ) : (
160 <Fragment>
161 {/* ===== Workspace could not be loaded error ===== */}
162 {getUserWorkspacesRequest.error ? (
163 <Infobox
164 icon="alert"
165 type="danger"
166 ctaLabel={intl.formatMessage(messages.tryReloadWorkspaces)}
167 ctaLoading={getUserWorkspacesRequest.isExecuting}
168 ctaOnClick={getUserWorkspacesRequest.retry}
169 >
170 {intl.formatMessage(messages.workspacesRequestFailed)}
171 </Infobox>
172 ) : (
173 <table className={classes.table}>
174 {/* ===== Workspaces list ===== */}
175 <tbody>
176 {workspaces.map(workspace => (
177 <WorkspaceItem
178 key={workspace.id}
179 workspace={workspace}
180 onItemClick={w => onWorkspaceClick(w)}
181 />
182 ))}
183 </tbody>
184 </table>
185 )}
186 </Fragment>
187 )}
188 </PremiumFeatureContainer>
189 </div>
190 </div>
191 );
192 }
193}
194
195export default WorkspacesDashboard;
diff --git a/src/features/workspaces/containers/EditWorkspaceScreen.js b/src/features/workspaces/containers/EditWorkspaceScreen.js
new file mode 100644
index 000000000..248b40131
--- /dev/null
+++ b/src/features/workspaces/containers/EditWorkspaceScreen.js
@@ -0,0 +1,60 @@
1import React, { Component } from 'react';
2import { inject, observer } from 'mobx-react';
3import PropTypes from 'prop-types';
4
5import ErrorBoundary from '../../../components/util/ErrorBoundary';
6import EditWorkspaceForm from '../components/EditWorkspaceForm';
7import ServicesStore from '../../../stores/ServicesStore';
8import Workspace from '../models/Workspace';
9import { workspaceStore } from '../index';
10import { deleteWorkspaceRequest, updateWorkspaceRequest } from '../api';
11
12@inject('stores', 'actions') @observer
13class EditWorkspaceScreen extends Component {
14 static propTypes = {
15 actions: PropTypes.shape({
16 workspace: PropTypes.shape({
17 delete: PropTypes.func.isRequired,
18 }),
19 }).isRequired,
20 stores: PropTypes.shape({
21 services: PropTypes.instanceOf(ServicesStore).isRequired,
22 }).isRequired,
23 };
24
25 onDelete = () => {
26 const { workspaceBeingEdited } = workspaceStore;
27 const { actions } = this.props;
28 if (!workspaceBeingEdited) return null;
29 actions.workspaces.delete({ workspace: workspaceBeingEdited });
30 };
31
32 onSave = (values) => {
33 const { workspaceBeingEdited } = workspaceStore;
34 const { actions } = this.props;
35 const workspace = new Workspace(
36 Object.assign({}, workspaceBeingEdited, values),
37 );
38 actions.workspaces.update({ workspace });
39 };
40
41 render() {
42 const { workspaceBeingEdited } = workspaceStore;
43 const { stores } = this.props;
44 if (!workspaceBeingEdited) return null;
45 return (
46 <ErrorBoundary>
47 <EditWorkspaceForm
48 workspace={workspaceBeingEdited}
49 services={stores.services.all}
50 onDelete={this.onDelete}
51 onSave={this.onSave}
52 updateWorkspaceRequest={updateWorkspaceRequest}
53 deleteWorkspaceRequest={deleteWorkspaceRequest}
54 />
55 </ErrorBoundary>
56 );
57 }
58}
59
60export default EditWorkspaceScreen;
diff --git a/src/features/workspaces/containers/WorkspacesScreen.js b/src/features/workspaces/containers/WorkspacesScreen.js
new file mode 100644
index 000000000..2ab565fa1
--- /dev/null
+++ b/src/features/workspaces/containers/WorkspacesScreen.js
@@ -0,0 +1,42 @@
1import React, { Component } from 'react';
2import { inject, observer } from 'mobx-react';
3import PropTypes from 'prop-types';
4import WorkspacesDashboard from '../components/WorkspacesDashboard';
5import ErrorBoundary from '../../../components/util/ErrorBoundary';
6import { workspaceStore } from '../index';
7import {
8 createWorkspaceRequest,
9 deleteWorkspaceRequest,
10 getUserWorkspacesRequest,
11 updateWorkspaceRequest,
12} from '../api';
13
14@inject('actions') @observer
15class WorkspacesScreen extends Component {
16 static propTypes = {
17 actions: PropTypes.shape({
18 workspace: PropTypes.shape({
19 edit: PropTypes.func.isRequired,
20 }),
21 }).isRequired,
22 };
23
24 render() {
25 const { actions } = this.props;
26 return (
27 <ErrorBoundary>
28 <WorkspacesDashboard
29 workspaces={workspaceStore.workspaces}
30 getUserWorkspacesRequest={getUserWorkspacesRequest}
31 createWorkspaceRequest={createWorkspaceRequest}
32 deleteWorkspaceRequest={deleteWorkspaceRequest}
33 updateWorkspaceRequest={updateWorkspaceRequest}
34 onCreateWorkspaceSubmit={data => actions.workspaces.create(data)}
35 onWorkspaceClick={w => actions.workspaces.edit({ workspace: w })}
36 />
37 </ErrorBoundary>
38 );
39 }
40}
41
42export default WorkspacesScreen;
diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.js
new file mode 100644
index 000000000..ad9023b8b
--- /dev/null
+++ b/src/features/workspaces/index.js
@@ -0,0 +1,37 @@
1import { reaction } from 'mobx';
2import WorkspacesStore from './store';
3import { resetApiRequests } from './api';
4
5const debug = require('debug')('Franz:feature:workspaces');
6
7export const GA_CATEGORY_WORKSPACES = 'Workspaces';
8
9export const workspaceStore = new WorkspacesStore();
10
11export default function initWorkspaces(stores, actions) {
12 stores.workspaces = workspaceStore;
13 const { features } = stores;
14
15 // Toggle workspace feature
16 reaction(
17 () => features.features.isWorkspaceEnabled,
18 (isEnabled) => {
19 if (isEnabled && !workspaceStore.isFeatureActive) {
20 debug('Initializing `workspaces` feature');
21 workspaceStore.start(stores, actions);
22 } else if (workspaceStore.isFeatureActive) {
23 debug('Disabling `workspaces` feature');
24 workspaceStore.stop();
25 resetApiRequests();
26 }
27 },
28 {
29 fireImmediately: true,
30 },
31 );
32}
33
34export const WORKSPACES_ROUTES = {
35 ROOT: '/settings/workspaces',
36 EDIT: '/settings/workspaces/:action/:id',
37};
diff --git a/src/features/workspaces/models/Workspace.js b/src/features/workspaces/models/Workspace.js
new file mode 100644
index 000000000..6c73d7095
--- /dev/null
+++ b/src/features/workspaces/models/Workspace.js
@@ -0,0 +1,25 @@
1import { observable } from 'mobx';
2
3export default class Workspace {
4 id = null;
5
6 @observable name = null;
7
8 @observable order = null;
9
10 @observable services = [];
11
12 @observable userId = null;
13
14 constructor(data) {
15 if (!data.id) {
16 throw Error('Workspace requires Id');
17 }
18
19 this.id = data.id;
20 this.name = data.name;
21 this.order = data.order;
22 this.services.replace(data.services);
23 this.userId = data.userId;
24 }
25}
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js
new file mode 100644
index 000000000..ea601700e
--- /dev/null
+++ b/src/features/workspaces/store.js
@@ -0,0 +1,276 @@
1import {
2 computed,
3 observable,
4 action,
5} from 'mobx';
6import localStorage from 'mobx-localstorage';
7import { matchRoute } from '../../helpers/routing-helpers';
8import { workspaceActions } from './actions';
9import { FeatureStore } from '../utils/FeatureStore';
10import {
11 createWorkspaceRequest,
12 deleteWorkspaceRequest,
13 getUserWorkspacesRequest,
14 updateWorkspaceRequest,
15} from './api';
16import { WORKSPACES_ROUTES } from './index';
17
18const debug = require('debug')('Franz:feature:workspaces:store');
19
20export default class WorkspacesStore extends FeatureStore {
21 @observable isFeatureEnabled = false;
22
23 @observable isFeatureActive = false;
24
25 @observable isPremiumFeature = true;
26
27 @observable isPremiumUpgradeRequired = true;
28
29 @observable activeWorkspace = null;
30
31 @observable nextWorkspace = null;
32
33 @observable workspaceBeingEdited = null;
34
35 @observable isSwitchingWorkspace = false;
36
37 @observable isWorkspaceDrawerOpen = false;
38
39 @observable isSettingsRouteActive = null;
40
41 @computed get workspaces() {
42 if (!this.isFeatureActive) return [];
43 return getUserWorkspacesRequest.result || [];
44 }
45
46 @computed get settings() {
47 return localStorage.getItem('workspaces') || {};
48 }
49
50 @computed get userHasWorkspaces() {
51 return getUserWorkspacesRequest.wasExecuted && this.workspaces.length > 0;
52 }
53
54 start(stores, actions) {
55 debug('WorkspacesStore::start');
56 this.stores = stores;
57 this.actions = actions;
58
59 this._listenToActions([
60 [workspaceActions.edit, this._edit],
61 [workspaceActions.create, this._create],
62 [workspaceActions.delete, this._delete],
63 [workspaceActions.update, this._update],
64 [workspaceActions.activate, this._setActiveWorkspace],
65 [workspaceActions.deactivate, this._deactivateActiveWorkspace],
66 [workspaceActions.toggleWorkspaceDrawer, this._toggleWorkspaceDrawer],
67 [workspaceActions.openWorkspaceSettings, this._openWorkspaceSettings],
68 ]);
69
70 this._startReactions([
71 this._setWorkspaceBeingEditedReaction,
72 this._setActiveServiceOnWorkspaceSwitchReaction,
73 this._setFeatureEnabledReaction,
74 this._setIsPremiumFeatureReaction,
75 this._activateLastUsedWorkspaceReaction,
76 this._openDrawerWithSettingsReaction,
77 this._cleanupInvalidServiceReferences,
78 ]);
79
80 getUserWorkspacesRequest.execute();
81 this.isFeatureActive = true;
82 }
83
84 stop() {
85 debug('WorkspacesStore::stop');
86 this.isFeatureActive = false;
87 this.activeWorkspace = null;
88 this.nextWorkspace = null;
89 this.workspaceBeingEdited = null;
90 this.isSwitchingWorkspace = false;
91 this.isWorkspaceDrawerOpen = false;
92 }
93
94 filterServicesByActiveWorkspace = (services) => {
95 const { activeWorkspace, isFeatureActive } = this;
96 if (isFeatureActive && activeWorkspace) {
97 return this.getWorkspaceServices(activeWorkspace);
98 }
99 return services;
100 };
101
102 getWorkspaceServices(workspace) {
103 const { services } = this.stores;
104 return workspace.services.map(id => services.one(id)).filter(s => !!s);
105 }
106
107 // ========== PRIVATE ========= //
108
109 _wasDrawerOpenBeforeSettingsRoute = null;
110
111 _getWorkspaceById = id => this.workspaces.find(w => w.id === id);
112
113 _updateSettings = (changes) => {
114 localStorage.setItem('workspaces', {
115 ...this.settings,
116 ...changes,
117 });
118 };
119
120 // Actions
121
122 @action _edit = ({ workspace }) => {
123 this.stores.router.push(`/settings/workspaces/edit/${workspace.id}`);
124 };
125
126 @action _create = async ({ name }) => {
127 try {
128 const workspace = await createWorkspaceRequest.execute(name);
129 await getUserWorkspacesRequest.result.push(workspace);
130 this._edit({ workspace });
131 } catch (error) {
132 throw error;
133 }
134 };
135
136 @action _delete = async ({ workspace }) => {
137 try {
138 await deleteWorkspaceRequest.execute(workspace);
139 await getUserWorkspacesRequest.result.remove(workspace);
140 this.stores.router.push('/settings/workspaces');
141 } catch (error) {
142 throw error;
143 }
144 };
145
146 @action _update = async ({ workspace }) => {
147 try {
148 await updateWorkspaceRequest.execute(workspace);
149 // Path local result optimistically
150 const localWorkspace = this._getWorkspaceById(workspace.id);
151 Object.assign(localWorkspace, workspace);
152 this.stores.router.push('/settings/workspaces');
153 } catch (error) {
154 throw error;
155 }
156 };
157
158 @action _setActiveWorkspace = ({ workspace }) => {
159 // Indicate that we are switching to another workspace
160 this.isSwitchingWorkspace = true;
161 this.nextWorkspace = workspace;
162 // Delay switching to next workspace so that the services loading does not drag down UI
163 setTimeout(() => {
164 this.activeWorkspace = workspace;
165 this._updateSettings({ lastActiveWorkspace: workspace.id });
166 }, 100);
167 // Indicate that we are done switching to the next workspace
168 setTimeout(() => {
169 this.isSwitchingWorkspace = false;
170 this.nextWorkspace = null;
171 }, 1000);
172 };
173
174 @action _deactivateActiveWorkspace = () => {
175 // Indicate that we are switching to default workspace
176 this.isSwitchingWorkspace = true;
177 this.nextWorkspace = null;
178 this._updateSettings({ lastActiveWorkspace: null });
179 // Delay switching to next workspace so that the services loading does not drag down UI
180 setTimeout(() => {
181 this.activeWorkspace = null;
182 }, 100);
183 // Indicate that we are done switching to the default workspace
184 setTimeout(() => { this.isSwitchingWorkspace = false; }, 1000);
185 };
186
187 @action _toggleWorkspaceDrawer = () => {
188 this.isWorkspaceDrawerOpen = !this.isWorkspaceDrawerOpen;
189 };
190
191 @action _openWorkspaceSettings = () => {
192 this.actions.ui.openSettings({ path: 'workspaces' });
193 };
194
195 // Reactions
196
197 _setFeatureEnabledReaction = () => {
198 const { isWorkspaceEnabled } = this.stores.features.features;
199 this.isFeatureEnabled = isWorkspaceEnabled;
200 };
201
202 _setIsPremiumFeatureReaction = () => {
203 const { features, user } = this.stores;
204 const { isPremium } = user.data;
205 const { isWorkspacePremiumFeature } = features.features;
206 this.isPremiumFeature = isWorkspacePremiumFeature;
207 this.isPremiumUpgradeRequired = isWorkspacePremiumFeature && !isPremium;
208 };
209
210 _setWorkspaceBeingEditedReaction = () => {
211 const { pathname } = this.stores.router.location;
212 const match = matchRoute('/settings/workspaces/edit/:id', pathname);
213 if (match) {
214 this.workspaceBeingEdited = this._getWorkspaceById(match.id);
215 }
216 };
217
218 _setActiveServiceOnWorkspaceSwitchReaction = () => {
219 if (!this.isFeatureActive) return;
220 if (this.activeWorkspace) {
221 const services = this.stores.services.allDisplayed;
222 const activeService = services.find(s => s.isActive);
223 const workspaceServices = this.getWorkspaceServices(this.activeWorkspace);
224 if (workspaceServices.length <= 0) return;
225 const isActiveServiceInWorkspace = workspaceServices.includes(activeService);
226 if (!isActiveServiceInWorkspace) {
227 this.actions.service.setActive({ serviceId: workspaceServices[0].id });
228 }
229 }
230 };
231
232 _activateLastUsedWorkspaceReaction = () => {
233 if (!this.activeWorkspace && this.userHasWorkspaces) {
234 const { lastActiveWorkspace } = this.settings;
235 if (lastActiveWorkspace) {
236 const workspace = this._getWorkspaceById(lastActiveWorkspace);
237 if (workspace) this._setActiveWorkspace({ workspace });
238 }
239 }
240 };
241
242 _openDrawerWithSettingsReaction = () => {
243 const { router } = this.stores;
244 const isWorkspaceSettingsRoute = router.location.pathname.includes(WORKSPACES_ROUTES.ROOT);
245 const isSwitchingToSettingsRoute = !this.isSettingsRouteActive && isWorkspaceSettingsRoute;
246 const isLeavingSettingsRoute = !isWorkspaceSettingsRoute && this.isSettingsRouteActive;
247
248 if (isSwitchingToSettingsRoute) {
249 this.isSettingsRouteActive = true;
250 this._wasDrawerOpenBeforeSettingsRoute = this.isWorkspaceDrawerOpen;
251 if (!this._wasDrawerOpenBeforeSettingsRoute) {
252 workspaceActions.toggleWorkspaceDrawer();
253 }
254 } else if (isLeavingSettingsRoute) {
255 this.isSettingsRouteActive = false;
256 if (!this._wasDrawerOpenBeforeSettingsRoute && this.isWorkspaceDrawerOpen) {
257 workspaceActions.toggleWorkspaceDrawer();
258 }
259 }
260 };
261
262 _cleanupInvalidServiceReferences = () => {
263 const { services } = this.stores;
264 let invalidServiceReferencesExist = false;
265 this.workspaces.forEach((workspace) => {
266 workspace.services.forEach((serviceId) => {
267 if (!services.one(serviceId)) {
268 invalidServiceReferencesExist = true;
269 }
270 });
271 });
272 if (invalidServiceReferencesExist) {
273 getUserWorkspacesRequest.execute();
274 }
275 };
276}
diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json
index 2560a5add..06a03db65 100644
--- a/src/i18n/locales/de.json
+++ b/src/i18n/locales/de.json
@@ -87,6 +87,9 @@
87 "menu.window" : "Fenster", 87 "menu.window" : "Fenster",
88 "menu.window.close" : "Schließen", 88 "menu.window.close" : "Schließen",
89 "menu.window.minimize" : "Minimieren", 89 "menu.window.minimize" : "Minimieren",
90 "menu.workspaces": "Workspaces",
91 "menu.workspaces.defaultWorkspace": "All services",
92 "menu.workspaces.addNewWorkspace": "Add New Workspace",
90 "password.email.label" : "E-Mail Adresse", 93 "password.email.label" : "E-Mail Adresse",
91 "password.headline" : "Passwort zurücksetzen", 94 "password.headline" : "Passwort zurücksetzen",
92 "password.link.login" : "An Deinem Konto anmelden", 95 "password.link.login" : "An Deinem Konto anmelden",
@@ -169,6 +172,7 @@
169 "settings.navigation.logout" : "Abmelden", 172 "settings.navigation.logout" : "Abmelden",
170 "settings.navigation.settings" : "Einstellungen", 173 "settings.navigation.settings" : "Einstellungen",
171 "settings.navigation.yourServices" : "Deine Dienste", 174 "settings.navigation.yourServices" : "Deine Dienste",
175 "settings.navigation.yourWorkspaces": "Deine Workspaces",
172 "settings.recipes.all" : "Alle Dienste", 176 "settings.recipes.all" : "Alle Dienste",
173 "settings.recipes.dev" : "Entwicklung", 177 "settings.recipes.dev" : "Entwicklung",
174 "settings.recipes.headline" : "Verfügbare Dienste", 178 "settings.recipes.headline" : "Verfügbare Dienste",
@@ -226,6 +230,14 @@
226 "settings.services.tooltip.isMuted" : "Alle Töne sind deaktiviert", 230 "settings.services.tooltip.isMuted" : "Alle Töne sind deaktiviert",
227 "settings.services.tooltip.notificationsDisabled" : "Benachrichtigungen deaktiviert", 231 "settings.services.tooltip.notificationsDisabled" : "Benachrichtigungen deaktiviert",
228 "settings.services.updatedInfo" : "Deine Änderungen wurden gespeichert", 232 "settings.services.updatedInfo" : "Deine Änderungen wurden gespeichert",
233 "settings.workspaces.headline": "Deine Workspaces",
234 "settings.workspace.add.form.submitButton": "Workspace erstellen",
235 "settings.workspace.add.form.name": "Name",
236 "settings.workspace.form.yourWorkspaces": "Deine Workspaces",
237 "settings.workspace.form.name": "Name",
238 "settings.workspace.form.buttonDelete": "Workspace löschen",
239 "settings.workspace.form.buttonSave": "Workspace speichern",
240 "settings.workspace.form.servicesInWorkspaceHeadline": "Services in diesem Workspace",
229 "settings.user.form.accountType.company" : "Firma", 241 "settings.user.form.accountType.company" : "Firma",
230 "settings.user.form.accountType.individual" : "Einzelperson", 242 "settings.user.form.accountType.individual" : "Einzelperson",
231 "settings.user.form.accountType.label" : "Konto-Typ", 243 "settings.user.form.accountType.label" : "Konto-Typ",
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index 0641c510c..65799b614 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -625,78 +625,78 @@
625 "defaultMessage": "!!!Your services have been updated.", 625 "defaultMessage": "!!!Your services have been updated.",
626 "end": { 626 "end": {
627 "column": 3, 627 "column": 3,
628 "line": 25 628 "line": 28
629 }, 629 },
630 "file": "src/components/layout/AppLayout.js", 630 "file": "src/components/layout/AppLayout.js",
631 "id": "infobar.servicesUpdated", 631 "id": "infobar.servicesUpdated",
632 "start": { 632 "start": {
633 "column": 19, 633 "column": 19,
634 "line": 22 634 "line": 25
635 } 635 }
636 }, 636 },
637 { 637 {
638 "defaultMessage": "!!!A new update for Franz is available.", 638 "defaultMessage": "!!!A new update for Franz is available.",
639 "end": { 639 "end": {
640 "column": 3, 640 "column": 3,
641 "line": 29 641 "line": 32
642 }, 642 },
643 "file": "src/components/layout/AppLayout.js", 643 "file": "src/components/layout/AppLayout.js",
644 "id": "infobar.updateAvailable", 644 "id": "infobar.updateAvailable",
645 "start": { 645 "start": {
646 "column": 19, 646 "column": 19,
647 "line": 26 647 "line": 29
648 } 648 }
649 }, 649 },
650 { 650 {
651 "defaultMessage": "!!!Reload services", 651 "defaultMessage": "!!!Reload services",
652 "end": { 652 "end": {
653 "column": 3, 653 "column": 3,
654 "line": 33 654 "line": 36
655 }, 655 },
656 "file": "src/components/layout/AppLayout.js", 656 "file": "src/components/layout/AppLayout.js",
657 "id": "infobar.buttonReloadServices", 657 "id": "infobar.buttonReloadServices",
658 "start": { 658 "start": {
659 "column": 24, 659 "column": 24,
660 "line": 30 660 "line": 33
661 } 661 }
662 }, 662 },
663 { 663 {
664 "defaultMessage": "!!!Changelog", 664 "defaultMessage": "!!!Changelog",
665 "end": { 665 "end": {
666 "column": 3, 666 "column": 3,
667 "line": 37 667 "line": 40
668 }, 668 },
669 "file": "src/components/layout/AppLayout.js", 669 "file": "src/components/layout/AppLayout.js",
670 "id": "infobar.buttonChangelog", 670 "id": "infobar.buttonChangelog",
671 "start": { 671 "start": {
672 "column": 13, 672 "column": 13,
673 "line": 34 673 "line": 37
674 } 674 }
675 }, 675 },
676 { 676 {
677 "defaultMessage": "!!!Restart & install update", 677 "defaultMessage": "!!!Restart & install update",
678 "end": { 678 "end": {
679 "column": 3, 679 "column": 3,
680 "line": 41 680 "line": 44
681 }, 681 },
682 "file": "src/components/layout/AppLayout.js", 682 "file": "src/components/layout/AppLayout.js",
683 "id": "infobar.buttonInstallUpdate", 683 "id": "infobar.buttonInstallUpdate",
684 "start": { 684 "start": {
685 "column": 23, 685 "column": 23,
686 "line": 38 686 "line": 41
687 } 687 }
688 }, 688 },
689 { 689 {
690 "defaultMessage": "!!!Could not load services and user information", 690 "defaultMessage": "!!!Could not load services and user information",
691 "end": { 691 "end": {
692 "column": 3, 692 "column": 3,
693 "line": 45 693 "line": 48
694 }, 694 },
695 "file": "src/components/layout/AppLayout.js", 695 "file": "src/components/layout/AppLayout.js",
696 "id": "infobar.requiredRequestsFailed", 696 "id": "infobar.requiredRequestsFailed",
697 "start": { 697 "start": {
698 "column": 26, 698 "column": 26,
699 "line": 42 699 "line": 45
700 } 700 }
701 } 701 }
702 ], 702 ],
@@ -708,52 +708,78 @@
708 "defaultMessage": "!!!Settings", 708 "defaultMessage": "!!!Settings",
709 "end": { 709 "end": {
710 "column": 3, 710 "column": 3,
711 "line": 14 711 "line": 16
712 }, 712 },
713 "file": "src/components/layout/Sidebar.js", 713 "file": "src/components/layout/Sidebar.js",
714 "id": "sidebar.settings", 714 "id": "sidebar.settings",
715 "start": { 715 "start": {
716 "column": 12, 716 "column": 12,
717 "line": 11 717 "line": 13
718 } 718 }
719 }, 719 },
720 { 720 {
721 "defaultMessage": "!!!Add new service", 721 "defaultMessage": "!!!Add new service",
722 "end": { 722 "end": {
723 "column": 3, 723 "column": 3,
724 "line": 18 724 "line": 20
725 }, 725 },
726 "file": "src/components/layout/Sidebar.js", 726 "file": "src/components/layout/Sidebar.js",
727 "id": "sidebar.addNewService", 727 "id": "sidebar.addNewService",
728 "start": { 728 "start": {
729 "column": 17, 729 "column": 17,
730 "line": 15 730 "line": 17
731 } 731 }
732 }, 732 },
733 { 733 {
734 "defaultMessage": "!!!Disable notifications & audio", 734 "defaultMessage": "!!!Disable notifications & audio",
735 "end": { 735 "end": {
736 "column": 3, 736 "column": 3,
737 "line": 22 737 "line": 24
738 }, 738 },
739 "file": "src/components/layout/Sidebar.js", 739 "file": "src/components/layout/Sidebar.js",
740 "id": "sidebar.muteApp", 740 "id": "sidebar.muteApp",
741 "start": { 741 "start": {
742 "column": 8, 742 "column": 8,
743 "line": 19 743 "line": 21
744 } 744 }
745 }, 745 },
746 { 746 {
747 "defaultMessage": "!!!Enable notifications & audio", 747 "defaultMessage": "!!!Enable notifications & audio",
748 "end": { 748 "end": {
749 "column": 3, 749 "column": 3,
750 "line": 26 750 "line": 28
751 }, 751 },
752 "file": "src/components/layout/Sidebar.js", 752 "file": "src/components/layout/Sidebar.js",
753 "id": "sidebar.unmuteApp", 753 "id": "sidebar.unmuteApp",
754 "start": { 754 "start": {
755 "column": 10, 755 "column": 10,
756 "line": 23 756 "line": 25
757 }
758 },
759 {
760 "defaultMessage": "!!!Open workspace drawer",
761 "end": {
762 "column": 3,
763 "line": 32
764 },
765 "file": "src/components/layout/Sidebar.js",
766 "id": "sidebar.openWorkspaceDrawer",
767 "start": {
768 "column": 23,
769 "line": 29
770 }
771 },
772 {
773 "defaultMessage": "!!!Close workspace drawer",
774 "end": {
775 "column": 3,
776 "line": 36
777 },
778 "file": "src/components/layout/Sidebar.js",
779 "id": "sidebar.closeWorkspaceDrawer",
780 "start": {
781 "column": 24,
782 "line": 33
757 } 783 }
758 } 784 }
759 ], 785 ],
@@ -1276,78 +1302,91 @@
1276 "defaultMessage": "!!!Available services", 1302 "defaultMessage": "!!!Available services",
1277 "end": { 1303 "end": {
1278 "column": 3, 1304 "column": 3,
1279 "line": 12 1305 "line": 15
1280 }, 1306 },
1281 "file": "src/components/settings/navigation/SettingsNavigation.js", 1307 "file": "src/components/settings/navigation/SettingsNavigation.js",
1282 "id": "settings.navigation.availableServices", 1308 "id": "settings.navigation.availableServices",
1283 "start": { 1309 "start": {
1284 "column": 21, 1310 "column": 21,
1285 "line": 9 1311 "line": 12
1286 } 1312 }
1287 }, 1313 },
1288 { 1314 {
1289 "defaultMessage": "!!!Your services", 1315 "defaultMessage": "!!!Your services",
1290 "end": { 1316 "end": {
1291 "column": 3, 1317 "column": 3,
1292 "line": 16 1318 "line": 19
1293 }, 1319 },
1294 "file": "src/components/settings/navigation/SettingsNavigation.js", 1320 "file": "src/components/settings/navigation/SettingsNavigation.js",
1295 "id": "settings.navigation.yourServices", 1321 "id": "settings.navigation.yourServices",
1296 "start": { 1322 "start": {
1297 "column": 16, 1323 "column": 16,
1298 "line": 13 1324 "line": 16
1299 } 1325 }
1300 }, 1326 },
1301 { 1327 {
1302 "defaultMessage": "!!!Account", 1328 "defaultMessage": "!!!Your workspaces",
1303 "end": { 1329 "end": {
1304 "column": 3, 1330 "column": 3,
1331 "line": 23
1332 },
1333 "file": "src/components/settings/navigation/SettingsNavigation.js",
1334 "id": "settings.navigation.yourWorkspaces",
1335 "start": {
1336 "column": 18,
1305 "line": 20 1337 "line": 20
1338 }
1339 },
1340 {
1341 "defaultMessage": "!!!Account",
1342 "end": {
1343 "column": 3,
1344 "line": 27
1306 }, 1345 },
1307 "file": "src/components/settings/navigation/SettingsNavigation.js", 1346 "file": "src/components/settings/navigation/SettingsNavigation.js",
1308 "id": "settings.navigation.account", 1347 "id": "settings.navigation.account",
1309 "start": { 1348 "start": {
1310 "column": 11, 1349 "column": 11,
1311 "line": 17 1350 "line": 24
1312 } 1351 }
1313 }, 1352 },
1314 { 1353 {
1315 "defaultMessage": "!!!Settings", 1354 "defaultMessage": "!!!Settings",
1316 "end": { 1355 "end": {
1317 "column": 3, 1356 "column": 3,
1318 "line": 24 1357 "line": 31
1319 }, 1358 },
1320 "file": "src/components/settings/navigation/SettingsNavigation.js", 1359 "file": "src/components/settings/navigation/SettingsNavigation.js",
1321 "id": "settings.navigation.settings", 1360 "id": "settings.navigation.settings",
1322 "start": { 1361 "start": {
1323 "column": 12, 1362 "column": 12,
1324 "line": 21 1363 "line": 28
1325 } 1364 }
1326 }, 1365 },
1327 { 1366 {
1328 "defaultMessage": "!!!Invite Friends", 1367 "defaultMessage": "!!!Invite Friends",
1329 "end": { 1368 "end": {
1330 "column": 3, 1369 "column": 3,
1331 "line": 28 1370 "line": 35
1332 }, 1371 },
1333 "file": "src/components/settings/navigation/SettingsNavigation.js", 1372 "file": "src/components/settings/navigation/SettingsNavigation.js",
1334 "id": "settings.navigation.inviteFriends", 1373 "id": "settings.navigation.inviteFriends",
1335 "start": { 1374 "start": {
1336 "column": 17, 1375 "column": 17,
1337 "line": 25 1376 "line": 32
1338 } 1377 }
1339 }, 1378 },
1340 { 1379 {
1341 "defaultMessage": "!!!Logout", 1380 "defaultMessage": "!!!Logout",
1342 "end": { 1381 "end": {
1343 "column": 3, 1382 "column": 3,
1344 "line": 32 1383 "line": 39
1345 }, 1384 },
1346 "file": "src/components/settings/navigation/SettingsNavigation.js", 1385 "file": "src/components/settings/navigation/SettingsNavigation.js",
1347 "id": "settings.navigation.logout", 1386 "id": "settings.navigation.logout",
1348 "start": { 1387 "start": {
1349 "column": 10, 1388 "column": 10,
1350 "line": 29 1389 "line": 36
1351 } 1390 }
1352 } 1391 }
1353 ], 1392 ],
@@ -2496,13 +2535,13 @@
2496 "defaultMessage": "!!!Upgrade account", 2535 "defaultMessage": "!!!Upgrade account",
2497 "end": { 2536 "end": {
2498 "column": 3, 2537 "column": 3,
2499 "line": 17 2538 "line": 18
2500 }, 2539 },
2501 "file": "src/components/ui/PremiumFeatureContainer/index.js", 2540 "file": "src/components/ui/PremiumFeatureContainer/index.js",
2502 "id": "premiumFeature.button.upgradeAccount", 2541 "id": "premiumFeature.button.upgradeAccount",
2503 "start": { 2542 "start": {
2504 "column": 10, 2543 "column": 10,
2505 "line": 14 2544 "line": 15
2506 } 2545 }
2507 } 2546 }
2508 ], 2547 ],
@@ -2511,6 +2550,24 @@
2511 { 2550 {
2512 "descriptors": [ 2551 "descriptors": [
2513 { 2552 {
2553 "defaultMessage": "!!!Loading",
2554 "end": {
2555 "column": 3,
2556 "line": 14
2557 },
2558 "file": "src/components/ui/WebviewLoader/index.js",
2559 "id": "service.webviewLoader.loading",
2560 "start": {
2561 "column": 11,
2562 "line": 11
2563 }
2564 }
2565 ],
2566 "path": "src/components/ui/WebviewLoader/index.json"
2567 },
2568 {
2569 "descriptors": [
2570 {
2514 "defaultMessage": "!!!Something went wrong.", 2571 "defaultMessage": "!!!Something went wrong.",
2515 "end": { 2572 "end": {
2516 "column": 3, 2573 "column": 3,
@@ -3147,7 +3204,7 @@
3147 } 3204 }
3148 }, 3205 },
3149 { 3206 {
3150 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @MeetFranz", 3207 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger",
3151 "end": { 3208 "end": {
3152 "column": 3, 3209 "column": 3,
3153 "line": 42 3210 "line": 42
@@ -3165,6 +3222,374 @@
3165 { 3222 {
3166 "descriptors": [ 3223 "descriptors": [
3167 { 3224 {
3225 "defaultMessage": "!!!Create workspace",
3226 "end": {
3227 "column": 3,
3228 "line": 16
3229 },
3230 "file": "src/features/workspaces/components/CreateWorkspaceForm.js",
3231 "id": "settings.workspace.add.form.submitButton",
3232 "start": {
3233 "column": 16,
3234 "line": 13
3235 }
3236 },
3237 {
3238 "defaultMessage": "!!!Name",
3239 "end": {
3240 "column": 3,
3241 "line": 20
3242 },
3243 "file": "src/features/workspaces/components/CreateWorkspaceForm.js",
3244 "id": "settings.workspace.add.form.name",
3245 "start": {
3246 "column": 8,
3247 "line": 17
3248 }
3249 }
3250 ],
3251 "path": "src/features/workspaces/components/CreateWorkspaceForm.json"
3252 },
3253 {
3254 "descriptors": [
3255 {
3256 "defaultMessage": "!!!Delete workspace",
3257 "end": {
3258 "column": 3,
3259 "line": 22
3260 },
3261 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
3262 "id": "settings.workspace.form.buttonDelete",
3263 "start": {
3264 "column": 16,
3265 "line": 19
3266 }
3267 },
3268 {
3269 "defaultMessage": "!!!Save workspace",
3270 "end": {
3271 "column": 3,
3272 "line": 26
3273 },
3274 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
3275 "id": "settings.workspace.form.buttonSave",
3276 "start": {
3277 "column": 14,
3278 "line": 23
3279 }
3280 },
3281 {
3282 "defaultMessage": "!!!Name",
3283 "end": {
3284 "column": 3,
3285 "line": 30
3286 },
3287 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
3288 "id": "settings.workspace.form.name",
3289 "start": {
3290 "column": 8,
3291 "line": 27
3292 }
3293 },
3294 {
3295 "defaultMessage": "!!!Your workspaces",
3296 "end": {
3297 "column": 3,
3298 "line": 34
3299 },
3300 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
3301 "id": "settings.workspace.form.yourWorkspaces",
3302 "start": {
3303 "column": 18,
3304 "line": 31
3305 }
3306 },
3307 {
3308 "defaultMessage": "!!!Services in this Workspace",
3309 "end": {
3310 "column": 3,
3311 "line": 38
3312 },
3313 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
3314 "id": "settings.workspace.form.servicesInWorkspaceHeadline",
3315 "start": {
3316 "column": 31,
3317 "line": 35
3318 }
3319 }
3320 ],
3321 "path": "src/features/workspaces/components/EditWorkspaceForm.json"
3322 },
3323 {
3324 "descriptors": [
3325 {
3326 "defaultMessage": "!!!Workspaces",
3327 "end": {
3328 "column": 3,
3329 "line": 19
3330 },
3331 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3332 "id": "workspaceDrawer.headline",
3333 "start": {
3334 "column": 12,
3335 "line": 16
3336 }
3337 },
3338 {
3339 "defaultMessage": "!!!All services",
3340 "end": {
3341 "column": 3,
3342 "line": 23
3343 },
3344 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3345 "id": "workspaceDrawer.allServices",
3346 "start": {
3347 "column": 15,
3348 "line": 20
3349 }
3350 },
3351 {
3352 "defaultMessage": "!!!Workspaces settings",
3353 "end": {
3354 "column": 3,
3355 "line": 27
3356 },
3357 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3358 "id": "workspaceDrawer.workspacesSettingsTooltip",
3359 "start": {
3360 "column": 29,
3361 "line": 24
3362 }
3363 },
3364 {
3365 "defaultMessage": "!!!Info about workspace feature",
3366 "end": {
3367 "column": 3,
3368 "line": 31
3369 },
3370 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3371 "id": "workspaceDrawer.workspaceFeatureInfo",
3372 "start": {
3373 "column": 24,
3374 "line": 28
3375 }
3376 },
3377 {
3378 "defaultMessage": "!!!Create your first workspace",
3379 "end": {
3380 "column": 3,
3381 "line": 35
3382 },
3383 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3384 "id": "workspaceDrawer.premiumCtaButtonLabel",
3385 "start": {
3386 "column": 25,
3387 "line": 32
3388 }
3389 },
3390 {
3391 "defaultMessage": "!!!Reactivate premium account",
3392 "end": {
3393 "column": 3,
3394 "line": 39
3395 },
3396 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3397 "id": "workspaceDrawer.reactivatePremiumAccountLabel",
3398 "start": {
3399 "column": 28,
3400 "line": 36
3401 }
3402 },
3403 {
3404 "defaultMessage": "!!!add new workspace",
3405 "end": {
3406 "column": 3,
3407 "line": 43
3408 },
3409 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3410 "id": "workspaceDrawer.addNewWorkspaceLabel",
3411 "start": {
3412 "column": 24,
3413 "line": 40
3414 }
3415 },
3416 {
3417 "defaultMessage": "!!!Premium feature",
3418 "end": {
3419 "column": 3,
3420 "line": 47
3421 },
3422 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3423 "id": "workspaceDrawer.proFeatureBadge",
3424 "start": {
3425 "column": 23,
3426 "line": 44
3427 }
3428 }
3429 ],
3430 "path": "src/features/workspaces/components/WorkspaceDrawer.json"
3431 },
3432 {
3433 "descriptors": [
3434 {
3435 "defaultMessage": "!!!No services added yet",
3436 "end": {
3437 "column": 3,
3438 "line": 15
3439 },
3440 "file": "src/features/workspaces/components/WorkspaceDrawerItem.js",
3441 "id": "workspaceDrawer.item.noServicesAddedYet",
3442 "start": {
3443 "column": 22,
3444 "line": 12
3445 }
3446 },
3447 {
3448 "defaultMessage": "!!!edit",
3449 "end": {
3450 "column": 3,
3451 "line": 19
3452 },
3453 "file": "src/features/workspaces/components/WorkspaceDrawerItem.js",
3454 "id": "workspaceDrawer.item.contextMenuEdit",
3455 "start": {
3456 "column": 19,
3457 "line": 16
3458 }
3459 }
3460 ],
3461 "path": "src/features/workspaces/components/WorkspaceDrawerItem.json"
3462 },
3463 {
3464 "descriptors": [
3465 {
3466 "defaultMessage": "!!!Your workspaces",
3467 "end": {
3468 "column": 3,
3469 "line": 20
3470 },
3471 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3472 "id": "settings.workspaces.headline",
3473 "start": {
3474 "column": 12,
3475 "line": 17
3476 }
3477 },
3478 {
3479 "defaultMessage": "!!!You haven't added any workspaces yet.",
3480 "end": {
3481 "column": 3,
3482 "line": 24
3483 },
3484 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3485 "id": "settings.workspaces.noWorkspacesAdded",
3486 "start": {
3487 "column": 19,
3488 "line": 21
3489 }
3490 },
3491 {
3492 "defaultMessage": "!!!Could not load your workspaces",
3493 "end": {
3494 "column": 3,
3495 "line": 28
3496 },
3497 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3498 "id": "settings.workspaces.workspacesRequestFailed",
3499 "start": {
3500 "column": 27,
3501 "line": 25
3502 }
3503 },
3504 {
3505 "defaultMessage": "!!!Try again",
3506 "end": {
3507 "column": 3,
3508 "line": 32
3509 },
3510 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3511 "id": "settings.workspaces.tryReloadWorkspaces",
3512 "start": {
3513 "column": 23,
3514 "line": 29
3515 }
3516 },
3517 {
3518 "defaultMessage": "!!!Your changes have been saved",
3519 "end": {
3520 "column": 3,
3521 "line": 36
3522 },
3523 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3524 "id": "settings.workspaces.updatedInfo",
3525 "start": {
3526 "column": 15,
3527 "line": 33
3528 }
3529 },
3530 {
3531 "defaultMessage": "!!!Workspace has been deleted",
3532 "end": {
3533 "column": 3,
3534 "line": 40
3535 },
3536 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3537 "id": "settings.workspaces.deletedInfo",
3538 "start": {
3539 "column": 15,
3540 "line": 37
3541 }
3542 },
3543 {
3544 "defaultMessage": "!!!Info about workspace feature",
3545 "end": {
3546 "column": 3,
3547 "line": 44
3548 },
3549 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3550 "id": "settings.workspaces.workspaceFeatureInfo",
3551 "start": {
3552 "column": 24,
3553 "line": 41
3554 }
3555 },
3556 {
3557 "defaultMessage": "!!!Less is More: Introducing Franz Workspaces",
3558 "end": {
3559 "column": 3,
3560 "line": 48
3561 },
3562 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3563 "id": "settings.workspaces.workspaceFeatureHeadline",
3564 "start": {
3565 "column": 28,
3566 "line": 45
3567 }
3568 }
3569 ],
3570 "path": "src/features/workspaces/components/WorkspacesDashboard.json"
3571 },
3572 {
3573 "descriptors": [
3574 {
3575 "defaultMessage": "!!!Switching to",
3576 "end": {
3577 "column": 3,
3578 "line": 15
3579 },
3580 "file": "src/features/workspaces/components/WorkspaceSwitchingIndicator.js",
3581 "id": "workspaces.switchingIndicator.switchingTo",
3582 "start": {
3583 "column": 15,
3584 "line": 12
3585 }
3586 }
3587 ],
3588 "path": "src/features/workspaces/components/WorkspaceSwitchingIndicator.json"
3589 },
3590 {
3591 "descriptors": [
3592 {
3168 "defaultMessage": "!!!Field is required", 3593 "defaultMessage": "!!!Field is required",
3169 "end": { 3594 "end": {
3170 "column": 3, 3595 "column": 3,
@@ -3321,611 +3746,676 @@
3321 "defaultMessage": "!!!Edit", 3746 "defaultMessage": "!!!Edit",
3322 "end": { 3747 "end": {
3323 "column": 3, 3748 "column": 3,
3324 "line": 13 3749 "line": 16
3325 }, 3750 },
3326 "file": "src/lib/Menu.js", 3751 "file": "src/lib/Menu.js",
3327 "id": "menu.edit", 3752 "id": "menu.edit",
3328 "start": { 3753 "start": {
3329 "column": 8, 3754 "column": 8,
3330 "line": 10 3755 "line": 13
3331 } 3756 }
3332 }, 3757 },
3333 { 3758 {
3334 "defaultMessage": "!!!Undo", 3759 "defaultMessage": "!!!Undo",
3335 "end": { 3760 "end": {
3336 "column": 3, 3761 "column": 3,
3337 "line": 17 3762 "line": 20
3338 }, 3763 },
3339 "file": "src/lib/Menu.js", 3764 "file": "src/lib/Menu.js",
3340 "id": "menu.edit.undo", 3765 "id": "menu.edit.undo",
3341 "start": { 3766 "start": {
3342 "column": 8, 3767 "column": 8,
3343 "line": 14 3768 "line": 17
3344 } 3769 }
3345 }, 3770 },
3346 { 3771 {
3347 "defaultMessage": "!!!Redo", 3772 "defaultMessage": "!!!Redo",
3348 "end": { 3773 "end": {
3349 "column": 3, 3774 "column": 3,
3350 "line": 21 3775 "line": 24
3351 }, 3776 },
3352 "file": "src/lib/Menu.js", 3777 "file": "src/lib/Menu.js",
3353 "id": "menu.edit.redo", 3778 "id": "menu.edit.redo",
3354 "start": { 3779 "start": {
3355 "column": 8, 3780 "column": 8,
3356 "line": 18 3781 "line": 21
3357 } 3782 }
3358 }, 3783 },
3359 { 3784 {
3360 "defaultMessage": "!!!Cut", 3785 "defaultMessage": "!!!Cut",
3361 "end": { 3786 "end": {
3362 "column": 3, 3787 "column": 3,
3363 "line": 25 3788 "line": 28
3364 }, 3789 },
3365 "file": "src/lib/Menu.js", 3790 "file": "src/lib/Menu.js",
3366 "id": "menu.edit.cut", 3791 "id": "menu.edit.cut",
3367 "start": { 3792 "start": {
3368 "column": 7, 3793 "column": 7,
3369 "line": 22 3794 "line": 25
3370 } 3795 }
3371 }, 3796 },
3372 { 3797 {
3373 "defaultMessage": "!!!Copy", 3798 "defaultMessage": "!!!Copy",
3374 "end": { 3799 "end": {
3375 "column": 3, 3800 "column": 3,
3376 "line": 29 3801 "line": 32
3377 }, 3802 },
3378 "file": "src/lib/Menu.js", 3803 "file": "src/lib/Menu.js",
3379 "id": "menu.edit.copy", 3804 "id": "menu.edit.copy",
3380 "start": { 3805 "start": {
3381 "column": 8, 3806 "column": 8,
3382 "line": 26 3807 "line": 29
3383 } 3808 }
3384 }, 3809 },
3385 { 3810 {
3386 "defaultMessage": "!!!Paste", 3811 "defaultMessage": "!!!Paste",
3387 "end": { 3812 "end": {
3388 "column": 3, 3813 "column": 3,
3389 "line": 33 3814 "line": 36
3390 }, 3815 },
3391 "file": "src/lib/Menu.js", 3816 "file": "src/lib/Menu.js",
3392 "id": "menu.edit.paste", 3817 "id": "menu.edit.paste",
3393 "start": { 3818 "start": {
3394 "column": 9, 3819 "column": 9,
3395 "line": 30 3820 "line": 33
3396 } 3821 }
3397 }, 3822 },
3398 { 3823 {
3399 "defaultMessage": "!!!Paste And Match Style", 3824 "defaultMessage": "!!!Paste And Match Style",
3400 "end": { 3825 "end": {
3401 "column": 3, 3826 "column": 3,
3402 "line": 37 3827 "line": 40
3403 }, 3828 },
3404 "file": "src/lib/Menu.js", 3829 "file": "src/lib/Menu.js",
3405 "id": "menu.edit.pasteAndMatchStyle", 3830 "id": "menu.edit.pasteAndMatchStyle",
3406 "start": { 3831 "start": {
3407 "column": 22, 3832 "column": 22,
3408 "line": 34 3833 "line": 37
3409 } 3834 }
3410 }, 3835 },
3411 { 3836 {
3412 "defaultMessage": "!!!Delete", 3837 "defaultMessage": "!!!Delete",
3413 "end": { 3838 "end": {
3414 "column": 3, 3839 "column": 3,
3415 "line": 41 3840 "line": 44
3416 }, 3841 },
3417 "file": "src/lib/Menu.js", 3842 "file": "src/lib/Menu.js",
3418 "id": "menu.edit.delete", 3843 "id": "menu.edit.delete",
3419 "start": { 3844 "start": {
3420 "column": 10, 3845 "column": 10,
3421 "line": 38 3846 "line": 41
3422 } 3847 }
3423 }, 3848 },
3424 { 3849 {
3425 "defaultMessage": "!!!Select All", 3850 "defaultMessage": "!!!Select All",
3426 "end": { 3851 "end": {
3427 "column": 3, 3852 "column": 3,
3428 "line": 45 3853 "line": 48
3429 }, 3854 },
3430 "file": "src/lib/Menu.js", 3855 "file": "src/lib/Menu.js",
3431 "id": "menu.edit.selectAll", 3856 "id": "menu.edit.selectAll",
3432 "start": { 3857 "start": {
3433 "column": 13, 3858 "column": 13,
3434 "line": 42 3859 "line": 45
3435 } 3860 }
3436 }, 3861 },
3437 { 3862 {
3438 "defaultMessage": "!!!Speech", 3863 "defaultMessage": "!!!Speech",
3439 "end": { 3864 "end": {
3440 "column": 3, 3865 "column": 3,
3441 "line": 49 3866 "line": 52
3442 }, 3867 },
3443 "file": "src/lib/Menu.js", 3868 "file": "src/lib/Menu.js",
3444 "id": "menu.edit.speech", 3869 "id": "menu.edit.speech",
3445 "start": { 3870 "start": {
3446 "column": 10, 3871 "column": 10,
3447 "line": 46 3872 "line": 49
3448 } 3873 }
3449 }, 3874 },
3450 { 3875 {
3451 "defaultMessage": "!!!Start Speaking", 3876 "defaultMessage": "!!!Start Speaking",
3452 "end": { 3877 "end": {
3453 "column": 3, 3878 "column": 3,
3454 "line": 53 3879 "line": 56
3455 }, 3880 },
3456 "file": "src/lib/Menu.js", 3881 "file": "src/lib/Menu.js",
3457 "id": "menu.edit.startSpeaking", 3882 "id": "menu.edit.startSpeaking",
3458 "start": { 3883 "start": {
3459 "column": 17, 3884 "column": 17,
3460 "line": 50 3885 "line": 53
3461 } 3886 }
3462 }, 3887 },
3463 { 3888 {
3464 "defaultMessage": "!!!Stop Speaking", 3889 "defaultMessage": "!!!Stop Speaking",
3465 "end": { 3890 "end": {
3466 "column": 3, 3891 "column": 3,
3467 "line": 57 3892 "line": 60
3468 }, 3893 },
3469 "file": "src/lib/Menu.js", 3894 "file": "src/lib/Menu.js",
3470 "id": "menu.edit.stopSpeaking", 3895 "id": "menu.edit.stopSpeaking",
3471 "start": { 3896 "start": {
3472 "column": 16, 3897 "column": 16,
3473 "line": 54 3898 "line": 57
3474 } 3899 }
3475 }, 3900 },
3476 { 3901 {
3477 "defaultMessage": "!!!Start Dictation", 3902 "defaultMessage": "!!!Start Dictation",
3478 "end": { 3903 "end": {
3479 "column": 3, 3904 "column": 3,
3480 "line": 61 3905 "line": 64
3481 }, 3906 },
3482 "file": "src/lib/Menu.js", 3907 "file": "src/lib/Menu.js",
3483 "id": "menu.edit.startDictation", 3908 "id": "menu.edit.startDictation",
3484 "start": { 3909 "start": {
3485 "column": 18, 3910 "column": 18,
3486 "line": 58 3911 "line": 61
3487 } 3912 }
3488 }, 3913 },
3489 { 3914 {
3490 "defaultMessage": "!!!Emoji & Symbols", 3915 "defaultMessage": "!!!Emoji & Symbols",
3491 "end": { 3916 "end": {
3492 "column": 3, 3917 "column": 3,
3493 "line": 65 3918 "line": 68
3494 }, 3919 },
3495 "file": "src/lib/Menu.js", 3920 "file": "src/lib/Menu.js",
3496 "id": "menu.edit.emojiSymbols", 3921 "id": "menu.edit.emojiSymbols",
3497 "start": { 3922 "start": {
3498 "column": 16, 3923 "column": 16,
3499 "line": 62 3924 "line": 65
3500 } 3925 }
3501 }, 3926 },
3502 { 3927 {
3503 "defaultMessage": "!!!Actual Size", 3928 "defaultMessage": "!!!Actual Size",
3504 "end": { 3929 "end": {
3505 "column": 3, 3930 "column": 3,
3506 "line": 69 3931 "line": 72
3507 }, 3932 },
3508 "file": "src/lib/Menu.js", 3933 "file": "src/lib/Menu.js",
3509 "id": "menu.view.resetZoom", 3934 "id": "menu.view.resetZoom",
3510 "start": { 3935 "start": {
3511 "column": 13, 3936 "column": 13,
3512 "line": 66 3937 "line": 69
3513 } 3938 }
3514 }, 3939 },
3515 { 3940 {
3516 "defaultMessage": "!!!Zoom In", 3941 "defaultMessage": "!!!Zoom In",
3517 "end": { 3942 "end": {
3518 "column": 3, 3943 "column": 3,
3519 "line": 73 3944 "line": 76
3520 }, 3945 },
3521 "file": "src/lib/Menu.js", 3946 "file": "src/lib/Menu.js",
3522 "id": "menu.view.zoomIn", 3947 "id": "menu.view.zoomIn",
3523 "start": { 3948 "start": {
3524 "column": 10, 3949 "column": 10,
3525 "line": 70 3950 "line": 73
3526 } 3951 }
3527 }, 3952 },
3528 { 3953 {
3529 "defaultMessage": "!!!Zoom Out", 3954 "defaultMessage": "!!!Zoom Out",
3530 "end": { 3955 "end": {
3531 "column": 3, 3956 "column": 3,
3532 "line": 77 3957 "line": 80
3533 }, 3958 },
3534 "file": "src/lib/Menu.js", 3959 "file": "src/lib/Menu.js",
3535 "id": "menu.view.zoomOut", 3960 "id": "menu.view.zoomOut",
3536 "start": { 3961 "start": {
3537 "column": 11, 3962 "column": 11,
3538 "line": 74 3963 "line": 77
3539 } 3964 }
3540 }, 3965 },
3541 { 3966 {
3542 "defaultMessage": "!!!Enter Full Screen", 3967 "defaultMessage": "!!!Enter Full Screen",
3543 "end": { 3968 "end": {
3544 "column": 3, 3969 "column": 3,
3545 "line": 81 3970 "line": 84
3546 }, 3971 },
3547 "file": "src/lib/Menu.js", 3972 "file": "src/lib/Menu.js",
3548 "id": "menu.view.enterFullScreen", 3973 "id": "menu.view.enterFullScreen",
3549 "start": { 3974 "start": {
3550 "column": 19, 3975 "column": 19,
3551 "line": 78 3976 "line": 81
3552 } 3977 }
3553 }, 3978 },
3554 { 3979 {
3555 "defaultMessage": "!!!Exit Full Screen", 3980 "defaultMessage": "!!!Exit Full Screen",
3556 "end": { 3981 "end": {
3557 "column": 3, 3982 "column": 3,
3558 "line": 85 3983 "line": 88
3559 }, 3984 },
3560 "file": "src/lib/Menu.js", 3985 "file": "src/lib/Menu.js",
3561 "id": "menu.view.exitFullScreen", 3986 "id": "menu.view.exitFullScreen",
3562 "start": { 3987 "start": {
3563 "column": 18, 3988 "column": 18,
3564 "line": 82 3989 "line": 85
3565 } 3990 }
3566 }, 3991 },
3567 { 3992 {
3568 "defaultMessage": "!!!Toggle Full Screen", 3993 "defaultMessage": "!!!Toggle Full Screen",
3569 "end": { 3994 "end": {
3570 "column": 3, 3995 "column": 3,
3571 "line": 89 3996 "line": 92
3572 }, 3997 },
3573 "file": "src/lib/Menu.js", 3998 "file": "src/lib/Menu.js",
3574 "id": "menu.view.toggleFullScreen", 3999 "id": "menu.view.toggleFullScreen",
3575 "start": { 4000 "start": {
3576 "column": 20, 4001 "column": 20,
3577 "line": 86 4002 "line": 89
3578 } 4003 }
3579 }, 4004 },
3580 { 4005 {
3581 "defaultMessage": "!!!Toggle Developer Tools", 4006 "defaultMessage": "!!!Toggle Developer Tools",
3582 "end": { 4007 "end": {
3583 "column": 3, 4008 "column": 3,
3584 "line": 93 4009 "line": 96
3585 }, 4010 },
3586 "file": "src/lib/Menu.js", 4011 "file": "src/lib/Menu.js",
3587 "id": "menu.view.toggleDevTools", 4012 "id": "menu.view.toggleDevTools",
3588 "start": { 4013 "start": {
3589 "column": 18, 4014 "column": 18,
3590 "line": 90 4015 "line": 93
3591 } 4016 }
3592 }, 4017 },
3593 { 4018 {
3594 "defaultMessage": "!!!Toggle Service Developer Tools", 4019 "defaultMessage": "!!!Toggle Service Developer Tools",
3595 "end": { 4020 "end": {
3596 "column": 3, 4021 "column": 3,
3597 "line": 97 4022 "line": 100
3598 }, 4023 },
3599 "file": "src/lib/Menu.js", 4024 "file": "src/lib/Menu.js",
3600 "id": "menu.view.toggleServiceDevTools", 4025 "id": "menu.view.toggleServiceDevTools",
3601 "start": { 4026 "start": {
3602 "column": 25, 4027 "column": 25,
3603 "line": 94 4028 "line": 97
3604 } 4029 }
3605 }, 4030 },
3606 { 4031 {
3607 "defaultMessage": "!!!Reload Service", 4032 "defaultMessage": "!!!Reload Service",
3608 "end": { 4033 "end": {
3609 "column": 3, 4034 "column": 3,
3610 "line": 101 4035 "line": 104
3611 }, 4036 },
3612 "file": "src/lib/Menu.js", 4037 "file": "src/lib/Menu.js",
3613 "id": "menu.view.reloadService", 4038 "id": "menu.view.reloadService",
3614 "start": { 4039 "start": {
3615 "column": 17, 4040 "column": 17,
3616 "line": 98 4041 "line": 101
3617 } 4042 }
3618 }, 4043 },
3619 { 4044 {
3620 "defaultMessage": "!!!Reload Franz", 4045 "defaultMessage": "!!!Reload Franz",
3621 "end": { 4046 "end": {
3622 "column": 3, 4047 "column": 3,
3623 "line": 105 4048 "line": 108
3624 }, 4049 },
3625 "file": "src/lib/Menu.js", 4050 "file": "src/lib/Menu.js",
3626 "id": "menu.view.reloadFranz", 4051 "id": "menu.view.reloadFranz",
3627 "start": { 4052 "start": {
3628 "column": 15, 4053 "column": 15,
3629 "line": 102 4054 "line": 105
3630 } 4055 }
3631 }, 4056 },
3632 { 4057 {
3633 "defaultMessage": "!!!Minimize", 4058 "defaultMessage": "!!!Minimize",
3634 "end": { 4059 "end": {
3635 "column": 3, 4060 "column": 3,
3636 "line": 109 4061 "line": 112
3637 }, 4062 },
3638 "file": "src/lib/Menu.js", 4063 "file": "src/lib/Menu.js",
3639 "id": "menu.window.minimize", 4064 "id": "menu.window.minimize",
3640 "start": { 4065 "start": {
3641 "column": 12, 4066 "column": 12,
3642 "line": 106 4067 "line": 109
3643 } 4068 }
3644 }, 4069 },
3645 { 4070 {
3646 "defaultMessage": "!!!Close", 4071 "defaultMessage": "!!!Close",
3647 "end": { 4072 "end": {
3648 "column": 3, 4073 "column": 3,
3649 "line": 113 4074 "line": 116
3650 }, 4075 },
3651 "file": "src/lib/Menu.js", 4076 "file": "src/lib/Menu.js",
3652 "id": "menu.window.close", 4077 "id": "menu.window.close",
3653 "start": { 4078 "start": {
3654 "column": 9, 4079 "column": 9,
3655 "line": 110 4080 "line": 113
3656 } 4081 }
3657 }, 4082 },
3658 { 4083 {
3659 "defaultMessage": "!!!Learn More", 4084 "defaultMessage": "!!!Learn More",
3660 "end": { 4085 "end": {
3661 "column": 3, 4086 "column": 3,
3662 "line": 117 4087 "line": 120
3663 }, 4088 },
3664 "file": "src/lib/Menu.js", 4089 "file": "src/lib/Menu.js",
3665 "id": "menu.help.learnMore", 4090 "id": "menu.help.learnMore",
3666 "start": { 4091 "start": {
3667 "column": 13, 4092 "column": 13,
3668 "line": 114 4093 "line": 117
3669 } 4094 }
3670 }, 4095 },
3671 { 4096 {
3672 "defaultMessage": "!!!Changelog", 4097 "defaultMessage": "!!!Changelog",
3673 "end": { 4098 "end": {
3674 "column": 3, 4099 "column": 3,
3675 "line": 121 4100 "line": 124
3676 }, 4101 },
3677 "file": "src/lib/Menu.js", 4102 "file": "src/lib/Menu.js",
3678 "id": "menu.help.changelog", 4103 "id": "menu.help.changelog",
3679 "start": { 4104 "start": {
3680 "column": 13, 4105 "column": 13,
3681 "line": 118 4106 "line": 121
3682 } 4107 }
3683 }, 4108 },
3684 { 4109 {
3685 "defaultMessage": "!!!Support", 4110 "defaultMessage": "!!!Support",
3686 "end": { 4111 "end": {
3687 "column": 3, 4112 "column": 3,
3688 "line": 125 4113 "line": 128
3689 }, 4114 },
3690 "file": "src/lib/Menu.js", 4115 "file": "src/lib/Menu.js",
3691 "id": "menu.help.support", 4116 "id": "menu.help.support",
3692 "start": { 4117 "start": {
3693 "column": 11, 4118 "column": 11,
3694 "line": 122 4119 "line": 125
3695 } 4120 }
3696 }, 4121 },
3697 { 4122 {
3698 "defaultMessage": "!!!Terms of Service", 4123 "defaultMessage": "!!!Terms of Service",
3699 "end": { 4124 "end": {
3700 "column": 3, 4125 "column": 3,
3701 "line": 129 4126 "line": 132
3702 }, 4127 },
3703 "file": "src/lib/Menu.js", 4128 "file": "src/lib/Menu.js",
3704 "id": "menu.help.tos", 4129 "id": "menu.help.tos",
3705 "start": { 4130 "start": {
3706 "column": 7, 4131 "column": 7,
3707 "line": 126 4132 "line": 129
3708 } 4133 }
3709 }, 4134 },
3710 { 4135 {
3711 "defaultMessage": "!!!Privacy Statement", 4136 "defaultMessage": "!!!Privacy Statement",
3712 "end": { 4137 "end": {
3713 "column": 3, 4138 "column": 3,
3714 "line": 133 4139 "line": 136
3715 }, 4140 },
3716 "file": "src/lib/Menu.js", 4141 "file": "src/lib/Menu.js",
3717 "id": "menu.help.privacy", 4142 "id": "menu.help.privacy",
3718 "start": { 4143 "start": {
3719 "column": 11, 4144 "column": 11,
3720 "line": 130 4145 "line": 133
3721 } 4146 }
3722 }, 4147 },
3723 { 4148 {
3724 "defaultMessage": "!!!File", 4149 "defaultMessage": "!!!File",
3725 "end": { 4150 "end": {
3726 "column": 3, 4151 "column": 3,
3727 "line": 137 4152 "line": 140
3728 }, 4153 },
3729 "file": "src/lib/Menu.js", 4154 "file": "src/lib/Menu.js",
3730 "id": "menu.file", 4155 "id": "menu.file",
3731 "start": { 4156 "start": {
3732 "column": 8, 4157 "column": 8,
3733 "line": 134 4158 "line": 137
3734 } 4159 }
3735 }, 4160 },
3736 { 4161 {
3737 "defaultMessage": "!!!View", 4162 "defaultMessage": "!!!View",
3738 "end": { 4163 "end": {
3739 "column": 3, 4164 "column": 3,
3740 "line": 141 4165 "line": 144
3741 }, 4166 },
3742 "file": "src/lib/Menu.js", 4167 "file": "src/lib/Menu.js",
3743 "id": "menu.view", 4168 "id": "menu.view",
3744 "start": { 4169 "start": {
3745 "column": 8, 4170 "column": 8,
3746 "line": 138 4171 "line": 141
3747 } 4172 }
3748 }, 4173 },
3749 { 4174 {
3750 "defaultMessage": "!!!Services", 4175 "defaultMessage": "!!!Services",
3751 "end": { 4176 "end": {
3752 "column": 3, 4177 "column": 3,
3753 "line": 145 4178 "line": 148
3754 }, 4179 },
3755 "file": "src/lib/Menu.js", 4180 "file": "src/lib/Menu.js",
3756 "id": "menu.services", 4181 "id": "menu.services",
3757 "start": { 4182 "start": {
3758 "column": 12, 4183 "column": 12,
3759 "line": 142 4184 "line": 145
3760 } 4185 }
3761 }, 4186 },
3762 { 4187 {
3763 "defaultMessage": "!!!Window", 4188 "defaultMessage": "!!!Window",
3764 "end": { 4189 "end": {
3765 "column": 3, 4190 "column": 3,
3766 "line": 149 4191 "line": 152
3767 }, 4192 },
3768 "file": "src/lib/Menu.js", 4193 "file": "src/lib/Menu.js",
3769 "id": "menu.window", 4194 "id": "menu.window",
3770 "start": { 4195 "start": {
3771 "column": 10, 4196 "column": 10,
3772 "line": 146 4197 "line": 149
3773 } 4198 }
3774 }, 4199 },
3775 { 4200 {
3776 "defaultMessage": "!!!Help", 4201 "defaultMessage": "!!!Help",
3777 "end": { 4202 "end": {
3778 "column": 3, 4203 "column": 3,
3779 "line": 153 4204 "line": 156
3780 }, 4205 },
3781 "file": "src/lib/Menu.js", 4206 "file": "src/lib/Menu.js",
3782 "id": "menu.help", 4207 "id": "menu.help",
3783 "start": { 4208 "start": {
3784 "column": 8, 4209 "column": 8,
3785 "line": 150 4210 "line": 153
3786 } 4211 }
3787 }, 4212 },
3788 { 4213 {
3789 "defaultMessage": "!!!About Franz", 4214 "defaultMessage": "!!!About Franz",
3790 "end": { 4215 "end": {
3791 "column": 3, 4216 "column": 3,
3792 "line": 157 4217 "line": 160
3793 }, 4218 },
3794 "file": "src/lib/Menu.js", 4219 "file": "src/lib/Menu.js",
3795 "id": "menu.app.about", 4220 "id": "menu.app.about",
3796 "start": { 4221 "start": {
3797 "column": 9, 4222 "column": 9,
3798 "line": 154 4223 "line": 157
3799 } 4224 }
3800 }, 4225 },
3801 { 4226 {
3802 "defaultMessage": "!!!Settings", 4227 "defaultMessage": "!!!Settings",
3803 "end": { 4228 "end": {
3804 "column": 3, 4229 "column": 3,
3805 "line": 161 4230 "line": 164
3806 }, 4231 },
3807 "file": "src/lib/Menu.js", 4232 "file": "src/lib/Menu.js",
3808 "id": "menu.app.settings", 4233 "id": "menu.app.settings",
3809 "start": { 4234 "start": {
3810 "column": 12, 4235 "column": 12,
3811 "line": 158 4236 "line": 161
3812 } 4237 }
3813 }, 4238 },
3814 { 4239 {
3815 "defaultMessage": "!!!Hide", 4240 "defaultMessage": "!!!Hide",
3816 "end": { 4241 "end": {
3817 "column": 3, 4242 "column": 3,
3818 "line": 165 4243 "line": 168
3819 }, 4244 },
3820 "file": "src/lib/Menu.js", 4245 "file": "src/lib/Menu.js",
3821 "id": "menu.app.hide", 4246 "id": "menu.app.hide",
3822 "start": { 4247 "start": {
3823 "column": 8, 4248 "column": 8,
3824 "line": 162 4249 "line": 165
3825 } 4250 }
3826 }, 4251 },
3827 { 4252 {
3828 "defaultMessage": "!!!Hide Others", 4253 "defaultMessage": "!!!Hide Others",
3829 "end": { 4254 "end": {
3830 "column": 3, 4255 "column": 3,
3831 "line": 169 4256 "line": 172
3832 }, 4257 },
3833 "file": "src/lib/Menu.js", 4258 "file": "src/lib/Menu.js",
3834 "id": "menu.app.hideOthers", 4259 "id": "menu.app.hideOthers",
3835 "start": { 4260 "start": {
3836 "column": 14, 4261 "column": 14,
3837 "line": 166 4262 "line": 169
3838 } 4263 }
3839 }, 4264 },
3840 { 4265 {
3841 "defaultMessage": "!!!Unhide", 4266 "defaultMessage": "!!!Unhide",
3842 "end": { 4267 "end": {
3843 "column": 3, 4268 "column": 3,
3844 "line": 173 4269 "line": 176
3845 }, 4270 },
3846 "file": "src/lib/Menu.js", 4271 "file": "src/lib/Menu.js",
3847 "id": "menu.app.unhide", 4272 "id": "menu.app.unhide",
3848 "start": { 4273 "start": {
3849 "column": 10, 4274 "column": 10,
3850 "line": 170 4275 "line": 173
3851 } 4276 }
3852 }, 4277 },
3853 { 4278 {
3854 "defaultMessage": "!!!Quit", 4279 "defaultMessage": "!!!Quit",
3855 "end": { 4280 "end": {
3856 "column": 3, 4281 "column": 3,
3857 "line": 177 4282 "line": 180
3858 }, 4283 },
3859 "file": "src/lib/Menu.js", 4284 "file": "src/lib/Menu.js",
3860 "id": "menu.app.quit", 4285 "id": "menu.app.quit",
3861 "start": { 4286 "start": {
3862 "column": 8, 4287 "column": 8,
3863 "line": 174 4288 "line": 177
3864 } 4289 }
3865 }, 4290 },
3866 { 4291 {
3867 "defaultMessage": "!!!Add New Service...", 4292 "defaultMessage": "!!!Add New Service...",
3868 "end": { 4293 "end": {
3869 "column": 3, 4294 "column": 3,
3870 "line": 181 4295 "line": 184
3871 }, 4296 },
3872 "file": "src/lib/Menu.js", 4297 "file": "src/lib/Menu.js",
3873 "id": "menu.services.addNewService", 4298 "id": "menu.services.addNewService",
3874 "start": { 4299 "start": {
3875 "column": 17, 4300 "column": 17,
3876 "line": 178 4301 "line": 181
3877 } 4302 }
3878 }, 4303 },
3879 { 4304 {
3880 "defaultMessage": "!!!Activate next service...", 4305 "defaultMessage": "!!!Add New Workspace...",
3881 "end": { 4306 "end": {
3882 "column": 3, 4307 "column": 3,
4308 "line": 188
4309 },
4310 "file": "src/lib/Menu.js",
4311 "id": "menu.workspaces.addNewWorkspace",
4312 "start": {
4313 "column": 19,
3883 "line": 185 4314 "line": 185
4315 }
4316 },
4317 {
4318 "defaultMessage": "!!!Open workspace drawer",
4319 "end": {
4320 "column": 3,
4321 "line": 192
4322 },
4323 "file": "src/lib/Menu.js",
4324 "id": "menu.workspaces.openWorkspaceDrawer",
4325 "start": {
4326 "column": 23,
4327 "line": 189
4328 }
4329 },
4330 {
4331 "defaultMessage": "!!!Close workspace drawer",
4332 "end": {
4333 "column": 3,
4334 "line": 196
4335 },
4336 "file": "src/lib/Menu.js",
4337 "id": "menu.workspaces.closeWorkspaceDrawer",
4338 "start": {
4339 "column": 24,
4340 "line": 193
4341 }
4342 },
4343 {
4344 "defaultMessage": "!!!Activate next service...",
4345 "end": {
4346 "column": 3,
4347 "line": 200
3884 }, 4348 },
3885 "file": "src/lib/Menu.js", 4349 "file": "src/lib/Menu.js",
3886 "id": "menu.services.setNextServiceActive", 4350 "id": "menu.services.setNextServiceActive",
3887 "start": { 4351 "start": {
3888 "column": 23, 4352 "column": 23,
3889 "line": 182 4353 "line": 197
3890 } 4354 }
3891 }, 4355 },
3892 { 4356 {
3893 "defaultMessage": "!!!Activate previous service...", 4357 "defaultMessage": "!!!Activate previous service...",
3894 "end": { 4358 "end": {
3895 "column": 3, 4359 "column": 3,
3896 "line": 189 4360 "line": 204
3897 }, 4361 },
3898 "file": "src/lib/Menu.js", 4362 "file": "src/lib/Menu.js",
3899 "id": "menu.services.activatePreviousService", 4363 "id": "menu.services.activatePreviousService",
3900 "start": { 4364 "start": {
3901 "column": 27, 4365 "column": 27,
3902 "line": 186 4366 "line": 201
3903 } 4367 }
3904 }, 4368 },
3905 { 4369 {
3906 "defaultMessage": "!!!Disable notifications & audio", 4370 "defaultMessage": "!!!Disable notifications & audio",
3907 "end": { 4371 "end": {
3908 "column": 3, 4372 "column": 3,
3909 "line": 193 4373 "line": 208
3910 }, 4374 },
3911 "file": "src/lib/Menu.js", 4375 "file": "src/lib/Menu.js",
3912 "id": "sidebar.muteApp", 4376 "id": "sidebar.muteApp",
3913 "start": { 4377 "start": {
3914 "column": 11, 4378 "column": 11,
3915 "line": 190 4379 "line": 205
3916 } 4380 }
3917 }, 4381 },
3918 { 4382 {
3919 "defaultMessage": "!!!Enable notifications & audio", 4383 "defaultMessage": "!!!Enable notifications & audio",
3920 "end": { 4384 "end": {
3921 "column": 3, 4385 "column": 3,
3922 "line": 197 4386 "line": 212
3923 }, 4387 },
3924 "file": "src/lib/Menu.js", 4388 "file": "src/lib/Menu.js",
3925 "id": "sidebar.unmuteApp", 4389 "id": "sidebar.unmuteApp",
3926 "start": { 4390 "start": {
3927 "column": 13, 4391 "column": 13,
3928 "line": 194 4392 "line": 209
4393 }
4394 },
4395 {
4396 "defaultMessage": "!!!Workspaces",
4397 "end": {
4398 "column": 3,
4399 "line": 216
4400 },
4401 "file": "src/lib/Menu.js",
4402 "id": "menu.workspaces",
4403 "start": {
4404 "column": 14,
4405 "line": 213
4406 }
4407 },
4408 {
4409 "defaultMessage": "!!!Default",
4410 "end": {
4411 "column": 3,
4412 "line": 220
4413 },
4414 "file": "src/lib/Menu.js",
4415 "id": "menu.workspaces.defaultWorkspace",
4416 "start": {
4417 "column": 20,
4418 "line": 217
3929 } 4419 }
3930 } 4420 }
3931 ], 4421 ],
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 06cda4aca..84a71117a 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -87,6 +87,11 @@
87 "menu.window": "Window", 87 "menu.window": "Window",
88 "menu.window.close": "Close", 88 "menu.window.close": "Close",
89 "menu.window.minimize": "Minimize", 89 "menu.window.minimize": "Minimize",
90 "menu.workspaces": "Workspaces",
91 "menu.workspaces.addNewWorkspace": "Add New Workspace...",
92 "menu.workspaces.closeWorkspaceDrawer": "Close workspace drawer",
93 "menu.workspaces.defaultWorkspace": "All services",
94 "menu.workspaces.openWorkspaceDrawer": "Open workspace drawer",
90 "password.email.label": "Email address", 95 "password.email.label": "Email address",
91 "password.headline": "Reset password", 96 "password.headline": "Reset password",
92 "password.link.login": "Sign in to your account", 97 "password.link.login": "Sign in to your account",
@@ -110,6 +115,7 @@
110 "service.errorHandler.headline": "Oh no!", 115 "service.errorHandler.headline": "Oh no!",
111 "service.errorHandler.message": "Error", 116 "service.errorHandler.message": "Error",
112 "service.errorHandler.text": "{name} has failed to load.", 117 "service.errorHandler.text": "{name} has failed to load.",
118 "service.webviewLoader.loading": "Loading",
113 "services.getStarted": "Get started", 119 "services.getStarted": "Get started",
114 "services.welcome": "Welcome to Franz", 120 "services.welcome": "Welcome to Franz",
115 "settings.account.account.editButton": "Edit account", 121 "settings.account.account.editButton": "Edit account",
@@ -169,6 +175,7 @@
169 "settings.navigation.logout": "Logout", 175 "settings.navigation.logout": "Logout",
170 "settings.navigation.settings": "Settings", 176 "settings.navigation.settings": "Settings",
171 "settings.navigation.yourServices": "Your services", 177 "settings.navigation.yourServices": "Your services",
178 "settings.navigation.yourWorkspaces": "Your workspaces",
172 "settings.recipes.all": "All services", 179 "settings.recipes.all": "All services",
173 "settings.recipes.dev": "Development", 180 "settings.recipes.dev": "Development",
174 "settings.recipes.headline": "Available services", 181 "settings.recipes.headline": "Available services",
@@ -235,8 +242,25 @@
235 "settings.user.form.firstname": "First Name", 242 "settings.user.form.firstname": "First Name",
236 "settings.user.form.lastname": "Last Name", 243 "settings.user.form.lastname": "Last Name",
237 "settings.user.form.newPassword": "New password", 244 "settings.user.form.newPassword": "New password",
245 "settings.workspace.add.form.name": "Name",
246 "settings.workspace.add.form.submitButton": "Create workspace",
247 "settings.workspace.form.buttonDelete": "Delete workspace",
248 "settings.workspace.form.buttonSave": "Save workspace",
249 "settings.workspace.form.name": "Name",
250 "settings.workspace.form.servicesInWorkspaceHeadline": "Services in this Workspace",
251 "settings.workspace.form.yourWorkspaces": "Your workspaces",
252 "settings.workspaces.deletedInfo": "Workspace has been deleted",
253 "settings.workspaces.headline": "Your workspaces",
254 "settings.workspaces.noWorkspacesAdded": "You haven't added any workspaces yet.",
255 "settings.workspaces.tryReloadWorkspaces": "Try again",
256 "settings.workspaces.updatedInfo": "Your changes have been saved",
257 "settings.workspaces.workspaceFeatureHeadline": "Less is More: Introducing Franz Workspaces",
258 "settings.workspaces.workspaceFeatureInfo": "Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time. You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.",
259 "settings.workspaces.workspacesRequestFailed": "Could not load your workspaces",
238 "sidebar.addNewService": "Add new service", 260 "sidebar.addNewService": "Add new service",
261 "sidebar.closeWorkspaceDrawer": "Close workspace drawer",
239 "sidebar.muteApp": "Disable notifications & audio", 262 "sidebar.muteApp": "Disable notifications & audio",
263 "sidebar.openWorkspaceDrawer": "Open workspace drawer",
240 "sidebar.settings": "Settings", 264 "sidebar.settings": "Settings",
241 "sidebar.unmuteApp": "Enable notifications & audio", 265 "sidebar.unmuteApp": "Enable notifications & audio",
242 "signup.company.label": "Company", 266 "signup.company.label": "Company",
@@ -281,5 +305,16 @@
281 "validation.required": "{field} is required", 305 "validation.required": "{field} is required",
282 "validation.url": "{field} is not a valid URL", 306 "validation.url": "{field} is not a valid URL",
283 "welcome.loginButton": "Login to your account", 307 "welcome.loginButton": "Login to your account",
284 "welcome.signupButton": "Create a free account" 308 "welcome.signupButton": "Create a free account",
285} 309 "workspaceDrawer.addNewWorkspaceLabel": "Add new workspace",
310 "workspaceDrawer.allServices": "All services",
311 "workspaceDrawer.headline": "Workspaces",
312 "workspaceDrawer.item.contextMenuEdit": "edit",
313 "workspaceDrawer.item.noServicesAddedYet": "No services added yet",
314 "workspaceDrawer.premiumCtaButtonLabel": "Create your first workspace",
315 "workspaceDrawer.proFeatureBadge": "Premium feature",
316 "workspaceDrawer.reactivatePremiumAccountLabel": "Reactivate premium account",
317 "workspaceDrawer.workspaceFeatureInfo": "<p>Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time.</p><p>You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.</p>",
318 "workspaceDrawer.workspacesSettingsTooltip": "Edit workspaces settings",
319 "workspaces.switchingIndicator.switchingTo": "Switching to"
320} \ No newline at end of file
diff --git a/src/i18n/messages/src/components/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json
index 07603d062..92593ed5c 100644
--- a/src/i18n/messages/src/components/layout/AppLayout.json
+++ b/src/i18n/messages/src/components/layout/AppLayout.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Your services have been updated.", 4 "defaultMessage": "!!!Your services have been updated.",
5 "file": "src/components/layout/AppLayout.js", 5 "file": "src/components/layout/AppLayout.js",
6 "start": { 6 "start": {
7 "line": 22, 7 "line": 25,
8 "column": 19 8 "column": 19
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 25, 11 "line": 28,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!A new update for Franz is available.", 17 "defaultMessage": "!!!A new update for Franz is available.",
18 "file": "src/components/layout/AppLayout.js", 18 "file": "src/components/layout/AppLayout.js",
19 "start": { 19 "start": {
20 "line": 26, 20 "line": 29,
21 "column": 19 21 "column": 19
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 29, 24 "line": 32,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Reload services", 30 "defaultMessage": "!!!Reload services",
31 "file": "src/components/layout/AppLayout.js", 31 "file": "src/components/layout/AppLayout.js",
32 "start": { 32 "start": {
33 "line": 30, 33 "line": 33,
34 "column": 24 34 "column": 24
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 33, 37 "line": 36,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Changelog", 43 "defaultMessage": "!!!Changelog",
44 "file": "src/components/layout/AppLayout.js", 44 "file": "src/components/layout/AppLayout.js",
45 "start": { 45 "start": {
46 "line": 34, 46 "line": 37,
47 "column": 13 47 "column": 13
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 37, 50 "line": 40,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Restart & install update", 56 "defaultMessage": "!!!Restart & install update",
57 "file": "src/components/layout/AppLayout.js", 57 "file": "src/components/layout/AppLayout.js",
58 "start": { 58 "start": {
59 "line": 38, 59 "line": 41,
60 "column": 23 60 "column": 23
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 41, 63 "line": 44,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Could not load services and user information", 69 "defaultMessage": "!!!Could not load services and user information",
70 "file": "src/components/layout/AppLayout.js", 70 "file": "src/components/layout/AppLayout.js",
71 "start": { 71 "start": {
72 "line": 42, 72 "line": 45,
73 "column": 26 73 "column": 26
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 45, 76 "line": 48,
77 "column": 3 77 "column": 3
78 } 78 }
79 } 79 }
diff --git a/src/i18n/messages/src/components/layout/Sidebar.json b/src/i18n/messages/src/components/layout/Sidebar.json
index 7aa00a186..d67adc96e 100644
--- a/src/i18n/messages/src/components/layout/Sidebar.json
+++ b/src/i18n/messages/src/components/layout/Sidebar.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Settings", 4 "defaultMessage": "!!!Settings",
5 "file": "src/components/layout/Sidebar.js", 5 "file": "src/components/layout/Sidebar.js",
6 "start": { 6 "start": {
7 "line": 11, 7 "line": 13,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 14, 11 "line": 16,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Add new service", 17 "defaultMessage": "!!!Add new service",
18 "file": "src/components/layout/Sidebar.js", 18 "file": "src/components/layout/Sidebar.js",
19 "start": { 19 "start": {
20 "line": 15, 20 "line": 17,
21 "column": 17 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 18, 24 "line": 20,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Disable notifications & audio", 30 "defaultMessage": "!!!Disable notifications & audio",
31 "file": "src/components/layout/Sidebar.js", 31 "file": "src/components/layout/Sidebar.js",
32 "start": { 32 "start": {
33 "line": 19, 33 "line": 21,
34 "column": 8 34 "column": 8
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 22, 37 "line": 24,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,37 @@
43 "defaultMessage": "!!!Enable notifications & audio", 43 "defaultMessage": "!!!Enable notifications & audio",
44 "file": "src/components/layout/Sidebar.js", 44 "file": "src/components/layout/Sidebar.js",
45 "start": { 45 "start": {
46 "line": 23, 46 "line": 25,
47 "column": 10 47 "column": 10
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 26, 50 "line": 28,
51 "column": 3
52 }
53 },
54 {
55 "id": "sidebar.openWorkspaceDrawer",
56 "defaultMessage": "!!!Open workspace drawer",
57 "file": "src/components/layout/Sidebar.js",
58 "start": {
59 "line": 29,
60 "column": 23
61 },
62 "end": {
63 "line": 32,
64 "column": 3
65 }
66 },
67 {
68 "id": "sidebar.closeWorkspaceDrawer",
69 "defaultMessage": "!!!Close workspace drawer",
70 "file": "src/components/layout/Sidebar.js",
71 "start": {
72 "line": 33,
73 "column": 24
74 },
75 "end": {
76 "line": 36,
51 "column": 3 77 "column": 3
52 } 78 }
53 } 79 }
diff --git a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json
index 785ce9f29..77b0ed8a4 100644
--- a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json
+++ b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Available services", 4 "defaultMessage": "!!!Available services",
5 "file": "src/components/settings/navigation/SettingsNavigation.js", 5 "file": "src/components/settings/navigation/SettingsNavigation.js",
6 "start": { 6 "start": {
7 "line": 9, 7 "line": 12,
8 "column": 21 8 "column": 21
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 12, 11 "line": 15,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,24 @@
17 "defaultMessage": "!!!Your services", 17 "defaultMessage": "!!!Your services",
18 "file": "src/components/settings/navigation/SettingsNavigation.js", 18 "file": "src/components/settings/navigation/SettingsNavigation.js",
19 "start": { 19 "start": {
20 "line": 13, 20 "line": 16,
21 "column": 16 21 "column": 16
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 16, 24 "line": 19,
25 "column": 3
26 }
27 },
28 {
29 "id": "settings.navigation.yourWorkspaces",
30 "defaultMessage": "!!!Your workspaces",
31 "file": "src/components/settings/navigation/SettingsNavigation.js",
32 "start": {
33 "line": 20,
34 "column": 18
35 },
36 "end": {
37 "line": 23,
25 "column": 3 38 "column": 3
26 } 39 }
27 }, 40 },
@@ -30,11 +43,11 @@
30 "defaultMessage": "!!!Account", 43 "defaultMessage": "!!!Account",
31 "file": "src/components/settings/navigation/SettingsNavigation.js", 44 "file": "src/components/settings/navigation/SettingsNavigation.js",
32 "start": { 45 "start": {
33 "line": 17, 46 "line": 24,
34 "column": 11 47 "column": 11
35 }, 48 },
36 "end": { 49 "end": {
37 "line": 20, 50 "line": 27,
38 "column": 3 51 "column": 3
39 } 52 }
40 }, 53 },
@@ -43,11 +56,11 @@
43 "defaultMessage": "!!!Settings", 56 "defaultMessage": "!!!Settings",
44 "file": "src/components/settings/navigation/SettingsNavigation.js", 57 "file": "src/components/settings/navigation/SettingsNavigation.js",
45 "start": { 58 "start": {
46 "line": 21, 59 "line": 28,
47 "column": 12 60 "column": 12
48 }, 61 },
49 "end": { 62 "end": {
50 "line": 24, 63 "line": 31,
51 "column": 3 64 "column": 3
52 } 65 }
53 }, 66 },
@@ -56,11 +69,11 @@
56 "defaultMessage": "!!!Invite Friends", 69 "defaultMessage": "!!!Invite Friends",
57 "file": "src/components/settings/navigation/SettingsNavigation.js", 70 "file": "src/components/settings/navigation/SettingsNavigation.js",
58 "start": { 71 "start": {
59 "line": 25, 72 "line": 32,
60 "column": 17 73 "column": 17
61 }, 74 },
62 "end": { 75 "end": {
63 "line": 28, 76 "line": 35,
64 "column": 3 77 "column": 3
65 } 78 }
66 }, 79 },
@@ -69,11 +82,11 @@
69 "defaultMessage": "!!!Logout", 82 "defaultMessage": "!!!Logout",
70 "file": "src/components/settings/navigation/SettingsNavigation.js", 83 "file": "src/components/settings/navigation/SettingsNavigation.js",
71 "start": { 84 "start": {
72 "line": 29, 85 "line": 36,
73 "column": 10 86 "column": 10
74 }, 87 },
75 "end": { 88 "end": {
76 "line": 32, 89 "line": 39,
77 "column": 3 90 "column": 3
78 } 91 }
79 } 92 }
diff --git a/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json b/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json
index 582d546fa..320d3ca3e 100644
--- a/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json
+++ b/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Upgrade account", 4 "defaultMessage": "!!!Upgrade account",
5 "file": "src/components/ui/PremiumFeatureContainer/index.js", 5 "file": "src/components/ui/PremiumFeatureContainer/index.js",
6 "start": { 6 "start": {
7 "line": 14, 7 "line": 15,
8 "column": 10 8 "column": 10
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 17, 11 "line": 18,
12 "column": 3 12 "column": 3
13 } 13 }
14 } 14 }
diff --git a/src/i18n/messages/src/components/ui/WebviewLoader/index.json b/src/i18n/messages/src/components/ui/WebviewLoader/index.json
new file mode 100644
index 000000000..ef3e4b593
--- /dev/null
+++ b/src/i18n/messages/src/components/ui/WebviewLoader/index.json
@@ -0,0 +1,15 @@
1[
2 {
3 "id": "service.webviewLoader.loading",
4 "defaultMessage": "!!!Loading",
5 "file": "src/components/ui/WebviewLoader/index.js",
6 "start": {
7 "line": 11,
8 "column": 11
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 }
15] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/CreateWorkspaceForm.json b/src/i18n/messages/src/features/workspaces/components/CreateWorkspaceForm.json
new file mode 100644
index 000000000..f62bac42c
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/CreateWorkspaceForm.json
@@ -0,0 +1,28 @@
1[
2 {
3 "id": "settings.workspace.add.form.submitButton",
4 "defaultMessage": "!!!Create workspace",
5 "file": "src/features/workspaces/components/CreateWorkspaceForm.js",
6 "start": {
7 "line": 13,
8 "column": 16
9 },
10 "end": {
11 "line": 16,
12 "column": 3
13 }
14 },
15 {
16 "id": "settings.workspace.add.form.name",
17 "defaultMessage": "!!!Name",
18 "file": "src/features/workspaces/components/CreateWorkspaceForm.js",
19 "start": {
20 "line": 17,
21 "column": 8
22 },
23 "end": {
24 "line": 20,
25 "column": 3
26 }
27 }
28] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/EditWorkspaceForm.json b/src/i18n/messages/src/features/workspaces/components/EditWorkspaceForm.json
new file mode 100644
index 000000000..7b0c3e1ce
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/EditWorkspaceForm.json
@@ -0,0 +1,67 @@
1[
2 {
3 "id": "settings.workspace.form.buttonDelete",
4 "defaultMessage": "!!!Delete workspace",
5 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
6 "start": {
7 "line": 19,
8 "column": 16
9 },
10 "end": {
11 "line": 22,
12 "column": 3
13 }
14 },
15 {
16 "id": "settings.workspace.form.buttonSave",
17 "defaultMessage": "!!!Save workspace",
18 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
19 "start": {
20 "line": 23,
21 "column": 14
22 },
23 "end": {
24 "line": 26,
25 "column": 3
26 }
27 },
28 {
29 "id": "settings.workspace.form.name",
30 "defaultMessage": "!!!Name",
31 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
32 "start": {
33 "line": 27,
34 "column": 8
35 },
36 "end": {
37 "line": 30,
38 "column": 3
39 }
40 },
41 {
42 "id": "settings.workspace.form.yourWorkspaces",
43 "defaultMessage": "!!!Your workspaces",
44 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
45 "start": {
46 "line": 31,
47 "column": 18
48 },
49 "end": {
50 "line": 34,
51 "column": 3
52 }
53 },
54 {
55 "id": "settings.workspace.form.servicesInWorkspaceHeadline",
56 "defaultMessage": "!!!Services in this Workspace",
57 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
58 "start": {
59 "line": 35,
60 "column": 31
61 },
62 "end": {
63 "line": 38,
64 "column": 3
65 }
66 }
67] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json
new file mode 100644
index 000000000..9f0935620
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json
@@ -0,0 +1,106 @@
1[
2 {
3 "id": "workspaceDrawer.headline",
4 "defaultMessage": "!!!Workspaces",
5 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
6 "start": {
7 "line": 16,
8 "column": 12
9 },
10 "end": {
11 "line": 19,
12 "column": 3
13 }
14 },
15 {
16 "id": "workspaceDrawer.allServices",
17 "defaultMessage": "!!!All services",
18 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
19 "start": {
20 "line": 20,
21 "column": 15
22 },
23 "end": {
24 "line": 23,
25 "column": 3
26 }
27 },
28 {
29 "id": "workspaceDrawer.workspacesSettingsTooltip",
30 "defaultMessage": "!!!Workspaces settings",
31 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
32 "start": {
33 "line": 24,
34 "column": 29
35 },
36 "end": {
37 "line": 27,
38 "column": 3
39 }
40 },
41 {
42 "id": "workspaceDrawer.workspaceFeatureInfo",
43 "defaultMessage": "!!!Info about workspace feature",
44 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
45 "start": {
46 "line": 28,
47 "column": 24
48 },
49 "end": {
50 "line": 31,
51 "column": 3
52 }
53 },
54 {
55 "id": "workspaceDrawer.premiumCtaButtonLabel",
56 "defaultMessage": "!!!Create your first workspace",
57 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
58 "start": {
59 "line": 32,
60 "column": 25
61 },
62 "end": {
63 "line": 35,
64 "column": 3
65 }
66 },
67 {
68 "id": "workspaceDrawer.reactivatePremiumAccountLabel",
69 "defaultMessage": "!!!Reactivate premium account",
70 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
71 "start": {
72 "line": 36,
73 "column": 28
74 },
75 "end": {
76 "line": 39,
77 "column": 3
78 }
79 },
80 {
81 "id": "workspaceDrawer.addNewWorkspaceLabel",
82 "defaultMessage": "!!!add new workspace",
83 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
84 "start": {
85 "line": 40,
86 "column": 24
87 },
88 "end": {
89 "line": 43,
90 "column": 3
91 }
92 },
93 {
94 "id": "workspaceDrawer.proFeatureBadge",
95 "defaultMessage": "!!!Premium feature",
96 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
97 "start": {
98 "line": 44,
99 "column": 23
100 },
101 "end": {
102 "line": 47,
103 "column": 3
104 }
105 }
106] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json
new file mode 100644
index 000000000..4ff190606
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json
@@ -0,0 +1,28 @@
1[
2 {
3 "id": "workspaceDrawer.item.noServicesAddedYet",
4 "defaultMessage": "!!!No services added yet",
5 "file": "src/features/workspaces/components/WorkspaceDrawerItem.js",
6 "start": {
7 "line": 12,
8 "column": 22
9 },
10 "end": {
11 "line": 15,
12 "column": 3
13 }
14 },
15 {
16 "id": "workspaceDrawer.item.contextMenuEdit",
17 "defaultMessage": "!!!edit",
18 "file": "src/features/workspaces/components/WorkspaceDrawerItem.js",
19 "start": {
20 "line": 16,
21 "column": 19
22 },
23 "end": {
24 "line": 19,
25 "column": 3
26 }
27 }
28] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceSwitchingIndicator.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceSwitchingIndicator.json
new file mode 100644
index 000000000..4f3e6d55c
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceSwitchingIndicator.json
@@ -0,0 +1,15 @@
1[
2 {
3 "id": "workspaces.switchingIndicator.switchingTo",
4 "defaultMessage": "!!!Switching to",
5 "file": "src/features/workspaces/components/WorkspaceSwitchingIndicator.js",
6 "start": {
7 "line": 12,
8 "column": 15
9 },
10 "end": {
11 "line": 15,
12 "column": 3
13 }
14 }
15] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json
new file mode 100644
index 000000000..ef8f1bebc
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json
@@ -0,0 +1,106 @@
1[
2 {
3 "id": "settings.workspaces.headline",
4 "defaultMessage": "!!!Your workspaces",
5 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
6 "start": {
7 "line": 17,
8 "column": 12
9 },
10 "end": {
11 "line": 20,
12 "column": 3
13 }
14 },
15 {
16 "id": "settings.workspaces.noWorkspacesAdded",
17 "defaultMessage": "!!!You haven't added any workspaces yet.",
18 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
19 "start": {
20 "line": 21,
21 "column": 19
22 },
23 "end": {
24 "line": 24,
25 "column": 3
26 }
27 },
28 {
29 "id": "settings.workspaces.workspacesRequestFailed",
30 "defaultMessage": "!!!Could not load your workspaces",
31 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
32 "start": {
33 "line": 25,
34 "column": 27
35 },
36 "end": {
37 "line": 28,
38 "column": 3
39 }
40 },
41 {
42 "id": "settings.workspaces.tryReloadWorkspaces",
43 "defaultMessage": "!!!Try again",
44 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
45 "start": {
46 "line": 29,
47 "column": 23
48 },
49 "end": {
50 "line": 32,
51 "column": 3
52 }
53 },
54 {
55 "id": "settings.workspaces.updatedInfo",
56 "defaultMessage": "!!!Your changes have been saved",
57 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
58 "start": {
59 "line": 33,
60 "column": 15
61 },
62 "end": {
63 "line": 36,
64 "column": 3
65 }
66 },
67 {
68 "id": "settings.workspaces.deletedInfo",
69 "defaultMessage": "!!!Workspace has been deleted",
70 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
71 "start": {
72 "line": 37,
73 "column": 15
74 },
75 "end": {
76 "line": 40,
77 "column": 3
78 }
79 },
80 {
81 "id": "settings.workspaces.workspaceFeatureInfo",
82 "defaultMessage": "!!!Info about workspace feature",
83 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
84 "start": {
85 "line": 41,
86 "column": 24
87 },
88 "end": {
89 "line": 44,
90 "column": 3
91 }
92 },
93 {
94 "id": "settings.workspaces.workspaceFeatureHeadline",
95 "defaultMessage": "!!!Less is More: Introducing Franz Workspaces",
96 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
97 "start": {
98 "line": 45,
99 "column": 28
100 },
101 "end": {
102 "line": 48,
103 "column": 3
104 }
105 }
106] \ No newline at end of file
diff --git a/src/i18n/messages/src/lib/Menu.json b/src/i18n/messages/src/lib/Menu.json
index 9314f5cce..3889d39e0 100644
--- a/src/i18n/messages/src/lib/Menu.json
+++ b/src/i18n/messages/src/lib/Menu.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Edit", 4 "defaultMessage": "!!!Edit",
5 "file": "src/lib/Menu.js", 5 "file": "src/lib/Menu.js",
6 "start": { 6 "start": {
7 "line": 10, 7 "line": 13,
8 "column": 8 8 "column": 8
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 13, 11 "line": 16,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Undo", 17 "defaultMessage": "!!!Undo",
18 "file": "src/lib/Menu.js", 18 "file": "src/lib/Menu.js",
19 "start": { 19 "start": {
20 "line": 14, 20 "line": 17,
21 "column": 8 21 "column": 8
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 17, 24 "line": 20,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Redo", 30 "defaultMessage": "!!!Redo",
31 "file": "src/lib/Menu.js", 31 "file": "src/lib/Menu.js",
32 "start": { 32 "start": {
33 "line": 18, 33 "line": 21,
34 "column": 8 34 "column": 8
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 21, 37 "line": 24,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Cut", 43 "defaultMessage": "!!!Cut",
44 "file": "src/lib/Menu.js", 44 "file": "src/lib/Menu.js",
45 "start": { 45 "start": {
46 "line": 22, 46 "line": 25,
47 "column": 7 47 "column": 7
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 25, 50 "line": 28,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Copy", 56 "defaultMessage": "!!!Copy",
57 "file": "src/lib/Menu.js", 57 "file": "src/lib/Menu.js",
58 "start": { 58 "start": {
59 "line": 26, 59 "line": 29,
60 "column": 8 60 "column": 8
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 29, 63 "line": 32,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Paste", 69 "defaultMessage": "!!!Paste",
70 "file": "src/lib/Menu.js", 70 "file": "src/lib/Menu.js",
71 "start": { 71 "start": {
72 "line": 30, 72 "line": 33,
73 "column": 9 73 "column": 9
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 33, 76 "line": 36,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Paste And Match Style", 82 "defaultMessage": "!!!Paste And Match Style",
83 "file": "src/lib/Menu.js", 83 "file": "src/lib/Menu.js",
84 "start": { 84 "start": {
85 "line": 34, 85 "line": 37,
86 "column": 22 86 "column": 22
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 37, 89 "line": 40,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Delete", 95 "defaultMessage": "!!!Delete",
96 "file": "src/lib/Menu.js", 96 "file": "src/lib/Menu.js",
97 "start": { 97 "start": {
98 "line": 38, 98 "line": 41,
99 "column": 10 99 "column": 10
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 41, 102 "line": 44,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Select All", 108 "defaultMessage": "!!!Select All",
109 "file": "src/lib/Menu.js", 109 "file": "src/lib/Menu.js",
110 "start": { 110 "start": {
111 "line": 42, 111 "line": 45,
112 "column": 13 112 "column": 13
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 45, 115 "line": 48,
116 "column": 3 116 "column": 3
117 } 117 }
118 }, 118 },
@@ -121,11 +121,11 @@
121 "defaultMessage": "!!!Speech", 121 "defaultMessage": "!!!Speech",
122 "file": "src/lib/Menu.js", 122 "file": "src/lib/Menu.js",
123 "start": { 123 "start": {
124 "line": 46, 124 "line": 49,
125 "column": 10 125 "column": 10
126 }, 126 },
127 "end": { 127 "end": {
128 "line": 49, 128 "line": 52,
129 "column": 3 129 "column": 3
130 } 130 }
131 }, 131 },
@@ -134,11 +134,11 @@
134 "defaultMessage": "!!!Start Speaking", 134 "defaultMessage": "!!!Start Speaking",
135 "file": "src/lib/Menu.js", 135 "file": "src/lib/Menu.js",
136 "start": { 136 "start": {
137 "line": 50, 137 "line": 53,
138 "column": 17 138 "column": 17
139 }, 139 },
140 "end": { 140 "end": {
141 "line": 53, 141 "line": 56,
142 "column": 3 142 "column": 3
143 } 143 }
144 }, 144 },
@@ -147,11 +147,11 @@
147 "defaultMessage": "!!!Stop Speaking", 147 "defaultMessage": "!!!Stop Speaking",
148 "file": "src/lib/Menu.js", 148 "file": "src/lib/Menu.js",
149 "start": { 149 "start": {
150 "line": 54, 150 "line": 57,
151 "column": 16 151 "column": 16
152 }, 152 },
153 "end": { 153 "end": {
154 "line": 57, 154 "line": 60,
155 "column": 3 155 "column": 3
156 } 156 }
157 }, 157 },
@@ -160,11 +160,11 @@
160 "defaultMessage": "!!!Start Dictation", 160 "defaultMessage": "!!!Start Dictation",
161 "file": "src/lib/Menu.js", 161 "file": "src/lib/Menu.js",
162 "start": { 162 "start": {
163 "line": 58, 163 "line": 61,
164 "column": 18 164 "column": 18
165 }, 165 },
166 "end": { 166 "end": {
167 "line": 61, 167 "line": 64,
168 "column": 3 168 "column": 3
169 } 169 }
170 }, 170 },
@@ -173,11 +173,11 @@
173 "defaultMessage": "!!!Emoji & Symbols", 173 "defaultMessage": "!!!Emoji & Symbols",
174 "file": "src/lib/Menu.js", 174 "file": "src/lib/Menu.js",
175 "start": { 175 "start": {
176 "line": 62, 176 "line": 65,
177 "column": 16 177 "column": 16
178 }, 178 },
179 "end": { 179 "end": {
180 "line": 65, 180 "line": 68,
181 "column": 3 181 "column": 3
182 } 182 }
183 }, 183 },
@@ -186,11 +186,11 @@
186 "defaultMessage": "!!!Actual Size", 186 "defaultMessage": "!!!Actual Size",
187 "file": "src/lib/Menu.js", 187 "file": "src/lib/Menu.js",
188 "start": { 188 "start": {
189 "line": 66, 189 "line": 69,
190 "column": 13 190 "column": 13
191 }, 191 },
192 "end": { 192 "end": {
193 "line": 69, 193 "line": 72,
194 "column": 3 194 "column": 3
195 } 195 }
196 }, 196 },
@@ -199,11 +199,11 @@
199 "defaultMessage": "!!!Zoom In", 199 "defaultMessage": "!!!Zoom In",
200 "file": "src/lib/Menu.js", 200 "file": "src/lib/Menu.js",
201 "start": { 201 "start": {
202 "line": 70, 202 "line": 73,
203 "column": 10 203 "column": 10
204 }, 204 },
205 "end": { 205 "end": {
206 "line": 73, 206 "line": 76,
207 "column": 3 207 "column": 3
208 } 208 }
209 }, 209 },
@@ -212,11 +212,11 @@
212 "defaultMessage": "!!!Zoom Out", 212 "defaultMessage": "!!!Zoom Out",
213 "file": "src/lib/Menu.js", 213 "file": "src/lib/Menu.js",
214 "start": { 214 "start": {
215 "line": 74, 215 "line": 77,
216 "column": 11 216 "column": 11
217 }, 217 },
218 "end": { 218 "end": {
219 "line": 77, 219 "line": 80,
220 "column": 3 220 "column": 3
221 } 221 }
222 }, 222 },
@@ -225,11 +225,11 @@
225 "defaultMessage": "!!!Enter Full Screen", 225 "defaultMessage": "!!!Enter Full Screen",
226 "file": "src/lib/Menu.js", 226 "file": "src/lib/Menu.js",
227 "start": { 227 "start": {
228 "line": 78, 228 "line": 81,
229 "column": 19 229 "column": 19
230 }, 230 },
231 "end": { 231 "end": {
232 "line": 81, 232 "line": 84,
233 "column": 3 233 "column": 3
234 } 234 }
235 }, 235 },
@@ -238,11 +238,11 @@
238 "defaultMessage": "!!!Exit Full Screen", 238 "defaultMessage": "!!!Exit Full Screen",
239 "file": "src/lib/Menu.js", 239 "file": "src/lib/Menu.js",
240 "start": { 240 "start": {
241 "line": 82, 241 "line": 85,
242 "column": 18 242 "column": 18
243 }, 243 },
244 "end": { 244 "end": {
245 "line": 85, 245 "line": 88,
246 "column": 3 246 "column": 3
247 } 247 }
248 }, 248 },
@@ -251,11 +251,11 @@
251 "defaultMessage": "!!!Toggle Full Screen", 251 "defaultMessage": "!!!Toggle Full Screen",
252 "file": "src/lib/Menu.js", 252 "file": "src/lib/Menu.js",
253 "start": { 253 "start": {
254 "line": 86, 254 "line": 89,
255 "column": 20 255 "column": 20
256 }, 256 },
257 "end": { 257 "end": {
258 "line": 89, 258 "line": 92,
259 "column": 3 259 "column": 3
260 } 260 }
261 }, 261 },
@@ -264,11 +264,11 @@
264 "defaultMessage": "!!!Toggle Developer Tools", 264 "defaultMessage": "!!!Toggle Developer Tools",
265 "file": "src/lib/Menu.js", 265 "file": "src/lib/Menu.js",
266 "start": { 266 "start": {
267 "line": 90, 267 "line": 93,
268 "column": 18 268 "column": 18
269 }, 269 },
270 "end": { 270 "end": {
271 "line": 93, 271 "line": 96,
272 "column": 3 272 "column": 3
273 } 273 }
274 }, 274 },
@@ -277,11 +277,11 @@
277 "defaultMessage": "!!!Toggle Service Developer Tools", 277 "defaultMessage": "!!!Toggle Service Developer Tools",
278 "file": "src/lib/Menu.js", 278 "file": "src/lib/Menu.js",
279 "start": { 279 "start": {
280 "line": 94, 280 "line": 97,
281 "column": 25 281 "column": 25
282 }, 282 },
283 "end": { 283 "end": {
284 "line": 97, 284 "line": 100,
285 "column": 3 285 "column": 3
286 } 286 }
287 }, 287 },
@@ -290,11 +290,11 @@
290 "defaultMessage": "!!!Reload Service", 290 "defaultMessage": "!!!Reload Service",
291 "file": "src/lib/Menu.js", 291 "file": "src/lib/Menu.js",
292 "start": { 292 "start": {
293 "line": 98, 293 "line": 101,
294 "column": 17 294 "column": 17
295 }, 295 },
296 "end": { 296 "end": {
297 "line": 101, 297 "line": 104,
298 "column": 3 298 "column": 3
299 } 299 }
300 }, 300 },
@@ -303,11 +303,11 @@
303 "defaultMessage": "!!!Reload Franz", 303 "defaultMessage": "!!!Reload Franz",
304 "file": "src/lib/Menu.js", 304 "file": "src/lib/Menu.js",
305 "start": { 305 "start": {
306 "line": 102, 306 "line": 105,
307 "column": 15 307 "column": 15
308 }, 308 },
309 "end": { 309 "end": {
310 "line": 105, 310 "line": 108,
311 "column": 3 311 "column": 3
312 } 312 }
313 }, 313 },
@@ -316,11 +316,11 @@
316 "defaultMessage": "!!!Minimize", 316 "defaultMessage": "!!!Minimize",
317 "file": "src/lib/Menu.js", 317 "file": "src/lib/Menu.js",
318 "start": { 318 "start": {
319 "line": 106, 319 "line": 109,
320 "column": 12 320 "column": 12
321 }, 321 },
322 "end": { 322 "end": {
323 "line": 109, 323 "line": 112,
324 "column": 3 324 "column": 3
325 } 325 }
326 }, 326 },
@@ -329,11 +329,11 @@
329 "defaultMessage": "!!!Close", 329 "defaultMessage": "!!!Close",
330 "file": "src/lib/Menu.js", 330 "file": "src/lib/Menu.js",
331 "start": { 331 "start": {
332 "line": 110, 332 "line": 113,
333 "column": 9 333 "column": 9
334 }, 334 },
335 "end": { 335 "end": {
336 "line": 113, 336 "line": 116,
337 "column": 3 337 "column": 3
338 } 338 }
339 }, 339 },
@@ -342,11 +342,11 @@
342 "defaultMessage": "!!!Learn More", 342 "defaultMessage": "!!!Learn More",
343 "file": "src/lib/Menu.js", 343 "file": "src/lib/Menu.js",
344 "start": { 344 "start": {
345 "line": 114, 345 "line": 117,
346 "column": 13 346 "column": 13
347 }, 347 },
348 "end": { 348 "end": {
349 "line": 117, 349 "line": 120,
350 "column": 3 350 "column": 3
351 } 351 }
352 }, 352 },
@@ -355,11 +355,11 @@
355 "defaultMessage": "!!!Changelog", 355 "defaultMessage": "!!!Changelog",
356 "file": "src/lib/Menu.js", 356 "file": "src/lib/Menu.js",
357 "start": { 357 "start": {
358 "line": 118, 358 "line": 121,
359 "column": 13 359 "column": 13
360 }, 360 },
361 "end": { 361 "end": {
362 "line": 121, 362 "line": 124,
363 "column": 3 363 "column": 3
364 } 364 }
365 }, 365 },
@@ -368,11 +368,11 @@
368 "defaultMessage": "!!!Support", 368 "defaultMessage": "!!!Support",
369 "file": "src/lib/Menu.js", 369 "file": "src/lib/Menu.js",
370 "start": { 370 "start": {
371 "line": 122, 371 "line": 125,
372 "column": 11 372 "column": 11
373 }, 373 },
374 "end": { 374 "end": {
375 "line": 125, 375 "line": 128,
376 "column": 3 376 "column": 3
377 } 377 }
378 }, 378 },
@@ -381,11 +381,11 @@
381 "defaultMessage": "!!!Terms of Service", 381 "defaultMessage": "!!!Terms of Service",
382 "file": "src/lib/Menu.js", 382 "file": "src/lib/Menu.js",
383 "start": { 383 "start": {
384 "line": 126, 384 "line": 129,
385 "column": 7 385 "column": 7
386 }, 386 },
387 "end": { 387 "end": {
388 "line": 129, 388 "line": 132,
389 "column": 3 389 "column": 3
390 } 390 }
391 }, 391 },
@@ -394,11 +394,11 @@
394 "defaultMessage": "!!!Privacy Statement", 394 "defaultMessage": "!!!Privacy Statement",
395 "file": "src/lib/Menu.js", 395 "file": "src/lib/Menu.js",
396 "start": { 396 "start": {
397 "line": 130, 397 "line": 133,
398 "column": 11 398 "column": 11
399 }, 399 },
400 "end": { 400 "end": {
401 "line": 133, 401 "line": 136,
402 "column": 3 402 "column": 3
403 } 403 }
404 }, 404 },
@@ -407,11 +407,11 @@
407 "defaultMessage": "!!!File", 407 "defaultMessage": "!!!File",
408 "file": "src/lib/Menu.js", 408 "file": "src/lib/Menu.js",
409 "start": { 409 "start": {
410 "line": 134, 410 "line": 137,
411 "column": 8 411 "column": 8
412 }, 412 },
413 "end": { 413 "end": {
414 "line": 137, 414 "line": 140,
415 "column": 3 415 "column": 3
416 } 416 }
417 }, 417 },
@@ -420,11 +420,11 @@
420 "defaultMessage": "!!!View", 420 "defaultMessage": "!!!View",
421 "file": "src/lib/Menu.js", 421 "file": "src/lib/Menu.js",
422 "start": { 422 "start": {
423 "line": 138, 423 "line": 141,
424 "column": 8 424 "column": 8
425 }, 425 },
426 "end": { 426 "end": {
427 "line": 141, 427 "line": 144,
428 "column": 3 428 "column": 3
429 } 429 }
430 }, 430 },
@@ -433,11 +433,11 @@
433 "defaultMessage": "!!!Services", 433 "defaultMessage": "!!!Services",
434 "file": "src/lib/Menu.js", 434 "file": "src/lib/Menu.js",
435 "start": { 435 "start": {
436 "line": 142, 436 "line": 145,
437 "column": 12 437 "column": 12
438 }, 438 },
439 "end": { 439 "end": {
440 "line": 145, 440 "line": 148,
441 "column": 3 441 "column": 3
442 } 442 }
443 }, 443 },
@@ -446,11 +446,11 @@
446 "defaultMessage": "!!!Window", 446 "defaultMessage": "!!!Window",
447 "file": "src/lib/Menu.js", 447 "file": "src/lib/Menu.js",
448 "start": { 448 "start": {
449 "line": 146, 449 "line": 149,
450 "column": 10 450 "column": 10
451 }, 451 },
452 "end": { 452 "end": {
453 "line": 149, 453 "line": 152,
454 "column": 3 454 "column": 3
455 } 455 }
456 }, 456 },
@@ -459,11 +459,11 @@
459 "defaultMessage": "!!!Help", 459 "defaultMessage": "!!!Help",
460 "file": "src/lib/Menu.js", 460 "file": "src/lib/Menu.js",
461 "start": { 461 "start": {
462 "line": 150, 462 "line": 153,
463 "column": 8 463 "column": 8
464 }, 464 },
465 "end": { 465 "end": {
466 "line": 153, 466 "line": 156,
467 "column": 3 467 "column": 3
468 } 468 }
469 }, 469 },
@@ -472,11 +472,11 @@
472 "defaultMessage": "!!!About Franz", 472 "defaultMessage": "!!!About Franz",
473 "file": "src/lib/Menu.js", 473 "file": "src/lib/Menu.js",
474 "start": { 474 "start": {
475 "line": 154, 475 "line": 157,
476 "column": 9 476 "column": 9
477 }, 477 },
478 "end": { 478 "end": {
479 "line": 157, 479 "line": 160,
480 "column": 3 480 "column": 3
481 } 481 }
482 }, 482 },
@@ -485,11 +485,11 @@
485 "defaultMessage": "!!!Settings", 485 "defaultMessage": "!!!Settings",
486 "file": "src/lib/Menu.js", 486 "file": "src/lib/Menu.js",
487 "start": { 487 "start": {
488 "line": 158, 488 "line": 161,
489 "column": 12 489 "column": 12
490 }, 490 },
491 "end": { 491 "end": {
492 "line": 161, 492 "line": 164,
493 "column": 3 493 "column": 3
494 } 494 }
495 }, 495 },
@@ -498,11 +498,11 @@
498 "defaultMessage": "!!!Hide", 498 "defaultMessage": "!!!Hide",
499 "file": "src/lib/Menu.js", 499 "file": "src/lib/Menu.js",
500 "start": { 500 "start": {
501 "line": 162, 501 "line": 165,
502 "column": 8 502 "column": 8
503 }, 503 },
504 "end": { 504 "end": {
505 "line": 165, 505 "line": 168,
506 "column": 3 506 "column": 3
507 } 507 }
508 }, 508 },
@@ -511,11 +511,11 @@
511 "defaultMessage": "!!!Hide Others", 511 "defaultMessage": "!!!Hide Others",
512 "file": "src/lib/Menu.js", 512 "file": "src/lib/Menu.js",
513 "start": { 513 "start": {
514 "line": 166, 514 "line": 169,
515 "column": 14 515 "column": 14
516 }, 516 },
517 "end": { 517 "end": {
518 "line": 169, 518 "line": 172,
519 "column": 3 519 "column": 3
520 } 520 }
521 }, 521 },
@@ -524,11 +524,11 @@
524 "defaultMessage": "!!!Unhide", 524 "defaultMessage": "!!!Unhide",
525 "file": "src/lib/Menu.js", 525 "file": "src/lib/Menu.js",
526 "start": { 526 "start": {
527 "line": 170, 527 "line": 173,
528 "column": 10 528 "column": 10
529 }, 529 },
530 "end": { 530 "end": {
531 "line": 173, 531 "line": 176,
532 "column": 3 532 "column": 3
533 } 533 }
534 }, 534 },
@@ -537,11 +537,11 @@
537 "defaultMessage": "!!!Quit", 537 "defaultMessage": "!!!Quit",
538 "file": "src/lib/Menu.js", 538 "file": "src/lib/Menu.js",
539 "start": { 539 "start": {
540 "line": 174, 540 "line": 177,
541 "column": 8 541 "column": 8
542 }, 542 },
543 "end": { 543 "end": {
544 "line": 177, 544 "line": 180,
545 "column": 3 545 "column": 3
546 } 546 }
547 }, 547 },
@@ -550,11 +550,50 @@
550 "defaultMessage": "!!!Add New Service...", 550 "defaultMessage": "!!!Add New Service...",
551 "file": "src/lib/Menu.js", 551 "file": "src/lib/Menu.js",
552 "start": { 552 "start": {
553 "line": 178, 553 "line": 181,
554 "column": 17 554 "column": 17
555 }, 555 },
556 "end": { 556 "end": {
557 "line": 181, 557 "line": 184,
558 "column": 3
559 }
560 },
561 {
562 "id": "menu.workspaces.addNewWorkspace",
563 "defaultMessage": "!!!Add New Workspace...",
564 "file": "src/lib/Menu.js",
565 "start": {
566 "line": 185,
567 "column": 19
568 },
569 "end": {
570 "line": 188,
571 "column": 3
572 }
573 },
574 {
575 "id": "menu.workspaces.openWorkspaceDrawer",
576 "defaultMessage": "!!!Open workspace drawer",
577 "file": "src/lib/Menu.js",
578 "start": {
579 "line": 189,
580 "column": 23
581 },
582 "end": {
583 "line": 192,
584 "column": 3
585 }
586 },
587 {
588 "id": "menu.workspaces.closeWorkspaceDrawer",
589 "defaultMessage": "!!!Close workspace drawer",
590 "file": "src/lib/Menu.js",
591 "start": {
592 "line": 193,
593 "column": 24
594 },
595 "end": {
596 "line": 196,
558 "column": 3 597 "column": 3
559 } 598 }
560 }, 599 },
@@ -563,11 +602,11 @@
563 "defaultMessage": "!!!Activate next service...", 602 "defaultMessage": "!!!Activate next service...",
564 "file": "src/lib/Menu.js", 603 "file": "src/lib/Menu.js",
565 "start": { 604 "start": {
566 "line": 182, 605 "line": 197,
567 "column": 23 606 "column": 23
568 }, 607 },
569 "end": { 608 "end": {
570 "line": 185, 609 "line": 200,
571 "column": 3 610 "column": 3
572 } 611 }
573 }, 612 },
@@ -576,11 +615,11 @@
576 "defaultMessage": "!!!Activate previous service...", 615 "defaultMessage": "!!!Activate previous service...",
577 "file": "src/lib/Menu.js", 616 "file": "src/lib/Menu.js",
578 "start": { 617 "start": {
579 "line": 186, 618 "line": 201,
580 "column": 27 619 "column": 27
581 }, 620 },
582 "end": { 621 "end": {
583 "line": 189, 622 "line": 204,
584 "column": 3 623 "column": 3
585 } 624 }
586 }, 625 },
@@ -589,11 +628,11 @@
589 "defaultMessage": "!!!Disable notifications & audio", 628 "defaultMessage": "!!!Disable notifications & audio",
590 "file": "src/lib/Menu.js", 629 "file": "src/lib/Menu.js",
591 "start": { 630 "start": {
592 "line": 190, 631 "line": 205,
593 "column": 11 632 "column": 11
594 }, 633 },
595 "end": { 634 "end": {
596 "line": 193, 635 "line": 208,
597 "column": 3 636 "column": 3
598 } 637 }
599 }, 638 },
@@ -602,11 +641,37 @@
602 "defaultMessage": "!!!Enable notifications & audio", 641 "defaultMessage": "!!!Enable notifications & audio",
603 "file": "src/lib/Menu.js", 642 "file": "src/lib/Menu.js",
604 "start": { 643 "start": {
605 "line": 194, 644 "line": 209,
606 "column": 13 645 "column": 13
607 }, 646 },
608 "end": { 647 "end": {
609 "line": 197, 648 "line": 212,
649 "column": 3
650 }
651 },
652 {
653 "id": "menu.workspaces",
654 "defaultMessage": "!!!Workspaces",
655 "file": "src/lib/Menu.js",
656 "start": {
657 "line": 213,
658 "column": 14
659 },
660 "end": {
661 "line": 216,
662 "column": 3
663 }
664 },
665 {
666 "id": "menu.workspaces.defaultWorkspace",
667 "defaultMessage": "!!!Default",
668 "file": "src/lib/Menu.js",
669 "start": {
670 "line": 217,
671 "column": 20
672 },
673 "end": {
674 "line": 220,
610 "column": 3 675 "column": 3
611 } 676 }
612 } 677 }
diff --git a/src/lib/Menu.js b/src/lib/Menu.js
index 7a60c448f..a4e41c17c 100644
--- a/src/lib/Menu.js
+++ b/src/lib/Menu.js
@@ -3,6 +3,9 @@ import { observable, autorun } from 'mobx';
3import { defineMessages } from 'react-intl'; 3import { defineMessages } from 'react-intl';
4 4
5import { isMac, ctrlKey, cmdKey } from '../environment'; 5import { isMac, ctrlKey, cmdKey } from '../environment';
6import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../features/workspaces/index';
7import { workspaceActions } from '../features/workspaces/actions';
8import { gaEvent } from './analytics';
6 9
7const { app, Menu, dialog } = remote; 10const { app, Menu, dialog } = remote;
8 11
@@ -179,6 +182,18 @@ const menuItems = defineMessages({
179 id: 'menu.services.addNewService', 182 id: 'menu.services.addNewService',
180 defaultMessage: '!!!Add New Service...', 183 defaultMessage: '!!!Add New Service...',
181 }, 184 },
185 addNewWorkspace: {
186 id: 'menu.workspaces.addNewWorkspace',
187 defaultMessage: '!!!Add New Workspace...',
188 },
189 openWorkspaceDrawer: {
190 id: 'menu.workspaces.openWorkspaceDrawer',
191 defaultMessage: '!!!Open workspace drawer',
192 },
193 closeWorkspaceDrawer: {
194 id: 'menu.workspaces.closeWorkspaceDrawer',
195 defaultMessage: '!!!Close workspace drawer',
196 },
182 activateNextService: { 197 activateNextService: {
183 id: 'menu.services.setNextServiceActive', 198 id: 'menu.services.setNextServiceActive',
184 defaultMessage: '!!!Activate next service...', 199 defaultMessage: '!!!Activate next service...',
@@ -195,6 +210,14 @@ const menuItems = defineMessages({
195 id: 'sidebar.unmuteApp', 210 id: 'sidebar.unmuteApp',
196 defaultMessage: '!!!Enable notifications & audio', 211 defaultMessage: '!!!Enable notifications & audio',
197 }, 212 },
213 workspaces: {
214 id: 'menu.workspaces',
215 defaultMessage: '!!!Workspaces',
216 },
217 defaultWorkspace: {
218 id: 'menu.workspaces.defaultWorkspace',
219 defaultMessage: '!!!Default',
220 },
198}); 221});
199 222
200function getActiveWebview() { 223function getActiveWebview() {
@@ -298,6 +321,11 @@ const _templateFactory = intl => [
298 submenu: [], 321 submenu: [],
299 }, 322 },
300 { 323 {
324 label: intl.formatMessage(menuItems.workspaces),
325 submenu: [],
326 visible: workspaceStore.isFeatureEnabled,
327 },
328 {
301 label: intl.formatMessage(menuItems.window), 329 label: intl.formatMessage(menuItems.window),
302 role: 'window', 330 role: 'window',
303 submenu: [ 331 submenu: [
@@ -669,7 +697,7 @@ export default class FranzMenu {
669 }, 697 },
670 ); 698 );
671 699
672 tpl[4].submenu.unshift(about, { 700 tpl[5].submenu.unshift(about, {
673 type: 'separator', 701 type: 'separator',
674 }); 702 });
675 } else { 703 } else {
@@ -704,6 +732,10 @@ export default class FranzMenu {
704 tpl[3].submenu = serviceTpl; 732 tpl[3].submenu = serviceTpl;
705 } 733 }
706 734
735 if (workspaceStore.isFeatureEnabled) {
736 tpl[4].submenu = this.workspacesMenu();
737 }
738
707 this.currentTemplate = tpl; 739 this.currentTemplate = tpl;
708 const menu = Menu.buildFromTemplate(tpl); 740 const menu = Menu.buildFromTemplate(tpl);
709 Menu.setApplicationMenu(menu); 741 Menu.setApplicationMenu(menu);
@@ -754,6 +786,66 @@ export default class FranzMenu {
754 return menu; 786 return menu;
755 } 787 }
756 788
789 workspacesMenu() {
790 const { workspaces, activeWorkspace, isWorkspaceDrawerOpen } = workspaceStore;
791 const { intl } = window.franz;
792 const menu = [];
793
794 // Add new workspace item:
795 menu.push({
796 label: intl.formatMessage(menuItems.addNewWorkspace),
797 accelerator: `${cmdKey}+Shift+N`,
798 click: () => {
799 workspaceActions.openWorkspaceSettings();
800 },
801 enabled: this.stores.user.isLoggedIn,
802 });
803
804 // Open workspace drawer:
805 const drawerLabel = (
806 isWorkspaceDrawerOpen ? menuItems.closeWorkspaceDrawer : menuItems.openWorkspaceDrawer
807 );
808 menu.push({
809 label: intl.formatMessage(drawerLabel),
810 accelerator: `${cmdKey}+D`,
811 click: () => {
812 workspaceActions.toggleWorkspaceDrawer();
813 gaEvent(GA_CATEGORY_WORKSPACES, 'toggleDrawer', 'menu');
814 },
815 enabled: this.stores.user.isLoggedIn,
816 }, {
817 type: 'separator',
818 });
819
820 // Default workspace
821 menu.push({
822 label: intl.formatMessage(menuItems.defaultWorkspace),
823 accelerator: `${cmdKey}+Alt+0`,
824 type: 'radio',
825 checked: !activeWorkspace,
826 click: () => {
827 workspaceActions.deactivate();
828 gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'menu');
829 },
830 });
831
832 // Workspace items
833 if (this.stores.user.isPremium) {
834 workspaces.forEach((workspace, i) => menu.push({
835 label: workspace.name,
836 accelerator: i < 9 ? `${cmdKey}+Alt+${i + 1}` : null,
837 type: 'radio',
838 checked: activeWorkspace ? workspace.id === activeWorkspace.id : false,
839 click: () => {
840 workspaceActions.activate({ workspace });
841 gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'menu');
842 },
843 }));
844 }
845
846 return menu;
847 }
848
757 _getServiceName(service) { 849 _getServiceName(service) {
758 if (service.name) { 850 if (service.name) {
759 return service.name; 851 return service.name;
diff --git a/src/lib/analytics.js b/src/lib/analytics.js
index 0519192d1..e7daa9d06 100644
--- a/src/lib/analytics.js
+++ b/src/lib/analytics.js
@@ -28,12 +28,10 @@ ga('send', 'App');
28 28
29export function gaPage(page) { 29export function gaPage(page) {
30 ga('send', 'pageview', page); 30 ga('send', 'pageview', page);
31
32 debug('GA track page', page); 31 debug('GA track page', page);
33} 32}
34 33
35export function gaEvent(category, action, label) { 34export function gaEvent(category, action, label) {
36 ga('send', 'event', category, action, label); 35 ga('send', 'event', category, action, label);
37 36 debug('GA track event', category, action, label);
38 debug('GA track event', category, action);
39} 37}
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index d2842083c..8fe576813 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -1,4 +1,9 @@
1import { computed, observable, reaction } from 'mobx'; 1import {
2 computed,
3 observable,
4 reaction,
5 runInAction,
6} from 'mobx';
2 7
3import Store from './lib/Store'; 8import Store from './lib/Store';
4import CachedRequest from './lib/CachedRequest'; 9import CachedRequest from './lib/CachedRequest';
@@ -7,6 +12,7 @@ import delayApp from '../features/delayApp';
7import spellchecker from '../features/spellchecker'; 12import spellchecker from '../features/spellchecker';
8import serviceProxy from '../features/serviceProxy'; 13import serviceProxy from '../features/serviceProxy';
9import basicAuth from '../features/basicAuth'; 14import basicAuth from '../features/basicAuth';
15import workspaces from '../features/workspaces';
10import shareFranz from '../features/shareFranz'; 16import shareFranz from '../features/shareFranz';
11 17
12import { DEFAULT_FEATURES_CONFIG } from '../config'; 18import { DEFAULT_FEATURES_CONFIG } from '../config';
@@ -16,13 +22,16 @@ export default class FeaturesStore extends Store {
16 22
17 @observable featuresRequest = new CachedRequest(this.api.features, 'features'); 23 @observable featuresRequest = new CachedRequest(this.api.features, 'features');
18 24
25 @observable features = Object.assign({}, DEFAULT_FEATURES_CONFIG);
26
19 async setup() { 27 async setup() {
20 this.registerReactions([ 28 this.registerReactions([
29 this._updateFeatures,
21 this._monitorLoginStatus.bind(this), 30 this._monitorLoginStatus.bind(this),
22 ]); 31 ]);
23 32
24 await this.featuresRequest._promise; 33 await this.featuresRequest._promise;
25 setTimeout(this._enableFeatures.bind(this), 1); 34 setTimeout(this._setupFeatures.bind(this), 1);
26 35
27 // single key reaction 36 // single key reaction
28 reaction(() => this.stores.user.data.isPremium, () => { 37 reaction(() => this.stores.user.data.isPremium, () => {
@@ -36,13 +45,16 @@ export default class FeaturesStore extends Store {
36 return this.defaultFeaturesRequest.execute().result || DEFAULT_FEATURES_CONFIG; 45 return this.defaultFeaturesRequest.execute().result || DEFAULT_FEATURES_CONFIG;
37 } 46 }
38 47
39 @computed get features() { 48 _updateFeatures = () => {
49 const features = Object.assign({}, DEFAULT_FEATURES_CONFIG);
40 if (this.stores.user.isLoggedIn) { 50 if (this.stores.user.isLoggedIn) {
41 return this.featuresRequest.execute().result || DEFAULT_FEATURES_CONFIG; 51 const requestResult = this.featuresRequest.execute().result;
52 Object.assign(features, requestResult);
42 } 53 }
43 54 runInAction('FeaturesStore::_updateFeatures', () => {
44 return DEFAULT_FEATURES_CONFIG; 55 this.features = features;
45 } 56 });
57 };
46 58
47 _monitorLoginStatus() { 59 _monitorLoginStatus() {
48 if (this.stores.user.isLoggedIn) { 60 if (this.stores.user.isLoggedIn) {
@@ -52,11 +64,12 @@ export default class FeaturesStore extends Store {
52 } 64 }
53 } 65 }
54 66
55 _enableFeatures() { 67 _setupFeatures() {
56 delayApp(this.stores, this.actions); 68 delayApp(this.stores, this.actions);
57 spellchecker(this.stores, this.actions); 69 spellchecker(this.stores, this.actions);
58 serviceProxy(this.stores, this.actions); 70 serviceProxy(this.stores, this.actions);
59 basicAuth(this.stores, this.actions); 71 basicAuth(this.stores, this.actions);
72 workspaces(this.stores, this.actions);
60 shareFranz(this.stores, this.actions); 73 shareFranz(this.stores, this.actions);
61 } 74 }
62} 75}
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 69e616f0c..0ec6bf550 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -12,6 +12,7 @@ import Request from './lib/Request';
12import CachedRequest from './lib/CachedRequest'; 12import CachedRequest from './lib/CachedRequest';
13import { matchRoute } from '../helpers/routing-helpers'; 13import { matchRoute } from '../helpers/routing-helpers';
14import { gaEvent } from '../lib/analytics'; 14import { gaEvent } from '../lib/analytics';
15import { workspaceStore } from '../features/workspaces';
15 16
16const debug = require('debug')('Franz:ServiceStore'); 17const debug = require('debug')('Franz:ServiceStore');
17 18
@@ -99,7 +100,6 @@ export default class ServicesStore extends Store {
99 return observable(services.slice().slice().sort((a, b) => a.order - b.order)); 100 return observable(services.slice().slice().sort((a, b) => a.order - b.order));
100 } 101 }
101 } 102 }
102
103 return []; 103 return [];
104 } 104 }
105 105
@@ -108,13 +108,16 @@ export default class ServicesStore extends Store {
108 } 108 }
109 109
110 @computed get allDisplayed() { 110 @computed get allDisplayed() {
111 return this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; 111 const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled;
112 return workspaceStore.filterServicesByActiveWorkspace(services);
112 } 113 }
113 114
114 // This is just used to avoid unnecessary rerendering of resource-heavy webviews 115 // This is just used to avoid unnecessary rerendering of resource-heavy webviews
115 @computed get allDisplayedUnordered() { 116 @computed get allDisplayedUnordered() {
117 const { showDisabledServices } = this.stores.settings.all.app;
116 const services = this.allServicesRequest.execute().result || []; 118 const services = this.allServicesRequest.execute().result || [];
117 return this.stores.settings.all.app.showDisabledServices ? services : services.filter(service => service.isEnabled); 119 const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled);
120 return workspaceStore.filterServicesByActiveWorkspace(filteredServices);
118 } 121 }
119 122
120 @computed get filtered() { 123 @computed get filtered() {
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js
index bb7965a4a..a95a8e1e0 100644
--- a/src/stores/UIStore.js
+++ b/src/stores/UIStore.js
@@ -21,11 +21,12 @@ export default class UIStore extends Store {
21 return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.isAppMuted; 21 return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.isAppMuted;
22 } 22 }
23 23
24 @computed get theme() { 24 @computed get isDarkThemeActive() {
25 if (this.stores.settings.all.app.darkMode) { 25 return this.stores.settings.all.app.darkMode;
26 return theme('dark'); 26 }
27 }
28 27
28 @computed get theme() {
29 if (this.isDarkThemeActive) return theme('dark');
29 return theme('default'); 30 return theme('default');
30 } 31 }
31 32
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index 77d84afe1..534690fbb 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -142,6 +142,10 @@ export default class UserStore extends Store {
142 return this.getUserInfoRequest.execute().result || {}; 142 return this.getUserInfoRequest.execute().result || {};
143 } 143 }
144 144
145 @computed get isPremium() {
146 return !!this.data.isPremium;
147 }
148
145 @computed get legacyServices() { 149 @computed get legacyServices() {
146 return this.getLegacyServicesRequest.execute() || {}; 150 return this.getLegacyServicesRequest.execute() || {};
147 } 151 }
diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js
index 04f528156..486de8a49 100644
--- a/src/stores/lib/Request.js
+++ b/src/stores/lib/Request.js
@@ -85,6 +85,8 @@ export default class Request {
85 return this.execute(...this._currentApiCall.args); 85 return this.execute(...this._currentApiCall.args);
86 } 86 }
87 87
88 retry = () => this.reload();
89
88 isExecutingWithArgs(...args) { 90 isExecutingWithArgs(...args) {
89 return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args); 91 return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args);
90 } 92 }
@@ -107,7 +109,7 @@ export default class Request {
107 Request._hooks.forEach(hook => hook(this)); 109 Request._hooks.forEach(hook => hook(this));
108 } 110 }
109 111
110 reset() { 112 reset = () => {
111 this.result = null; 113 this.result = null;
112 this.isExecuting = false; 114 this.isExecuting = false;
113 this.isError = false; 115 this.isError = false;
@@ -116,5 +118,5 @@ export default class Request {
116 this._promise = Promise; 118 this._promise = Promise;
117 119
118 return this; 120 return this;
119 } 121 };
120} 122}
diff --git a/src/styles/layout.scss b/src/styles/layout.scss
index 9a003a922..e858b7904 100644
--- a/src/styles/layout.scss
+++ b/src/styles/layout.scss
@@ -18,8 +18,14 @@ html { overflow: hidden; }
18 font-size: 22px; 18 font-size: 22px;
19 19
20 &:hover, 20 &:hover,
21 &:active { color: $dark-theme-gray-smoke; } 21 &:active {
22 &.is-muted { color: $theme-brand-primary; } 22 color: $dark-theme-gray-smoke;
23 }
24
25 &.is-muted,
26 &.is-active {
27 color: $theme-brand-primary;
28 }
23 } 29 }
24 } 30 }
25 31
@@ -33,6 +39,7 @@ html { overflow: hidden; }
33 .app__content { display: flex; } 39 .app__content { display: flex; }
34 40
35 .app__service { 41 .app__service {
42 position: relative;
36 display: flex; 43 display: flex;
37 flex: 1; 44 flex: 1;
38 flex-direction: column; 45 flex-direction: column;
@@ -84,7 +91,7 @@ html { overflow: hidden; }
84 91
85 &:hover, 92 &:hover,
86 &:active { color: lighten($theme-gray-light, 10%); } 93 &:active { color: lighten($theme-gray-light, 10%); }
87 &.is-muted { color: $theme-brand-primary; } 94 &.is-muted, &.is-active { color: $theme-brand-primary; }
88 &--new-service { padding-bottom: 6px; } 95 &--new-service { padding-bottom: 6px; }
89 } 96 }
90 97
diff --git a/src/styles/settings.scss b/src/styles/settings.scss
index 750b6bedd..dd6f56d2b 100644
--- a/src/styles/settings.scss
+++ b/src/styles/settings.scss
@@ -68,7 +68,7 @@
68 } 68 }
69 } 69 }
70 70
71 .premium-info { 71 .premium-info {
72 background: $dark-theme-gray-darker; 72 background: $dark-theme-gray-darker;
73 border: 2px solid $theme-brand-primary; 73 border: 2px solid $theme-brand-primary;
74 } 74 }
@@ -414,6 +414,7 @@
414 414
415 .settings-navigation__link { 415 .settings-navigation__link {
416 align-items: center; 416 align-items: center;
417 justify-content: space-between;
417 color: $theme-text-color; 418 color: $theme-text-color;
418 display: flex; 419 display: flex;
419 flex-shrink: 0; 420 flex-shrink: 0;
@@ -425,7 +426,9 @@
425 &:hover { 426 &:hover {
426 background: darken($theme-gray-lightest, 5%); 427 background: darken($theme-gray-lightest, 5%);
427 428
428 .badge { background: #FFF; } 429 .badge {
430 background: #FFF;
431 }
429 } 432 }
430 433
431 &.is-active { 434 &.is-active {
@@ -442,8 +445,8 @@
442 .settings-navigation__expander { flex: 1; } 445 .settings-navigation__expander { flex: 1; }
443 446
444 .badge { 447 .badge {
448
445 display: initial; 449 display: initial;
446 margin-left: 5px;
447 transition: background $theme-transition-time, color $theme-transition-time; 450 transition: background $theme-transition-time, color $theme-transition-time;
448 } 451 }
449 452
diff --git a/uidev/src/stories/badge.stories.tsx b/uidev/src/stories/badge.stories.tsx
index 6de2034bf..d7b4d55b5 100644
--- a/uidev/src/stories/badge.stories.tsx
+++ b/uidev/src/stories/badge.stories.tsx
@@ -1,6 +1,6 @@
1import React from 'react'; 1import React from 'react';
2 2
3import { Badge } from '@meetfranz/ui'; 3import { Badge, ProBadge } from '@meetfranz/ui';
4import { storiesOf } from '../stores/stories'; 4import { storiesOf } from '../stores/stories';
5 5
6storiesOf('Badge') 6storiesOf('Badge')
@@ -18,4 +18,14 @@ storiesOf('Badge')
18 <Badge type="danger">danger</Badge> 18 <Badge type="danger">danger</Badge>
19 <Badge type="inverted">inverted</Badge> 19 <Badge type="inverted">inverted</Badge>
20 </> 20 </>
21 ))
22 .add('Pro Badge', () => (
23 <>
24 <ProBadge />
25 </>
26 ))
27 .add('Pro Badge inverted', () => (
28 <>
29 <ProBadge inverted />
30 </>
21 )); 31 ));