From 47c1c99d893517efc679ab29d675cc0bf44be8be Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Thu, 11 Apr 2019 16:54:01 +0200 Subject: feat(App): Added Workspaces for all your daily routines 🥳 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .eslintrc | 3 +- .gitignore | 1 + CHANGELOG.md | 1 - package-lock.json | 322 +++---- packages/forms/src/input/index.tsx | 12 +- packages/forms/src/input/styles.ts | 5 + packages/forms/src/label/styles.ts | 4 +- packages/forms/src/select/index.tsx | 6 + packages/forms/src/toggle/index.tsx | 4 +- packages/theme/src/themes/dark/index.ts | 67 ++ packages/theme/src/themes/default/index.ts | 78 ++ packages/ui/src/badge/ProBadge.tsx | 64 ++ packages/ui/src/index.ts | 1 + packages/ui/src/infobox/index.tsx | 7 + packages/ui/src/loader/index.tsx | 4 +- src/actions/index.js | 6 +- src/actions/lib/actions.js | 4 + src/app.js | 5 + src/components/layout/AppLayout.js | 26 +- src/components/layout/Sidebar.js | 49 +- src/components/services/content/ServiceView.js | 5 +- src/components/services/tabs/Tabbar.js | 4 +- .../settings/navigation/SettingsNavigation.js | 29 +- .../settings/services/EditServiceForm.js | 10 +- .../settings/services/ServicesDashboard.js | 2 +- .../settings/settings/EditSettingsForm.js | 1 + src/components/ui/AppLoader/index.js | 4 +- src/components/ui/FullscreenLoader/index.js | 4 +- src/components/ui/Infobox.js | 17 +- src/components/ui/PremiumFeatureContainer/index.js | 21 +- .../ui/PremiumFeatureContainer/styles.js | 5 +- src/components/ui/ServiceIcon.js | 67 ++ src/components/ui/WebviewLoader/index.js | 18 +- src/config.js | 2 + src/containers/layout/AppLayoutContainer.js | 15 + src/containers/settings/SettingsWindow.js | 2 + src/environment.js | 1 + src/features/delayApp/Component.js | 2 +- src/features/delayApp/index.js | 2 +- src/features/utils/FeatureStore.js | 21 + src/features/workspaces/actions.js | 26 + src/features/workspaces/api.js | 66 ++ .../workspaces/components/CreateWorkspaceForm.js | 100 +++ .../workspaces/components/EditWorkspaceForm.js | 189 +++++ .../workspaces/components/WorkspaceDrawer.js | 246 ++++++ .../workspaces/components/WorkspaceDrawerItem.js | 137 +++ .../workspaces/components/WorkspaceItem.js | 45 + .../components/WorkspaceServiceListItem.js | 75 ++ .../components/WorkspaceSwitchingIndicator.js | 91 ++ .../workspaces/components/WorkspacesDashboard.js | 195 +++++ .../workspaces/containers/EditWorkspaceScreen.js | 60 ++ .../workspaces/containers/WorkspacesScreen.js | 42 + src/features/workspaces/index.js | 37 + src/features/workspaces/models/Workspace.js | 25 + src/features/workspaces/store.js | 276 ++++++ src/i18n/locales/de.json | 12 + src/i18n/locales/defaultMessages.json | 930 ++++++++++++++++----- src/i18n/locales/en-US.json | 39 +- .../messages/src/components/layout/AppLayout.json | 24 +- .../messages/src/components/layout/Sidebar.json | 42 +- .../settings/navigation/SettingsNavigation.json | 37 +- .../ui/PremiumFeatureContainer/index.json | 4 +- .../src/components/ui/WebviewLoader/index.json | 15 + .../workspaces/components/CreateWorkspaceForm.json | 28 + .../workspaces/components/EditWorkspaceForm.json | 67 ++ .../workspaces/components/WorkspaceDrawer.json | 106 +++ .../workspaces/components/WorkspaceDrawerItem.json | 28 + .../components/WorkspaceSwitchingIndicator.json | 15 + .../workspaces/components/WorkspacesDashboard.json | 106 +++ src/i18n/messages/src/lib/Menu.json | 253 +++--- src/lib/Menu.js | 94 ++- src/lib/analytics.js | 4 +- src/stores/FeaturesStore.js | 29 +- src/stores/ServicesStore.js | 9 +- src/stores/UIStore.js | 9 +- src/stores/UserStore.js | 4 + src/stores/lib/Request.js | 6 +- src/styles/layout.scss | 13 +- src/styles/settings.scss | 9 +- uidev/src/stories/badge.stories.tsx | 12 +- 80 files changed, 3833 insertions(+), 573 deletions(-) create mode 100644 packages/ui/src/badge/ProBadge.tsx create mode 100644 src/components/ui/ServiceIcon.js create mode 100644 src/features/utils/FeatureStore.js create mode 100644 src/features/workspaces/actions.js create mode 100644 src/features/workspaces/api.js create mode 100644 src/features/workspaces/components/CreateWorkspaceForm.js create mode 100644 src/features/workspaces/components/EditWorkspaceForm.js create mode 100644 src/features/workspaces/components/WorkspaceDrawer.js create mode 100644 src/features/workspaces/components/WorkspaceDrawerItem.js create mode 100644 src/features/workspaces/components/WorkspaceItem.js create mode 100644 src/features/workspaces/components/WorkspaceServiceListItem.js create mode 100644 src/features/workspaces/components/WorkspaceSwitchingIndicator.js create mode 100644 src/features/workspaces/components/WorkspacesDashboard.js create mode 100644 src/features/workspaces/containers/EditWorkspaceScreen.js create mode 100644 src/features/workspaces/containers/WorkspacesScreen.js create mode 100644 src/features/workspaces/index.js create mode 100644 src/features/workspaces/models/Workspace.js create mode 100644 src/features/workspaces/store.js create mode 100644 src/i18n/messages/src/components/ui/WebviewLoader/index.json create mode 100644 src/i18n/messages/src/features/workspaces/components/CreateWorkspaceForm.json create mode 100644 src/i18n/messages/src/features/workspaces/components/EditWorkspaceForm.json create mode 100644 src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json create mode 100644 src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json create mode 100644 src/i18n/messages/src/features/workspaces/components/WorkspaceSwitchingIndicator.json create mode 100644 src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json diff --git a/.eslintrc b/.eslintrc index 743946d35..e61d99c20 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,6 +2,7 @@ "parser": "babel-eslint", "extends": "eslint-config-airbnb", "rules": { + "consistent-return": 0, "no-param-reassign": 0, "import/extensions": 0, "import/no-extraneous-dependencies": 0, @@ -14,7 +15,7 @@ "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], - "react/forbid-prop-types": 1, + "react/forbid-prop-types": 0, "react/destructuring-assignment": 1, "prefer-destructuring": 1, "no-underscore-dangle": 0, diff --git a/.gitignore b/.gitignore index a5677f0b8..d38c475bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea node_modules flow-typed out diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f758097..175ce06d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,6 @@ [See 5.0.1 changelog.](#5-0-1--2019-03-25-) - # [5.0.0](https://github.com/meetfranz/franz/compare/5.0.0-beta.24...5.0.0) (2019-02-15) ### 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 @@ "@lerna/get-packed": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-3.7.0.tgz", - "integrity": "sha512-yuFtjsUZIHjeIvIYQ/QuytC+FQcHwo3peB+yGBST2uWCLUCR5rx6knoQcPzbxdFDCuUb5IFccFGd3B1fHFg3RQ==", + "integrity": "sha1-VJx3OPe+XjsUM+gu2c2pEjvNHtU=", "dev": true, "requires": { "fs-extra": "^7.0.0", @@ -1567,7 +1567,7 @@ "@lerna/npm-conf": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.7.0.tgz", - "integrity": "sha512-+WSMDfPKcKzMfqq283ydz9RRpOU6p9wfx0wy4hVSUY/6YUpsyuk8SShjcRtY8zTM5AOrxvFBuuV90H4YpZ5+Ng==", + "integrity": "sha1-8QHU/fB8788RYbz688DxBbQgpFA=", "dev": true, "requires": { "config-chain": "^1.1.11", @@ -1823,7 +1823,7 @@ "@lerna/run-parallel-batches": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@lerna/run-parallel-batches/-/run-parallel-batches-3.0.0.tgz", - "integrity": "sha512-Mj1ravlXF7AkkewKd9YFq9BtVrsStNrvVLedD/b2wIVbNqcxp8lS68vehXVOzoL/VWNEDotvqCQtyDBilCodGw==", + "integrity": "sha1-RocEk0CEx0mR0xJNgGB4V9TfqEA=", "dev": true, "requires": { "p-map": "^1.2.0", @@ -1948,15 +1948,6 @@ "@meetfranz/theme": "^1.0.7", "react-html-attributes": "^1.4.3", "react-loader": "^2.4.5" - }, - "dependencies": { - "@meetfranz/theme": { - "version": "1.0.9", - "bundled": true, - "requires": { - "color": "^3.1.0" - } - } } }, "@meetfranz/theme": { @@ -1972,21 +1963,12 @@ "@mdi/react": "^1.1.0", "@meetfranz/theme": "^1.0.7", "react-loader": "^2.4.5" - }, - "dependencies": { - "@meetfranz/theme": { - "version": "1.0.9", - "bundled": true, - "requires": { - "color": "^3.1.0" - } - } } }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "integrity": "sha1-UkryQNGjYFJ7cwR17PoTRKpUDd4=", "dev": true, "requires": { "call-me-maybe": "^1.0.1", @@ -1996,7 +1978,7 @@ "@nodelib/fs.stat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "integrity": "sha1-K1o6s/kYzKSKjHVMCBaOPwPrphs=", "dev": true }, "@octokit/endpoint": { @@ -2373,7 +2355,7 @@ "dependencies": { "mime-types": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", "dev": true } @@ -2431,7 +2413,7 @@ "agent-base": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "integrity": "sha1-2J5ZmfeXh1Z0wH2H8mD8Qeg+jKk=", "dev": true, "requires": { "es6-promisify": "^5.0.0" @@ -2440,7 +2422,7 @@ "agentkeepalive": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", - "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "integrity": "sha1-oROSTdP6JKC8O3gQjEUMKr7gD2c=", "dev": true, "requires": { "humanize-ms": "^1.2.1" @@ -2487,7 +2469,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -2918,7 +2900,7 @@ }, "util": { "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -2953,7 +2935,7 @@ }, "async": { "version": "0.1.22", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "resolved": "http://registry.npmjs.org/async/-/async-0.1.22.tgz", "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=" }, "async-done": { @@ -3776,7 +3758,7 @@ "byte-size": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-4.0.4.tgz", - "integrity": "sha512-82RPeneC6nqCdSwCX2hZUz3JPOvN5at/nTEw/CMf05Smu3Hrpo9Psb7LjN+k+XndNArG1EY8L4+BM3aTM4BCvw==", + "integrity": "sha1-KdOBcJ9BquDYnGMfHIGuyIzUCyM=", "dev": true }, "bytes": { @@ -3788,7 +3770,7 @@ "cacache": { "version": "11.3.2", "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", - "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "integrity": "sha1-LYHjCOPSWMo4Eltna5iyrJzmm/o=", "dev": true, "requires": { "bluebird": "^3.5.3", @@ -3810,7 +3792,7 @@ "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "integrity": "sha1-HaJ+ZxAnGUdpXa9oSOhH8B2EuSA=", "dev": true, "requires": { "yallist": "^3.0.2" @@ -4507,7 +4489,7 @@ "config-chain": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "integrity": "sha1-D96NCRIA616AjK8l/mGMAvSOTvo=", "dev": true, "requires": { "ini": "^1.3.4", @@ -4553,7 +4535,7 @@ "dependencies": { "debug": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-1.0.4.tgz", "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", "dev": true, "requires": { @@ -4823,7 +4805,7 @@ "conventional-recommended-bump": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-4.0.4.tgz", - "integrity": "sha512-9mY5Yoblq+ZMqJpBzgS+RpSq+SUfP2miOR3H/NR9drGf08WCrY9B6HAGJZEm6+ThsVP917VHAahSOjM6k1vhPg==", + "integrity": "sha1-BVQFhGQdPadYyIY8CXiPyutYaHI=", "dev": true, "requires": { "concat-stream": "^1.6.0", @@ -4914,7 +4896,7 @@ "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "integrity": "sha1-kilzmMrjSTf8r9bsgTnBgFHwteA=", "dev": true, "requires": { "aproba": "^1.1.1", @@ -5495,7 +5477,7 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=", "dev": true }, "diffie-hellman": { @@ -5512,7 +5494,7 @@ "dir-glob": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "integrity": "sha1-CyBdK2rvmCOMooZZioIE0p0KADQ=", "dev": true, "requires": { "arrify": "^1.0.1", @@ -5597,7 +5579,7 @@ "dependencies": { "domelementtype": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" } } @@ -5641,7 +5623,7 @@ }, "dotenv": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", + "resolved": "http://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=", "dev": true }, @@ -5661,7 +5643,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -5995,7 +5977,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -6525,7 +6507,7 @@ }, "debug": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", "dev": true, "requires": { @@ -6534,7 +6516,7 @@ }, "ms": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.2.tgz", "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", "dev": true }, @@ -6568,7 +6550,7 @@ "dependencies": { "debug": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", "dev": true, "requires": { @@ -6577,7 +6559,7 @@ }, "ms": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.2.tgz", "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", "dev": true } @@ -6701,12 +6683,12 @@ "es6-promise": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", - "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", + "integrity": "sha1-2m0NVpLvtGHggsFIF/4kJ9j10FQ=", "dev": true }, "es6-promisify": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { @@ -6913,7 +6895,7 @@ }, "load-json-file": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { @@ -7396,7 +7378,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { @@ -7724,7 +7706,7 @@ "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "integrity": "sha1-hiRwESkBxyeg5JWoB0S9W6odZ5A=", "dev": true }, "figures": { @@ -7782,7 +7764,7 @@ }, "finalhandler": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.1.0.tgz", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-0.1.0.tgz", "integrity": "sha1-2gW7xPX0owyEzh2R88FUAHxOnao=", "dev": true, "requires": { @@ -7792,7 +7774,7 @@ "dependencies": { "debug": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-1.0.4.tgz", "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", "dev": true, "requires": { @@ -7801,7 +7783,7 @@ }, "ms": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.6.2.tgz", "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=", "dev": true } @@ -8146,30 +8128,36 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", - "bundled": true + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "requires": { "delegates": "^1.0.0" } }, "balanced-match": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8177,54 +8165,66 @@ }, "chownr": { "version": "1.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" }, "code-point-at": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "debug": { "version": "2.6.9", - "bundled": true + "resolved": false, + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==" }, "deep-extend": { "version": "0.6.0", - "bundled": true + "resolved": false, + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "delegates": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "detect-libc": { "version": "1.0.3", - "bundled": true + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "requires": { "minipass": "^2.2.1" } }, "fs.realpath": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -8236,7 +8236,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -8248,25 +8249,29 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": false, + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "requires": { "minimatch": "^3.0.4" } }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", "wrappy": "1" @@ -8274,15 +8279,18 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", - "bundled": true + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" } @@ -8378,7 +8386,7 @@ "genfun": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", - "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", + "integrity": "sha1-ndlxCgaQClxKW/V6yl2k5S/nZTc=", "dev": true }, "get-caller-file": { @@ -8917,7 +8925,7 @@ }, "got": { "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "dev": true, "requires": { @@ -9499,7 +9507,7 @@ }, "yargs": { "version": "3.32.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", "dev": true, "requires": { @@ -9674,7 +9682,7 @@ }, "lodash": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", "dev": true }, @@ -9734,7 +9742,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -10315,7 +10323,7 @@ "http-cache-semantics": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "integrity": "sha1-ObDhat2bYFvwqe89nar0hDtMrNI=", "dev": true }, "http-deceiver": { @@ -10326,7 +10334,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -10364,7 +10372,7 @@ "http-proxy-agent": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "integrity": "sha1-5IIb7vWyFCogJr1zkm/lN2McVAU=", "dev": true, "requires": { "agent-base": "4", @@ -10374,7 +10382,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -10422,7 +10430,7 @@ "https-proxy-agent": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "integrity": "sha1-UVUpcPoE1yPgTFbQQXjD+SWSu8A=", "dev": true, "requires": { "agent-base": "^4.1.0", @@ -10432,7 +10440,7 @@ "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", "dev": true, "requires": { "ms": "^2.1.1" @@ -10441,7 +10449,7 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=", "dev": true } } @@ -10457,7 +10465,7 @@ }, "hunspell-asm": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hunspell-asm/-/hunspell-asm-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/hunspell-asm/-/hunspell-asm-1.0.2.tgz", "integrity": "sha512-UTLBvc0yZiIcHl9qrgxnFTZbX3zF4CprzEY+u+N0iXlUKZnUJRIgvgppTdgiQTsucm5b0aN/rHsgXz2q/0kBRA==", "requires": { "emscripten-wasm-loader": "^1.0.0", @@ -10535,7 +10543,7 @@ "ignore-walk": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "integrity": "sha1-qD5i59JyrA47VRqqgoMaGbafgvg=", "dev": true, "requires": { "minimatch": "^3.0.4" @@ -10577,7 +10585,7 @@ "import-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "integrity": "sha1-Xk/9wD9P5sAJxnKb6yljHC+CJ7w=", "dev": true, "requires": { "pkg-dir": "^2.0.0", @@ -10641,7 +10649,7 @@ "init-package-json": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", - "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", + "integrity": "sha1-Rf/i9hCoyhNPK9HbVjeyNQcPbL4=", "dev": true, "requires": { "glob": "^7.1.1", @@ -10745,7 +10753,7 @@ "inversify": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", - "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==", + "integrity": "sha1-UA1wmxQ0iWzloNWJFcSkIQ40+24=", "dev": true }, "invert-kv": { @@ -10774,7 +10782,7 @@ }, "is": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is/-/is-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/is/-/is-0.3.0.tgz", "integrity": "sha1-qPcd/IpuKDcWJ/JskpCYxvTV1dc=", "dev": true }, @@ -10998,7 +11006,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, @@ -11180,7 +11188,7 @@ }, "isemail": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=" }, "isexe": { @@ -11211,7 +11219,7 @@ }, "joi": { "version": "6.10.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-6.10.1.tgz", + "resolved": "http://registry.npmjs.org/joi/-/joi-6.10.1.tgz", "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=", "requires": { "hoek": "2.x.x", @@ -11618,7 +11626,7 @@ "libnpmaccess": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.1.tgz", - "integrity": "sha512-RlZ7PNarCBt+XbnP7R6PoVgOq9t+kou5rvhaInoNibhPO7eMlRfS0B8yjatgn2yaHIwWNyoJDolC/6Lc5L/IQA==", + "integrity": "sha1-Wzqd5iHyk9QlGRqi53kQL4QWf6g=", "dev": true, "requires": { "aproba": "^2.0.0", @@ -11630,7 +11638,7 @@ "aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "integrity": "sha1-UlILiuW1aSFbNU78DKo/4eRaitw=", "dev": true } } @@ -12076,7 +12084,7 @@ "make-fetch-happen": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz", - "integrity": "sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==", + "integrity": "sha1-FBSXy4ePJDupMTbIPYq6EsIWwIM=", "dev": true, "requires": { "agentkeepalive": "^3.4.1", @@ -12167,7 +12175,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -12258,7 +12266,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -12289,7 +12297,7 @@ "merge2": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", - "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", + "integrity": "sha1-fumdvWm7ZIFoklPwGEiKG5ArDtU=", "dev": true }, "methods": { @@ -12383,7 +12391,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -12417,7 +12425,7 @@ "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "integrity": "sha1-6goykfl+C16HdrNj1fChLZTGcCI=", "dev": true, "requires": { "concat-stream": "^1.5.0", @@ -12455,7 +12463,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -12463,7 +12471,7 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } @@ -12635,7 +12643,7 @@ }, "multimatch": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", "dev": true, "requires": { @@ -12773,7 +12781,7 @@ "node-fetch-npm": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", - "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", + "integrity": "sha1-cljJBGGC3KNFtCCO2pGNrzNpf/c=", "dev": true, "requires": { "encoding": "^0.1.11", @@ -12809,7 +12817,7 @@ "dependencies": { "semver": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true }, @@ -13987,7 +13995,7 @@ "p-map": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "integrity": "sha1-5OlPMR6rvIYzoeeZCBZfyiYkG2s=", "dev": true }, "p-map-series": { @@ -14160,7 +14168,7 @@ "dependencies": { "color-convert": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "resolved": "http://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=", "dev": true } @@ -14305,7 +14313,7 @@ }, "path-browserify": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", "dev": true }, @@ -14584,7 +14592,7 @@ }, "pretty-hrtime": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, @@ -14730,7 +14738,7 @@ "protoduck": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", - "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", + "integrity": "sha1-A8NlnKGAB7aaUP2Cp+vMUWJhFR8=", "dev": true, "requires": { "genfun": "^5.0.0" @@ -15090,7 +15098,7 @@ }, "react-router": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-3.2.1.tgz", + "resolved": "http://registry.npmjs.org/react-router/-/react-router-3.2.1.tgz", "integrity": "sha512-SXkhC0nr3G0ltzVU07IN8jYl0bB6FsrDIqlLC9dK3SITXqyTJyM7yhXlUqs89w3Nqi5OkXsfRUeHX+P874HQrg==", "requires": { "create-react-class": "^15.5.1", @@ -15205,7 +15213,7 @@ "read-package-json": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.13.tgz", - "integrity": "sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg==", + "integrity": "sha1-LoLr2fYTuqbS6+Oqcs7+P2jkH0o=", "dev": true, "requires": { "glob": "^7.1.1", @@ -15226,7 +15234,7 @@ "read-package-tree": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.1.tgz", - "integrity": "sha512-2CNoRoh95LxY47LvqrehIAfUVda2JbuFE/HaGYs42bNrGG+ojbw1h3zOcPcQ+1GQ3+rkzNndZn85u1XyZ3UsIA==", + "integrity": "sha1-Yhixh9b6yCKJzkOHu7r47vU2rWM=", "dev": true, "requires": { "debuglog": "^1.0.1", @@ -15594,7 +15602,7 @@ }, "htmlparser2": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", "dev": true, "requires": { @@ -15612,7 +15620,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -15624,7 +15632,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, @@ -16247,7 +16255,7 @@ "dependencies": { "debug": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-1.0.4.tgz", "integrity": "sha1-W5wla9VLbsAigxdvqKDt5tFUy/g=", "dev": true, "requires": { @@ -16262,7 +16270,7 @@ }, "ms": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.6.2.tgz", "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=", "dev": true } @@ -16339,7 +16347,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -16590,7 +16598,7 @@ "dependencies": { "debug": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", "dev": true, "requires": { @@ -16599,7 +16607,7 @@ }, "ms": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.2.tgz", "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", "dev": true }, @@ -16623,7 +16631,7 @@ "dependencies": { "debug": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", "dev": true, "requires": { @@ -16632,7 +16640,7 @@ }, "ms": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.2.tgz", "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", "dev": true } @@ -16659,7 +16667,7 @@ "dependencies": { "debug": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", "dev": true, "requires": { @@ -16668,7 +16676,7 @@ }, "ms": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.2.tgz", "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", "dev": true } @@ -16694,7 +16702,7 @@ }, "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -16709,7 +16717,7 @@ }, "ms": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz", "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", "dev": true } @@ -16785,7 +16793,7 @@ "socks-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", - "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", + "integrity": "sha1-WTa/i3B6mTB5xvN9sgkYIb/6ZHM=", "dev": true, "requires": { "agent-base": "~4.2.0", @@ -17022,7 +17030,7 @@ "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "integrity": "sha1-KjxBso3UW2K2Nnbst0ABJlrp7dg=", "dev": true, "requires": { "figgy-pudding": "^3.5.1" @@ -17141,7 +17149,7 @@ "stream-each": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "integrity": "sha1-6+J6DDibBPvMIzZClS4Qcxr6m64=", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -17537,7 +17545,7 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -17809,7 +17817,7 @@ "tslint": { "version": "5.12.0", "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.0.tgz", - "integrity": "sha512-CKEcH1MHUBhoV43SA/Jmy1l24HJJgI0eyLbBNSRyFlsQvb9v6Zdq+Nz2vEOH00nC5SUx4SneJ59PZUS/ARcokQ==", + "integrity": "sha1-R/LbopHtPVgHUtEJhm+2QHaPyjY=", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -17829,7 +17837,7 @@ "tslint-config-airbnb": { "version": "5.11.1", "resolved": "https://registry.npmjs.org/tslint-config-airbnb/-/tslint-config-airbnb-5.11.1.tgz", - "integrity": "sha512-hkaittm2607vVMe8eotANGN1CimD5tor7uoY3ypg2VTtEcDB/KGWYbJOz58t8LI4cWSyWtgqYQ5F0HwKxxhlkQ==", + "integrity": "sha1-UaJ/u4vyTBRNBkonSnHaR+fs5hc=", "dev": true, "requires": { "tslint-consistent-codestyle": "^1.14.1", @@ -17851,7 +17859,7 @@ "tslint-eslint-rules": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz", - "integrity": "sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==", + "integrity": "sha1-5IjMkYG/GT/lzXv8ohOnaV8XN7U=", "dev": true, "requires": { "doctrine": "0.7.2", @@ -17861,7 +17869,7 @@ "dependencies": { "doctrine": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", + "resolved": "http://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", "dev": true, "requires": { @@ -17884,7 +17892,7 @@ "tslib": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", - "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", + "integrity": "sha1-43qG/ajLuvI6BX9HPJ9Nxk5fwug=", "dev": true }, "tsutils": { @@ -17901,7 +17909,7 @@ "tslint-microsoft-contrib": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.2.1.tgz", - "integrity": "sha512-PDYjvpo0gN9IfMULwKk0KpVOPMhU6cNoT9VwCOLeDl/QS8v8W2yspRpFFuUS7/c5EIH/n8ApMi8TxJAz1tfFUA==", + "integrity": "sha1-pihoOfgA4lkdBB6igAx3SHhErYE=", "dev": true, "requires": { "tsutils": "^2.27.2 <2.29.0" @@ -17910,7 +17918,7 @@ "tsutils": { "version": "2.28.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz", - "integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==", + "integrity": "sha1-a9ceFggo+dAZtvToRHQiKPhRaaE=", "dev": true, "requires": { "tslib": "^1.8.1" @@ -17921,7 +17929,7 @@ "tsutils": { "version": "2.29.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "integrity": "sha1-MrSIUBRnrL7dS4VJhnOggSrKC5k=", "dev": true, "requires": { "tslib": "^1.8.1" @@ -17929,7 +17937,7 @@ }, "tty-browserify": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, @@ -17976,7 +17984,7 @@ "typescript": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", - "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", + "integrity": "sha1-/oEBxGqhI/g1NSPr3PVzDCrkk+U=", "dev": true }, "ua-parser-js": { @@ -18115,7 +18123,7 @@ "unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "integrity": "sha1-HWl2k2mtoFgxA6HmrodoG1ZXMjA=", "dev": true, "requires": { "unique-slug": "^2.0.0" @@ -18124,7 +18132,7 @@ "unique-slug": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", - "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", + "integrity": "sha1-Xp7cbRzo+yZNsYpQfvm9hURFHKY=", "dev": true, "requires": { "imurmurhash": "^0.1.4" @@ -18569,7 +18577,7 @@ }, "vm-browserify": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", "dev": true, "requires": { @@ -18674,7 +18682,7 @@ "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "integrity": "sha1-qFWYCx8LazWbodXZ+zmulB+qY60=", "dev": true }, "webpack": { @@ -19165,7 +19173,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -19273,7 +19281,7 @@ "write-pkg": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-3.2.0.tgz", - "integrity": "sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw==", + "integrity": "sha1-DheP6Xgg04mokovHlTXb5ows/yE=", "dev": true, "requires": { "sort-keys": "^2.0.0", @@ -19315,7 +19323,7 @@ }, "xmlbuilder": { "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", "dev": true }, 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, IFormField showPasswordToggle?: boolean; data: IData; inputClassName?: string; + onEnterKey?: Function; } interface IState { @@ -33,7 +34,7 @@ interface IState { } class InputComponent extends Component { - public static defaultProps = { + static defaultProps = { focus: false, onChange: () => {}, onBlur: () => {}, @@ -81,6 +82,13 @@ class InputComponent extends Component { } } + onInputKeyPress(e: React.KeyboardEvent) { + if (e.key === "Enter") { + const { onEnterKey } = this.props; + onEnterKey && onEnterKey(); + } + } + render() { const { classes, @@ -124,6 +132,7 @@ class InputComponent extends Component { title={label} showLabel={showLabel} htmlFor={id} + className={classes.label} isRequired={required} >
{ onFocus={onFocus} onBlur={onBlur} disabled={disabled} + onKeyPress={this.onInputKeyPress.bind(this)} min={min} max={max} 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) => ({ }); export default (theme: Theme) => ({ + label: { + '& > div': { + marginTop: 5, + } + }, disabled: { opacity: theme.inputDisabledOpacity, }, 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 @@ import { Theme } from '../../../theme/lib'; export default (theme: Theme) => ({ - content: { - marginTop: 5, - }, + content: {}, label: { color: theme.labelColor, 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) => ({ textAlign: 'left', color: theme.selectColor, }, + label: { + '& > div': { + marginTop: 5, + } + }, popup: { opacity: 0, height: 0, @@ -335,6 +340,7 @@ class SelectComponent extends Component { title={label} showLabel={showLabel} htmlFor={id} + className={classes.label} isRequired={required} >
({ }, toggleLabel: { display: 'flex', + alignItems: 'center', '& > span': { order: 1, marginLeft: 15, - marginTop: 2, }, }, }); 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 @@ import color from 'color'; +import { merge, cloneDeep } from 'lodash'; import * as defaultStyles from '../default'; import * as legacyStyles from '../legacy'; @@ -63,3 +64,69 @@ export const selectSearchColor = inputBackground; // Modal export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.8).rgb().string(); + +// Services +export const services = merge({}, defaultStyles.services, { + listItems: { + borderColor: legacyStyles.darkThemeGrayDarker, + hoverBgColor: legacyStyles.darkThemeGrayDarker, + disabled: { + color: legacyStyles.darkThemeGray, + }, + }, +}); + +// Service Icon +export const serviceIcon = merge({}, defaultStyles.serviceIcon, { + isCustom: { + border: `1px solid ${legacyStyles.darkThemeGrayDark}`, + }, +}); + +// Workspaces +const drawerBg = color(colorBackground).lighten(0.3).hex(); + +export const workspaces = merge({}, defaultStyles.workspaces, { + settings: { + listItems: cloneDeep(services.listItems), + }, + drawer: { + background: drawerBg, + addButton: { + color: legacyStyles.darkThemeGrayLighter, + hoverColor: legacyStyles.darkThemeGraySmoke, + }, + listItem: { + border: color(drawerBg).lighten(0.2).hex(), + hoverBackground: color(drawerBg).lighten(0.2).hex(), + activeBackground: defaultStyles.brandPrimary, + name: { + color: colorText, + activeColor: 'white', + }, + services: { + color: color(colorText).darken(0.5).hex(), + active: color(defaultStyles.brandPrimary).lighten(0.5).hex(), + }, + }, + }, +}); + +// // Workspace settings +// export const workspaceSettings = merge({}, defaultStyles.workspaceSettings, { +// listItemBorderColor: legacyStyles.darkThemeGrayDarker, +// listItemHoverBgColor: legacyStyles.darkThemeGrayDarker, +// }); +// +// // Workspace Drawer +// export const workspaceDrawerBackground = color(colorBackground).lighten(0.3).hex(); +// export const workspaceDrawerAddButtonColor = legacyStyles.darkThemeGrayLighter; +// export const workspaceDrawerAddButtonHoverColor = legacyStyles.darkThemeGraySmoke; +// export const workspaceDrawerItemBorder = color(workspaceDrawerBackground).lighten(0.2).hex(); +// export const workspaceDrawerItemHoverBackground = color(workspaceDrawerBackground).lighten(0.2).hex(); +// export const workspaceDrawerItemActiveBackground = defaultStyles.brandPrimary; +// export const workspaceDrawerItemNameColor = colorText; +// export const workspaceDrawerItemNameActiveColor = 'white'; +// export const workspaceDrawerServicesColor = color(colorText).darken(0.5).hex(); +// export const workspaceDrawerServicesActiveColor = color(defaultStyles.brandPrimary).lighten(0.5).hex(); +// 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 @@ import color from 'color'; +import { cloneDeep } from 'lodash'; import * as legacyStyles from '../legacy'; @@ -142,3 +143,80 @@ export const badgeBorderRadius = 50; // Modal export const colorModalOverlayBackground = color('#000').alpha(0.5).rgb().string(); + +// Services +export const services = { + listItems: { + padding: 10, + height: 57, + borderColor: legacyStyles.themeGrayLightest, + hoverBgColor: legacyStyles.themeGrayLightest, + disabled: { + color: legacyStyles.themeGrayLight, + }, + }, +}; + +// Service Icon +export const serviceIcon = { + width: 35, + isCustom: { + border: `1px solid ${legacyStyles.themeGrayLighter}`, + borderRadius: legacyStyles.themeBorderRadius, + width: 37, + }, +}; + +// Workspaces +const drawerBg = color(colorBackground).lighten(0.1).hex(); + +export const workspaces = { + settings: { + listItems: cloneDeep(services.listItems), + }, + drawer: { + width: 300, + padding: 20, + background: drawerBg, + buttons: { + color: color(legacyStyles.themeGrayLight).lighten(0.1).hex(), + hoverColor: legacyStyles.themeGrayLight, + }, + listItem: { + hoverBackground: color(drawerBg).darken(0.01).hex(), + activeBackground: legacyStyles.themeGrayLightest, + border: color(drawerBg).darken(0.05).hex(), + name: { + color: colorText, + activeColor: colorText, + }, + services: { + color: color(colorText).lighten(1.5).hex(), + active: color(colorText).lighten(1.5).hex(), + }, + }, + }, + switchingIndicator: { + spinnerColor: 'white', + }, +}; + +// export const workspaceSettings = { +// listItemHeight: 57, +// listItemBorderColor: legacyStyles.themeGrayLightest, +// listItemHoverBgColor: legacyStyles.themeGrayLightest, +// }; +// +// // Workspace Drawer +// export const workspaceDrawerWidth = 300; +// export const workspaceDrawerPadding = 20; +// export const workspaceDrawerBackground = color(colorBackground).lighten(0.1).hex(); +// export const workspaceDrawerAddButtonColor = legacyStyles.themeGrayLight; +// export const workspaceDrawerAddButtonHoverColor = color(legacyStyles.themeGrayLight).lighten(0.1).hex(); +// export const workspaceDrawerItemHoverBackground = color(workspaceDrawerBackground).darken(0.01).hex(); +// export const workspaceDrawerItemActiveBackground = legacyStyles.themeGrayLightest; +// export const workspaceDrawerItemBorder = color(workspaceDrawerBackground).darken(0.05).hex(); +// export const workspaceDrawerItemNameColor = colorText; +// export const workspaceDrawerItemNameActiveColor = colorText; +// export const workspaceDrawerServicesColor = color(colorText).lighten(1.5).hex(); +// 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 @@ +import { Theme } from '@meetfranz/theme'; +import classnames from 'classnames'; +import React, { Component } from 'react'; +import injectStyle from 'react-jss'; + +import { Icon, Badge } from '../'; +import { IWithStyle } from '../typings/generic'; + +interface IProps extends IWithStyle { + badgeClasses?: string; + iconClasses?: string; + inverted?: boolean; +} + +const styles = (theme: Theme) => ({ + badge: { + height: 'auto', + padding: [4, 6, 2, 7], + borderRadius: theme.borderRadiusSmall, + }, + invertedBadge: { + background: theme.styleTypes.primary.contrast, + color: theme.styleTypes.primary.accent, + }, + icon: { + fill: theme.styleTypes.primary.contrast, + }, + invertedIcon: { + fill: theme.styleTypes.primary.accent, + }, +}); + +class ProBadgeComponent extends Component { + render() { + const { + classes, + badgeClasses, + iconClasses, + inverted, + } = this.props; + + return ( + + + + ); + } +} + +export 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'; export * from './headline'; export { Loader } from './loader'; export { Badge } from './badge'; +export { 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 { type?: string; dismissable?: boolean; onDismiss?: () => void; + onUnmount?: () => void; ctaOnClick?: () => void; ctaLabel?: string; ctaLoading?: boolean; @@ -46,6 +47,7 @@ const styles = (theme: Theme) => ({ wrapper: { position: 'relative', overflow: 'hidden', + height: 'auto', }, infobox: { alignItems: 'center', @@ -129,6 +131,11 @@ class InfoboxComponent extends Component { }, 3000); } + componentWillUnmount(): void { + const { onUnmount } = this.props; + if (onUnmount) onUnmount(); + } + render() { const { 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'; interface IProps extends IWithStyle { className?: string; + color?: string; } const styles = (theme: Theme) => ({ @@ -22,6 +23,7 @@ class LoaderComponent extends Component { const { classes, className, + color, theme, } = this.props; @@ -37,7 +39,7 @@ class LoaderComponent extends Component { loaded={false} width={4} scale={0.75} - color={theme.colorText} + color={color || theme.colorText} parentClassName={classes.loader} />
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'; import news from './news'; import settings from './settings'; import requests from './requests'; +import workspaces from '../features/workspaces/actions'; const actions = Object.assign({}, { service, @@ -25,4 +26,7 @@ const actions = Object.assign({}, { requests, }); -export default defineActions(actions, PropTypes.checkPropTypes); +export default Object.assign( + defineActions(actions, PropTypes.checkPropTypes), + { workspaces }, +); 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) => { actions[actionName] = action; action.listeners = []; action.listen = listener => action.listeners.push(listener); + action.off = (listener) => { + const { listeners } = action; + listeners.splice(listeners.indexOf(listener), 1); + }; action.notify = params => action.listeners.forEach(listener => listener(params)); }); 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'; import InviteScreen from './containers/auth/InviteScreen'; import AuthLayoutContainer from './containers/auth/AuthLayoutContainer'; import SubscriptionPopupScreen from './containers/subscription/SubscriptionPopupScreen'; +import WorkspacesScreen from './features/workspaces/containers/WorkspacesScreen'; +import EditWorkspaceScreen from './features/workspaces/containers/EditWorkspaceScreen'; +import { WORKSPACES_ROUTES } from './features/workspaces'; // Add Polyfills smoothScroll.polyfill(); @@ -75,6 +78,8 @@ window.addEventListener('load', () => { + + 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'; import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; import { TitleBar } from 'electron-react-titlebar'; +import injectSheet from 'react-jss'; import InfoBar from '../ui/InfoBar'; import { Component as DelayApp } from '../../features/delayApp'; @@ -13,6 +14,8 @@ import ErrorBoundary from '../util/ErrorBoundary'; // import globalMessages from '../../i18n/globalMessages'; import { isWindows } from '../../environment'; +import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; +import { workspaceStore } from '../../features/workspaces'; function createMarkup(HTMLString) { return { __html: HTMLString }; @@ -45,10 +48,23 @@ const messages = defineMessages({ }, }); -export default @observer class AppLayout extends Component { +const styles = theme => ({ + appContent: { + width: `calc(100% + ${theme.workspaces.drawer.width}px)`, + transition: 'transform 0.5s ease', + transform() { + return workspaceStore.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaces.drawer.width}px)`; + }, + }, +}); + +@injectSheet(styles) @observer +class AppLayout extends Component { static propTypes = { + classes: PropTypes.object.isRequired, isFullScreen: PropTypes.bool.isRequired, sidebar: PropTypes.element.isRequired, + workspacesDrawer: PropTypes.element.isRequired, services: PropTypes.element.isRequired, children: PropTypes.element, news: MobxPropTypes.arrayOrObservableArray.isRequired, @@ -76,7 +92,9 @@ export default @observer class AppLayout extends Component { render() { const { + classes, isFullScreen, + workspacesDrawer, sidebar, services, children, @@ -102,9 +120,11 @@ export default @observer class AppLayout extends Component {
{isWindows && !isFullScreen && } -
+
+ {workspacesDrawer} {sidebar}
+ {news.length > 0 && news.map(item => ( @@ -64,9 +90,26 @@ export default @observer class Sidebar extends Component { enableToolTip={() => this.enableToolTip()} disableToolTip={() => this.disableToolTip()} /> + {workspaceStore.isFeatureEnabled ? ( + + ) : null}
- +
null, ctaLabel: '', ctaLoading: false, + onDismiss: () => null, + onSeen: () => null, }; state = { dismissed: false, }; + componentDidMount() { + const { onSeen } = this.props; + if (onSeen) onSeen(); + } + render() { const { children, @@ -37,6 +46,7 @@ export default @observer class Infobox extends Component { ctaLoading, ctaOnClick, dismissable, + onDismiss, } = this.props; if (this.state.dismissed) { @@ -76,9 +86,10 @@ export default @observer class Infobox extends Component { {dismissable && ( @@ -73,3 +88,5 @@ PremiumFeatureContainer.wrappedComponent.propTypes = { }).isRequired, }).isRequired, }; + +export 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 => ({ padding: 20, 'border-radius': theme.borderRadius, pointerEvents: 'none', + height: 'auto', }, titleContainer: { display: 'flex', @@ -19,14 +20,14 @@ export default theme => ({ color: theme.colorSubscriptionContainerActionButtonColor, 'margin-left': 'auto', 'border-radius': theme.borderRadiusSmall, - padding: [2, 4], + padding: [4, 8], 'font-size': 12, pointerEvents: 'initial', }, content: { opacity: 0.5, 'margin-top': 20, - '& :last-child': { + '& > :last-child': { 'margin-bottom': 0, }, }, 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 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import injectSheet from 'react-jss'; +import classnames from 'classnames'; + +import ServiceModel from '../../models/Service'; + +const styles = theme => ({ + root: { + height: 'auto', + }, + icon: { + width: theme.serviceIcon.width, + }, + isCustomIcon: { + width: theme.serviceIcon.isCustom.width, + border: theme.serviceIcon.isCustom.border, + borderRadius: theme.serviceIcon.isCustom.borderRadius, + }, + isDisabled: { + filter: 'grayscale(100%)', + opacity: '.5', + }, +}); + +@injectSheet(styles) @observer +class ServiceIcon extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + service: PropTypes.instanceOf(ServiceModel).isRequired, + className: PropTypes.string, + }; + + static defaultProps = { + className: '', + }; + + render() { + const { + classes, + className, + service, + } = this.props; + + return ( +
+ +
+ ); + } +} + +export 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'; import PropTypes from 'prop-types'; import { observer } from 'mobx-react'; import injectSheet from 'react-jss'; +import { defineMessages, intlShape } from 'react-intl'; import FullscreenLoader from '../FullscreenLoader'; - import styles from './styles'; +const messages = defineMessages({ + loading: { + id: 'service.webviewLoader.loading', + defaultMessage: '!!!Loading', + }, +}); + export default @observer @injectSheet(styles) class WebviewLoader extends Component { static propTypes = { name: PropTypes.string.isRequired, classes: PropTypes.object.isRequired, - } + }; + + static contextTypes = { + intl: intlShape, + }; render() { const { classes, name } = this.props; + const { intl } = this.context; return ( ); } 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 = { }, isServiceProxyEnabled: false, isServiceProxyPremiumFeature: true, + isWorkspacePremiumFeature: true, + isWorkspaceEnabled: false, }; export 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'; import AppLoader from '../../components/ui/AppLoader'; import { state as delayAppState } from '../../features/delayApp'; +import { workspaceActions } from '../../features/workspaces/actions'; +import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer'; +import { workspaceStore } from '../../features/workspaces'; export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { static defaultProps = { @@ -82,6 +85,15 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e ); } + const workspacesDrawer = ( + ( + workspace ? workspaceStore.getWorkspaceServices(workspace).map(s => s.name) : services.all.map(s => s.name) + )} + onUpgradeAccountClick={() => openSettings({ path: 'user' })} + /> + ); + const sidebar = ( @@ -122,6 +136,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e showServicesUpdatedInfoBar={ui.showServicesUpdatedInfoBar} appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED} sidebar={sidebar} + workspacesDrawer={workspacesDrawer} services={servicesContainer} news={news.latest} 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'; import Layout from '../../components/settings/SettingsLayout'; import Navigation from '../../components/settings/navigation/SettingsNavigation'; import ErrorBoundary from '../../components/util/ErrorBoundary'; +import { workspaceStore } from '../../features/workspaces'; export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { render() { @@ -16,6 +17,7 @@ export default @inject('stores', 'actions') @observer class SettingsContainer ex const navigation = ( ); 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)) { } export const API = api; +export 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 state = { countdown: config.delayDuration, - } + }; countdownInterval = null; 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) { setVisibility(true); gaPage('/delayApp'); - gaEvent('delayApp', 'show', 'Delay App Feature'); + gaEvent('DelayApp', 'show', 'Delay App Feature'); timeLastDelay = moment(); 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 @@ +import Reaction from '../../stores/lib/Reaction'; + +export class FeatureStore { + _actions = null; + + _reactions = null; + + _listenToActions(actions) { + if (this._actions) this._actions.forEach(a => a[0].off(a[1])); + this._actions = []; + actions.forEach(a => this._actions.push(a)); + this._actions.forEach(a => a[0].listen(a[1])); + } + + _startReactions(reactions) { + if (this._reactions) this._reactions.forEach(r => r.stop()); + this._reactions = []; + reactions.forEach(r => this._reactions.push(new Reaction(r))); + this._reactions.forEach(r => r.start()); + } +} 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 @@ +import PropTypes from 'prop-types'; +import Workspace from './models/Workspace'; +import { createActionsFromDefinitions } from '../../actions/lib/actions'; + +export const workspaceActions = createActionsFromDefinitions({ + edit: { + workspace: PropTypes.instanceOf(Workspace).isRequired, + }, + create: { + name: PropTypes.string.isRequired, + }, + delete: { + workspace: PropTypes.instanceOf(Workspace).isRequired, + }, + update: { + workspace: PropTypes.instanceOf(Workspace).isRequired, + }, + activate: { + workspace: PropTypes.instanceOf(Workspace).isRequired, + }, + deactivate: {}, + toggleWorkspaceDrawer: {}, + openWorkspaceSettings: {}, +}, PropTypes.checkPropTypes); + +export 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 @@ +import { pick } from 'lodash'; +import { sendAuthRequest } from '../../api/utils/auth'; +import { API, API_VERSION } from '../../environment'; +import Request from '../../stores/lib/Request'; +import Workspace from './models/Workspace'; + +const debug = require('debug')('Franz:feature:workspaces:api'); + +export const workspaceApi = { + getUserWorkspaces: async () => { + const url = `${API}/${API_VERSION}/workspace`; + debug('getUserWorkspaces GET', url); + const result = await sendAuthRequest(url, { method: 'GET' }); + debug('getUserWorkspaces RESULT', result); + if (!result.ok) throw result; + const workspaces = await result.json(); + return workspaces.map(data => new Workspace(data)); + }, + + createWorkspace: async (name) => { + const url = `${API}/${API_VERSION}/workspace`; + const options = { + method: 'POST', + body: JSON.stringify({ name }), + }; + debug('createWorkspace POST', url, options); + const result = await sendAuthRequest(url, options); + debug('createWorkspace RESULT', result); + if (!result.ok) throw result; + return new Workspace(await result.json()); + }, + + deleteWorkspace: async (workspace) => { + const url = `${API}/${API_VERSION}/workspace/${workspace.id}`; + debug('deleteWorkspace DELETE', url); + const result = await sendAuthRequest(url, { method: 'DELETE' }); + debug('deleteWorkspace RESULT', result); + if (!result.ok) throw result; + return true; + }, + + updateWorkspace: async (workspace) => { + const url = `${API}/${API_VERSION}/workspace/${workspace.id}`; + const options = { + method: 'PUT', + body: JSON.stringify(pick(workspace, ['name', 'services'])), + }; + debug('updateWorkspace UPDATE', url, options); + const result = await sendAuthRequest(url, options); + debug('updateWorkspace RESULT', result); + if (!result.ok) throw result; + return new Workspace(await result.json()); + }, +}; + +export const getUserWorkspacesRequest = new Request(workspaceApi, 'getUserWorkspaces'); +export const createWorkspaceRequest = new Request(workspaceApi, 'createWorkspace'); +export const deleteWorkspaceRequest = new Request(workspaceApi, 'deleteWorkspace'); +export const updateWorkspaceRequest = new Request(workspaceApi, 'updateWorkspace'); + +export const resetApiRequests = () => { + getUserWorkspacesRequest.reset(); + createWorkspaceRequest.reset(); + deleteWorkspaceRequest.reset(); + updateWorkspaceRequest.reset(); +}; 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 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import { Input, Button } from '@meetfranz/forms'; +import injectSheet from 'react-jss'; +import Form from '../../../lib/Form'; +import { required } from '../../../helpers/validation-helpers'; +import { gaEvent } from '../../../lib/analytics'; +import { GA_CATEGORY_WORKSPACES } from '../index'; + +const messages = defineMessages({ + submitButton: { + id: 'settings.workspace.add.form.submitButton', + defaultMessage: '!!!Create workspace', + }, + name: { + id: 'settings.workspace.add.form.name', + defaultMessage: '!!!Name', + }, +}); + +const styles = () => ({ + form: { + display: 'flex', + }, + input: { + flexGrow: 1, + marginRight: '10px', + }, + submitButton: { + height: 'inherit', + }, +}); + +@injectSheet(styles) @observer +class CreateWorkspaceForm extends Component { + static contextTypes = { + intl: intlShape, + }; + + static propTypes = { + classes: PropTypes.object.isRequired, + isSubmitting: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + }; + + form = (() => { + const { intl } = this.context; + return new Form({ + fields: { + name: { + label: intl.formatMessage(messages.name), + placeholder: intl.formatMessage(messages.name), + value: '', + validators: [required], + }, + }, + }); + })(); + + submitForm() { + const { form } = this; + form.submit({ + onSuccess: async (f) => { + const { onSubmit } = this.props; + const values = f.values(); + onSubmit(values); + gaEvent(GA_CATEGORY_WORKSPACES, 'create', values.name); + }, + }); + } + + render() { + const { intl } = this.context; + const { classes, isSubmitting } = this.props; + const { form } = this; + return ( +
+ +
+ ); + } +} + +export 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 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import { Link } from 'react-router'; +import { Input, Button } from '@meetfranz/forms'; +import injectSheet from 'react-jss'; + +import Workspace from '../models/Workspace'; +import Service from '../../../models/Service'; +import Form from '../../../lib/Form'; +import { required } from '../../../helpers/validation-helpers'; +import WorkspaceServiceListItem from './WorkspaceServiceListItem'; +import Request from '../../../stores/lib/Request'; +import { gaEvent } from '../../../lib/analytics'; +import { GA_CATEGORY_WORKSPACES } from '../index'; + +const messages = defineMessages({ + buttonDelete: { + id: 'settings.workspace.form.buttonDelete', + defaultMessage: '!!!Delete workspace', + }, + buttonSave: { + id: 'settings.workspace.form.buttonSave', + defaultMessage: '!!!Save workspace', + }, + name: { + id: 'settings.workspace.form.name', + defaultMessage: '!!!Name', + }, + yourWorkspaces: { + id: 'settings.workspace.form.yourWorkspaces', + defaultMessage: '!!!Your workspaces', + }, + servicesInWorkspaceHeadline: { + id: 'settings.workspace.form.servicesInWorkspaceHeadline', + defaultMessage: '!!!Services in this Workspace', + }, +}); + +const styles = () => ({ + nameInput: { + height: 'auto', + }, + serviceList: { + height: 'auto', + }, +}); + +@injectSheet(styles) @observer +class EditWorkspaceForm extends Component { + static contextTypes = { + intl: intlShape, + }; + + static propTypes = { + classes: PropTypes.object.isRequired, + onDelete: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired, + services: PropTypes.arrayOf(PropTypes.instanceOf(Service)).isRequired, + workspace: PropTypes.instanceOf(Workspace).isRequired, + updateWorkspaceRequest: PropTypes.instanceOf(Request).isRequired, + deleteWorkspaceRequest: PropTypes.instanceOf(Request).isRequired, + }; + + form = this.prepareWorkspaceForm(this.props.workspace); + + componentWillReceiveProps(nextProps) { + const { workspace } = this.props; + if (workspace.id !== nextProps.workspace.id) { + this.form = this.prepareWorkspaceForm(nextProps.workspace); + } + } + + prepareWorkspaceForm(workspace) { + const { intl } = this.context; + return new Form({ + fields: { + name: { + label: intl.formatMessage(messages.name), + placeholder: intl.formatMessage(messages.name), + value: workspace.name, + validators: [required], + }, + services: { + value: workspace.services.slice(), + }, + }, + }); + } + + save(form) { + form.submit({ + onSuccess: async (f) => { + const { onSave } = this.props; + const values = f.values(); + onSave(values); + gaEvent(GA_CATEGORY_WORKSPACES, 'save'); + }, + onError: async () => {}, + }); + } + + delete() { + const { onDelete } = this.props; + onDelete(); + gaEvent(GA_CATEGORY_WORKSPACES, 'delete'); + } + + toggleService(service) { + const servicesField = this.form.$('services'); + const serviceIds = servicesField.value; + if (serviceIds.includes(service.id)) { + serviceIds.splice(serviceIds.indexOf(service.id), 1); + } else { + serviceIds.push(service.id); + } + servicesField.set(serviceIds); + } + + render() { + const { intl } = this.context; + const { + classes, + workspace, + services, + deleteWorkspaceRequest, + updateWorkspaceRequest, + } = this.props; + const { form } = this; + const workspaceServices = form.$('services').value; + const isDeleting = deleteWorkspaceRequest.isExecuting; + const isSaving = updateWorkspaceRequest.isExecuting; + return ( +
+
+ + + {intl.formatMessage(messages.yourWorkspaces)} + + + + + {workspace.name} + +
+
+
+ +
+

{intl.formatMessage(messages.servicesInWorkspaceHeadline)}

+
+ {services.map(s => ( + this.toggleService(s)} + /> + ))} +
+
+
+ {/* ===== Delete Button ===== */} +
+
+ ); + } +} + +export 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 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import injectSheet from 'react-jss'; +import { defineMessages, FormattedHTMLMessage, intlShape } from 'react-intl'; +import { H1, Icon, ProBadge } from '@meetfranz/ui'; +import { Button } from '@meetfranz/forms/lib'; +import ReactTooltip from 'react-tooltip'; + +import WorkspaceDrawerItem from './WorkspaceDrawerItem'; +import { workspaceActions } from '../actions'; +import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../index'; +import { gaEvent } from '../../../lib/analytics'; + +const messages = defineMessages({ + headline: { + id: 'workspaceDrawer.headline', + defaultMessage: '!!!Workspaces', + }, + allServices: { + id: 'workspaceDrawer.allServices', + defaultMessage: '!!!All services', + }, + workspacesSettingsTooltip: { + id: 'workspaceDrawer.workspacesSettingsTooltip', + defaultMessage: '!!!Workspaces settings', + }, + workspaceFeatureInfo: { + id: 'workspaceDrawer.workspaceFeatureInfo', + defaultMessage: '!!!Info about workspace feature', + }, + premiumCtaButtonLabel: { + id: 'workspaceDrawer.premiumCtaButtonLabel', + defaultMessage: '!!!Create your first workspace', + }, + reactivatePremiumAccount: { + id: 'workspaceDrawer.reactivatePremiumAccountLabel', + defaultMessage: '!!!Reactivate premium account', + }, + addNewWorkspaceLabel: { + id: 'workspaceDrawer.addNewWorkspaceLabel', + defaultMessage: '!!!add new workspace', + }, + premiumFeatureBadge: { + id: 'workspaceDrawer.proFeatureBadge', + defaultMessage: '!!!Premium feature', + }, +}); + +const styles = theme => ({ + drawer: { + background: theme.workspaces.drawer.background, + width: `${theme.workspaces.drawer.width}px`, + }, + headline: { + fontSize: '24px', + marginTop: '38px', + marginBottom: '25px', + marginLeft: theme.workspaces.drawer.padding, + }, + headlineProBadge: { + marginRight: 15, + }, + workspacesSettingsButton: { + float: 'right', + marginRight: theme.workspaces.drawer.padding, + marginTop: '2px', + }, + workspacesSettingsButtonIcon: { + fill: theme.workspaces.drawer.buttons.color, + '&:hover': { + fill: theme.workspaces.drawer.buttons.hoverColor, + }, + }, + workspaces: { + height: 'auto', + }, + premiumAnnouncement: { + padding: '20px', + paddingTop: '0', + height: 'auto', + }, + premiumCtaButton: { + marginTop: '20px', + width: '100%', + color: 'white !important', + }, + addNewWorkspaceLabel: { + height: 'auto', + color: theme.workspaces.drawer.buttons.color, + marginTop: 40, + textAlign: 'center', + '& > svg': { + fill: theme.workspaces.drawer.buttons.color, + }, + '& > span': { + fontSize: '13px', + marginLeft: 10, + position: 'relative', + top: -3, + }, + '&:hover': { + color: theme.workspaces.drawer.buttons.hoverColor, + '& > svg': { + fill: theme.workspaces.drawer.buttons.hoverColor, + }, + }, + }, +}); + +@injectSheet(styles) @observer +class WorkspaceDrawer extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + getServicesForWorkspace: PropTypes.func.isRequired, + onUpgradeAccountClick: PropTypes.func.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + componentDidMount() { + ReactTooltip.rebuild(); + } + + render() { + const { + classes, + getServicesForWorkspace, + onUpgradeAccountClick, + } = this.props; + const { intl } = this.context; + const { + activeWorkspace, + isSwitchingWorkspace, + nextWorkspace, + workspaces, + } = workspaceStore; + const actualWorkspace = isSwitchingWorkspace ? nextWorkspace : activeWorkspace; + return ( +
+

+ {workspaceStore.isPremiumUpgradeRequired && ( + + + + )} + {intl.formatMessage(messages.headline)} + { + workspaceActions.openWorkspaceSettings(); + gaEvent(GA_CATEGORY_WORKSPACES, 'settings', 'drawerHeadline'); + }} + data-tip={`${intl.formatMessage(messages.workspacesSettingsTooltip)}`} + > + + +

+ {workspaceStore.isPremiumUpgradeRequired ? ( +
+ + {workspaceStore.userHasWorkspaces ? ( +
+ ) : ( +
+ { + workspaceActions.deactivate(); + workspaceActions.toggleWorkspaceDrawer(); + gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'drawer'); + }} + services={getServicesForWorkspace(null)} + isActive={actualWorkspace == null} + /> + {workspaces.map(workspace => ( + { + if (actualWorkspace === workspace) return; + workspaceActions.activate({ workspace }); + workspaceActions.toggleWorkspaceDrawer(); + gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'drawer'); + }} + onContextMenuEditClick={() => workspaceActions.edit({ workspace })} + services={getServicesForWorkspace(workspace)} + /> + ))} +
{ + workspaceActions.openWorkspaceSettings(); + gaEvent(GA_CATEGORY_WORKSPACES, 'add', 'drawerAddLabel'); + }} + > + + + {intl.formatMessage(messages.addNewWorkspaceLabel)} + +
+
+ )} + +
+ ); + } +} + +export 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 @@ +import { remote } from 'electron'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import injectSheet from 'react-jss'; +import classnames from 'classnames'; +import { defineMessages, intlShape } from 'react-intl'; + +const { Menu } = remote; + +const messages = defineMessages({ + noServicesAddedYet: { + id: 'workspaceDrawer.item.noServicesAddedYet', + defaultMessage: '!!!No services added yet', + }, + contextMenuEdit: { + id: 'workspaceDrawer.item.contextMenuEdit', + defaultMessage: '!!!edit', + }, +}); + +const styles = theme => ({ + item: { + height: '67px', + padding: `15px ${theme.workspaces.drawer.padding}px`, + borderBottom: `1px solid ${theme.workspaces.drawer.listItem.border}`, + transition: 'background-color 300ms ease-out', + '&:first-child': { + borderTop: `1px solid ${theme.workspaces.drawer.listItem.border}`, + }, + '&:hover': { + backgroundColor: theme.workspaces.drawer.listItem.hoverBackground, + }, + }, + isActiveItem: { + backgroundColor: theme.workspaces.drawer.listItem.activeBackground, + '&:hover': { + backgroundColor: theme.workspaces.drawer.listItem.activeBackground, + }, + }, + name: { + marginTop: '4px', + color: theme.workspaces.drawer.listItem.name.color, + }, + activeName: { + color: theme.workspaces.drawer.listItem.name.activeColor, + }, + services: { + display: 'block', + fontSize: '11px', + marginTop: '5px', + color: theme.workspaces.drawer.listItem.services.color, + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + overflow: 'hidden', + lineHeight: '15px', + }, + activeServices: { + color: theme.workspaces.drawer.listItem.services.active, + }, +}); + +@injectSheet(styles) @observer +class WorkspaceDrawerItem extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + isActive: PropTypes.bool.isRequired, + name: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, + services: PropTypes.arrayOf(PropTypes.string).isRequired, + onContextMenuEditClick: PropTypes.func, + }; + + static defaultProps = { + onContextMenuEditClick: null, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { + classes, + isActive, + name, + onClick, + onContextMenuEditClick, + services, + } = this.props; + const { intl } = this.context; + + const contextMenuTemplate = [{ + label: name, + enabled: false, + }, { + type: 'separator', + }, { + label: intl.formatMessage(messages.contextMenuEdit), + click: onContextMenuEditClick, + }]; + + const contextMenu = Menu.buildFromTemplate(contextMenuTemplate); + + return ( +
( + onContextMenuEditClick && contextMenu.popup(remote.getCurrentWindow()) + )} + > + + {name} + + + {services.length ? services.join(', ') : intl.formatMessage(messages.noServicesAddedYet)} + +
+ ); + } +} + +export 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 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { intlShape } from 'react-intl'; +import { observer } from 'mobx-react'; +import injectSheet from 'react-jss'; + +import Workspace from '../models/Workspace'; + +const styles = theme => ({ + row: { + height: theme.workspaces.settings.listItems.height, + borderBottom: `1px solid ${theme.workspaces.settings.listItems.borderColor}`, + '&:hover': { + background: theme.workspaces.settings.listItems.hoverBgColor, + }, + }, + columnName: {}, +}); + +@injectSheet(styles) @observer +class WorkspaceItem extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + workspace: PropTypes.instanceOf(Workspace).isRequired, + onItemClick: PropTypes.func.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { classes, workspace, onItemClick } = this.props; + + return ( + + onItemClick(workspace)}> + {workspace.name} + + + ); + } +} + +export 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 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import injectSheet from 'react-jss'; +import classnames from 'classnames'; +import { Toggle } from '@meetfranz/forms'; + +import Service from '../../../models/Service'; +import ServiceIcon from '../../../components/ui/ServiceIcon'; + +const styles = theme => ({ + listItem: { + height: theme.workspaces.settings.listItems.height, + borderBottom: `1px solid ${theme.workspaces.settings.listItems.borderColor}`, + display: 'flex', + alignItems: 'center', + }, + serviceIcon: { + padding: theme.workspaces.settings.listItems.padding, + }, + toggle: { + height: 'auto', + margin: 0, + }, + label: { + padding: theme.workspaces.settings.listItems.padding, + flexGrow: 1, + }, + disabledLabel: { + color: theme.workspaces.settings.listItems.disabled.color, + }, +}); + +@injectSheet(styles) @observer +class WorkspaceServiceListItem extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + isInWorkspace: PropTypes.bool.isRequired, + onToggle: PropTypes.func.isRequired, + service: PropTypes.instanceOf(Service).isRequired, + }; + + render() { + const { + classes, + isInWorkspace, + onToggle, + service, + } = this.props; + + return ( +
+ + + {service.name} + + +
+ ); + } +} + +export 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 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import injectSheet from 'react-jss'; +import classnames from 'classnames'; +import { Loader } from '@meetfranz/ui'; +import { defineMessages, intlShape } from 'react-intl'; + +import { workspaceStore } from '../index'; + +const messages = defineMessages({ + switchingTo: { + id: 'workspaces.switchingIndicator.switchingTo', + defaultMessage: '!!!Switching to', + }, +}); + +const styles = theme => ({ + wrapper: { + display: 'flex', + alignItems: 'flex-start', + position: 'absolute', + transition: 'width 0.5s ease', + width: '100%', + marginTop: '20px', + }, + wrapperWhenDrawerIsOpen: { + width: `calc(100% - ${theme.workspaces.drawer.width}px)`, + }, + component: { + background: 'rgba(20, 20, 20, 0.4)', + padding: '10px 20px', + display: 'flex', + width: 'auto', + height: 'auto', + margin: [0, 'auto'], + borderRadius: 6, + alignItems: 'center', + zIndex: 200, + }, + spinner: { + width: 40, + height: 40, + marginRight: 10, + }, + message: { + fontSize: 16, + whiteSpace: 'nowrap', + color: theme.colorAppLoaderSpinner, + }, +}); + +@injectSheet(styles) @observer +class WorkspaceSwitchingIndicator extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { classes, theme } = this.props; + const { intl } = this.context; + const { isSwitchingWorkspace, isWorkspaceDrawerOpen, nextWorkspace } = workspaceStore; + if (!isSwitchingWorkspace) return null; + const nextWorkspaceName = nextWorkspace ? nextWorkspace.name : 'All services'; + return ( +
+
+ +

+ {`${intl.formatMessage(messages.switchingTo)} ${nextWorkspaceName}`} +

+
+
+ ); + } +} + +export 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 @@ +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import injectSheet from 'react-jss'; +import { Infobox } from '@meetfranz/ui'; + +import Loader from '../../../components/ui/Loader'; +import WorkspaceItem from './WorkspaceItem'; +import CreateWorkspaceForm from './CreateWorkspaceForm'; +import Request from '../../../stores/lib/Request'; +import Appear from '../../../components/ui/effects/Appear'; +import { workspaceStore } from '../index'; +import PremiumFeatureContainer from '../../../components/ui/PremiumFeatureContainer'; + +const messages = defineMessages({ + headline: { + id: 'settings.workspaces.headline', + defaultMessage: '!!!Your workspaces', + }, + noServicesAdded: { + id: 'settings.workspaces.noWorkspacesAdded', + defaultMessage: '!!!You haven\'t added any workspaces yet.', + }, + workspacesRequestFailed: { + id: 'settings.workspaces.workspacesRequestFailed', + defaultMessage: '!!!Could not load your workspaces', + }, + tryReloadWorkspaces: { + id: 'settings.workspaces.tryReloadWorkspaces', + defaultMessage: '!!!Try again', + }, + updatedInfo: { + id: 'settings.workspaces.updatedInfo', + defaultMessage: '!!!Your changes have been saved', + }, + deletedInfo: { + id: 'settings.workspaces.deletedInfo', + defaultMessage: '!!!Workspace has been deleted', + }, + workspaceFeatureInfo: { + id: 'settings.workspaces.workspaceFeatureInfo', + defaultMessage: '!!!Info about workspace feature', + }, + workspaceFeatureHeadline: { + id: 'settings.workspaces.workspaceFeatureHeadline', + defaultMessage: '!!!Less is More: Introducing Franz Workspaces', + }, +}); + +const styles = theme => ({ + table: { + width: '100%', + '& td': { + padding: '10px', + }, + }, + createForm: { + height: 'auto', + }, + appear: { + height: 'auto', + }, + premiumAnnouncement: { + padding: '20px', + backgroundColor: '#3498db', + marginLeft: '-20px', + marginBottom: '20px', + height: 'auto', + color: 'white', + borderRadius: theme.borderRadius, + }, +}); + +@injectSheet(styles) @observer +class WorkspacesDashboard extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + getUserWorkspacesRequest: PropTypes.instanceOf(Request).isRequired, + createWorkspaceRequest: PropTypes.instanceOf(Request).isRequired, + deleteWorkspaceRequest: PropTypes.instanceOf(Request).isRequired, + updateWorkspaceRequest: PropTypes.instanceOf(Request).isRequired, + onCreateWorkspaceSubmit: PropTypes.func.isRequired, + onWorkspaceClick: PropTypes.func.isRequired, + workspaces: MobxPropTypes.arrayOrObservableArray.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { + classes, + getUserWorkspacesRequest, + createWorkspaceRequest, + deleteWorkspaceRequest, + updateWorkspaceRequest, + onCreateWorkspaceSubmit, + onWorkspaceClick, + workspaces, + } = this.props; + const { intl } = this.context; + return ( +
+
+

{intl.formatMessage(messages.headline)}

+
+
+ + {/* ===== Workspace updated info ===== */} + {updateWorkspaceRequest.wasExecuted && updateWorkspaceRequest.result && ( + + + {intl.formatMessage(messages.updatedInfo)} + + + )} + + {/* ===== Workspace deleted info ===== */} + {deleteWorkspaceRequest.wasExecuted && deleteWorkspaceRequest.result && ( + + + {intl.formatMessage(messages.deletedInfo)} + + + )} + + {workspaceStore.isPremiumUpgradeRequired && ( +
+

{intl.formatMessage(messages.workspaceFeatureHeadline)}

+

{intl.formatMessage(messages.workspaceFeatureInfo)}

+
+ )} + + + {/* ===== Create workspace form ===== */} +
+ +
+ {getUserWorkspacesRequest.isExecuting ? ( + + ) : ( + + {/* ===== Workspace could not be loaded error ===== */} + {getUserWorkspacesRequest.error ? ( + + {intl.formatMessage(messages.workspacesRequestFailed)} + + ) : ( + + {/* ===== Workspaces list ===== */} + + {workspaces.map(workspace => ( + onWorkspaceClick(w)} + /> + ))} + +
+ )} +
+ )} +
+
+
+ ); + } +} + +export 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 @@ +import React, { Component } from 'react'; +import { inject, observer } from 'mobx-react'; +import PropTypes from 'prop-types'; + +import ErrorBoundary from '../../../components/util/ErrorBoundary'; +import EditWorkspaceForm from '../components/EditWorkspaceForm'; +import ServicesStore from '../../../stores/ServicesStore'; +import Workspace from '../models/Workspace'; +import { workspaceStore } from '../index'; +import { deleteWorkspaceRequest, updateWorkspaceRequest } from '../api'; + +@inject('stores', 'actions') @observer +class EditWorkspaceScreen extends Component { + static propTypes = { + actions: PropTypes.shape({ + workspace: PropTypes.shape({ + delete: PropTypes.func.isRequired, + }), + }).isRequired, + stores: PropTypes.shape({ + services: PropTypes.instanceOf(ServicesStore).isRequired, + }).isRequired, + }; + + onDelete = () => { + const { workspaceBeingEdited } = workspaceStore; + const { actions } = this.props; + if (!workspaceBeingEdited) return null; + actions.workspaces.delete({ workspace: workspaceBeingEdited }); + }; + + onSave = (values) => { + const { workspaceBeingEdited } = workspaceStore; + const { actions } = this.props; + const workspace = new Workspace( + Object.assign({}, workspaceBeingEdited, values), + ); + actions.workspaces.update({ workspace }); + }; + + render() { + const { workspaceBeingEdited } = workspaceStore; + const { stores } = this.props; + if (!workspaceBeingEdited) return null; + return ( + + + + ); + } +} + +export 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 @@ +import React, { Component } from 'react'; +import { inject, observer } from 'mobx-react'; +import PropTypes from 'prop-types'; +import WorkspacesDashboard from '../components/WorkspacesDashboard'; +import ErrorBoundary from '../../../components/util/ErrorBoundary'; +import { workspaceStore } from '../index'; +import { + createWorkspaceRequest, + deleteWorkspaceRequest, + getUserWorkspacesRequest, + updateWorkspaceRequest, +} from '../api'; + +@inject('actions') @observer +class WorkspacesScreen extends Component { + static propTypes = { + actions: PropTypes.shape({ + workspace: PropTypes.shape({ + edit: PropTypes.func.isRequired, + }), + }).isRequired, + }; + + render() { + const { actions } = this.props; + return ( + + actions.workspaces.create(data)} + onWorkspaceClick={w => actions.workspaces.edit({ workspace: w })} + /> + + ); + } +} + +export 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 @@ +import { reaction } from 'mobx'; +import WorkspacesStore from './store'; +import { resetApiRequests } from './api'; + +const debug = require('debug')('Franz:feature:workspaces'); + +export const GA_CATEGORY_WORKSPACES = 'Workspaces'; + +export const workspaceStore = new WorkspacesStore(); + +export default function initWorkspaces(stores, actions) { + stores.workspaces = workspaceStore; + const { features } = stores; + + // Toggle workspace feature + reaction( + () => features.features.isWorkspaceEnabled, + (isEnabled) => { + if (isEnabled && !workspaceStore.isFeatureActive) { + debug('Initializing `workspaces` feature'); + workspaceStore.start(stores, actions); + } else if (workspaceStore.isFeatureActive) { + debug('Disabling `workspaces` feature'); + workspaceStore.stop(); + resetApiRequests(); + } + }, + { + fireImmediately: true, + }, + ); +} + +export const WORKSPACES_ROUTES = { + ROOT: '/settings/workspaces', + EDIT: '/settings/workspaces/:action/:id', +}; 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 @@ +import { observable } from 'mobx'; + +export default class Workspace { + id = null; + + @observable name = null; + + @observable order = null; + + @observable services = []; + + @observable userId = null; + + constructor(data) { + if (!data.id) { + throw Error('Workspace requires Id'); + } + + this.id = data.id; + this.name = data.name; + this.order = data.order; + this.services.replace(data.services); + this.userId = data.userId; + } +} 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 @@ +import { + computed, + observable, + action, +} from 'mobx'; +import localStorage from 'mobx-localstorage'; +import { matchRoute } from '../../helpers/routing-helpers'; +import { workspaceActions } from './actions'; +import { FeatureStore } from '../utils/FeatureStore'; +import { + createWorkspaceRequest, + deleteWorkspaceRequest, + getUserWorkspacesRequest, + updateWorkspaceRequest, +} from './api'; +import { WORKSPACES_ROUTES } from './index'; + +const debug = require('debug')('Franz:feature:workspaces:store'); + +export default class WorkspacesStore extends FeatureStore { + @observable isFeatureEnabled = false; + + @observable isFeatureActive = false; + + @observable isPremiumFeature = true; + + @observable isPremiumUpgradeRequired = true; + + @observable activeWorkspace = null; + + @observable nextWorkspace = null; + + @observable workspaceBeingEdited = null; + + @observable isSwitchingWorkspace = false; + + @observable isWorkspaceDrawerOpen = false; + + @observable isSettingsRouteActive = null; + + @computed get workspaces() { + if (!this.isFeatureActive) return []; + return getUserWorkspacesRequest.result || []; + } + + @computed get settings() { + return localStorage.getItem('workspaces') || {}; + } + + @computed get userHasWorkspaces() { + return getUserWorkspacesRequest.wasExecuted && this.workspaces.length > 0; + } + + start(stores, actions) { + debug('WorkspacesStore::start'); + this.stores = stores; + this.actions = actions; + + this._listenToActions([ + [workspaceActions.edit, this._edit], + [workspaceActions.create, this._create], + [workspaceActions.delete, this._delete], + [workspaceActions.update, this._update], + [workspaceActions.activate, this._setActiveWorkspace], + [workspaceActions.deactivate, this._deactivateActiveWorkspace], + [workspaceActions.toggleWorkspaceDrawer, this._toggleWorkspaceDrawer], + [workspaceActions.openWorkspaceSettings, this._openWorkspaceSettings], + ]); + + this._startReactions([ + this._setWorkspaceBeingEditedReaction, + this._setActiveServiceOnWorkspaceSwitchReaction, + this._setFeatureEnabledReaction, + this._setIsPremiumFeatureReaction, + this._activateLastUsedWorkspaceReaction, + this._openDrawerWithSettingsReaction, + this._cleanupInvalidServiceReferences, + ]); + + getUserWorkspacesRequest.execute(); + this.isFeatureActive = true; + } + + stop() { + debug('WorkspacesStore::stop'); + this.isFeatureActive = false; + this.activeWorkspace = null; + this.nextWorkspace = null; + this.workspaceBeingEdited = null; + this.isSwitchingWorkspace = false; + this.isWorkspaceDrawerOpen = false; + } + + filterServicesByActiveWorkspace = (services) => { + const { activeWorkspace, isFeatureActive } = this; + if (isFeatureActive && activeWorkspace) { + return this.getWorkspaceServices(activeWorkspace); + } + return services; + }; + + getWorkspaceServices(workspace) { + const { services } = this.stores; + return workspace.services.map(id => services.one(id)).filter(s => !!s); + } + + // ========== PRIVATE ========= // + + _wasDrawerOpenBeforeSettingsRoute = null; + + _getWorkspaceById = id => this.workspaces.find(w => w.id === id); + + _updateSettings = (changes) => { + localStorage.setItem('workspaces', { + ...this.settings, + ...changes, + }); + }; + + // Actions + + @action _edit = ({ workspace }) => { + this.stores.router.push(`/settings/workspaces/edit/${workspace.id}`); + }; + + @action _create = async ({ name }) => { + try { + const workspace = await createWorkspaceRequest.execute(name); + await getUserWorkspacesRequest.result.push(workspace); + this._edit({ workspace }); + } catch (error) { + throw error; + } + }; + + @action _delete = async ({ workspace }) => { + try { + await deleteWorkspaceRequest.execute(workspace); + await getUserWorkspacesRequest.result.remove(workspace); + this.stores.router.push('/settings/workspaces'); + } catch (error) { + throw error; + } + }; + + @action _update = async ({ workspace }) => { + try { + await updateWorkspaceRequest.execute(workspace); + // Path local result optimistically + const localWorkspace = this._getWorkspaceById(workspace.id); + Object.assign(localWorkspace, workspace); + this.stores.router.push('/settings/workspaces'); + } catch (error) { + throw error; + } + }; + + @action _setActiveWorkspace = ({ workspace }) => { + // Indicate that we are switching to another workspace + this.isSwitchingWorkspace = true; + this.nextWorkspace = workspace; + // Delay switching to next workspace so that the services loading does not drag down UI + setTimeout(() => { + this.activeWorkspace = workspace; + this._updateSettings({ lastActiveWorkspace: workspace.id }); + }, 100); + // Indicate that we are done switching to the next workspace + setTimeout(() => { + this.isSwitchingWorkspace = false; + this.nextWorkspace = null; + }, 1000); + }; + + @action _deactivateActiveWorkspace = () => { + // Indicate that we are switching to default workspace + this.isSwitchingWorkspace = true; + this.nextWorkspace = null; + this._updateSettings({ lastActiveWorkspace: null }); + // Delay switching to next workspace so that the services loading does not drag down UI + setTimeout(() => { + this.activeWorkspace = null; + }, 100); + // Indicate that we are done switching to the default workspace + setTimeout(() => { this.isSwitchingWorkspace = false; }, 1000); + }; + + @action _toggleWorkspaceDrawer = () => { + this.isWorkspaceDrawerOpen = !this.isWorkspaceDrawerOpen; + }; + + @action _openWorkspaceSettings = () => { + this.actions.ui.openSettings({ path: 'workspaces' }); + }; + + // Reactions + + _setFeatureEnabledReaction = () => { + const { isWorkspaceEnabled } = this.stores.features.features; + this.isFeatureEnabled = isWorkspaceEnabled; + }; + + _setIsPremiumFeatureReaction = () => { + const { features, user } = this.stores; + const { isPremium } = user.data; + const { isWorkspacePremiumFeature } = features.features; + this.isPremiumFeature = isWorkspacePremiumFeature; + this.isPremiumUpgradeRequired = isWorkspacePremiumFeature && !isPremium; + }; + + _setWorkspaceBeingEditedReaction = () => { + const { pathname } = this.stores.router.location; + const match = matchRoute('/settings/workspaces/edit/:id', pathname); + if (match) { + this.workspaceBeingEdited = this._getWorkspaceById(match.id); + } + }; + + _setActiveServiceOnWorkspaceSwitchReaction = () => { + if (!this.isFeatureActive) return; + if (this.activeWorkspace) { + const services = this.stores.services.allDisplayed; + const activeService = services.find(s => s.isActive); + const workspaceServices = this.getWorkspaceServices(this.activeWorkspace); + if (workspaceServices.length <= 0) return; + const isActiveServiceInWorkspace = workspaceServices.includes(activeService); + if (!isActiveServiceInWorkspace) { + this.actions.service.setActive({ serviceId: workspaceServices[0].id }); + } + } + }; + + _activateLastUsedWorkspaceReaction = () => { + if (!this.activeWorkspace && this.userHasWorkspaces) { + const { lastActiveWorkspace } = this.settings; + if (lastActiveWorkspace) { + const workspace = this._getWorkspaceById(lastActiveWorkspace); + if (workspace) this._setActiveWorkspace({ workspace }); + } + } + }; + + _openDrawerWithSettingsReaction = () => { + const { router } = this.stores; + const isWorkspaceSettingsRoute = router.location.pathname.includes(WORKSPACES_ROUTES.ROOT); + const isSwitchingToSettingsRoute = !this.isSettingsRouteActive && isWorkspaceSettingsRoute; + const isLeavingSettingsRoute = !isWorkspaceSettingsRoute && this.isSettingsRouteActive; + + if (isSwitchingToSettingsRoute) { + this.isSettingsRouteActive = true; + this._wasDrawerOpenBeforeSettingsRoute = this.isWorkspaceDrawerOpen; + if (!this._wasDrawerOpenBeforeSettingsRoute) { + workspaceActions.toggleWorkspaceDrawer(); + } + } else if (isLeavingSettingsRoute) { + this.isSettingsRouteActive = false; + if (!this._wasDrawerOpenBeforeSettingsRoute && this.isWorkspaceDrawerOpen) { + workspaceActions.toggleWorkspaceDrawer(); + } + } + }; + + _cleanupInvalidServiceReferences = () => { + const { services } = this.stores; + let invalidServiceReferencesExist = false; + this.workspaces.forEach((workspace) => { + workspace.services.forEach((serviceId) => { + if (!services.one(serviceId)) { + invalidServiceReferencesExist = true; + } + }); + }); + if (invalidServiceReferencesExist) { + getUserWorkspacesRequest.execute(); + } + }; +} 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 @@ "menu.window" : "Fenster", "menu.window.close" : "Schließen", "menu.window.minimize" : "Minimieren", + "menu.workspaces": "Workspaces", + "menu.workspaces.defaultWorkspace": "All services", + "menu.workspaces.addNewWorkspace": "Add New Workspace", "password.email.label" : "E-Mail Adresse", "password.headline" : "Passwort zurücksetzen", "password.link.login" : "An Deinem Konto anmelden", @@ -169,6 +172,7 @@ "settings.navigation.logout" : "Abmelden", "settings.navigation.settings" : "Einstellungen", "settings.navigation.yourServices" : "Deine Dienste", + "settings.navigation.yourWorkspaces": "Deine Workspaces", "settings.recipes.all" : "Alle Dienste", "settings.recipes.dev" : "Entwicklung", "settings.recipes.headline" : "Verfügbare Dienste", @@ -226,6 +230,14 @@ "settings.services.tooltip.isMuted" : "Alle Töne sind deaktiviert", "settings.services.tooltip.notificationsDisabled" : "Benachrichtigungen deaktiviert", "settings.services.updatedInfo" : "Deine Änderungen wurden gespeichert", + "settings.workspaces.headline": "Deine Workspaces", + "settings.workspace.add.form.submitButton": "Workspace erstellen", + "settings.workspace.add.form.name": "Name", + "settings.workspace.form.yourWorkspaces": "Deine Workspaces", + "settings.workspace.form.name": "Name", + "settings.workspace.form.buttonDelete": "Workspace löschen", + "settings.workspace.form.buttonSave": "Workspace speichern", + "settings.workspace.form.servicesInWorkspaceHeadline": "Services in diesem Workspace", "settings.user.form.accountType.company" : "Firma", "settings.user.form.accountType.individual" : "Einzelperson", "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 @@ "defaultMessage": "!!!Your services have been updated.", "end": { "column": 3, - "line": 25 + "line": 28 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.servicesUpdated", "start": { "column": 19, - "line": 22 + "line": 25 } }, { "defaultMessage": "!!!A new update for Franz is available.", "end": { "column": 3, - "line": 29 + "line": 32 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.updateAvailable", "start": { "column": 19, - "line": 26 + "line": 29 } }, { "defaultMessage": "!!!Reload services", "end": { "column": 3, - "line": 33 + "line": 36 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonReloadServices", "start": { "column": 24, - "line": 30 + "line": 33 } }, { "defaultMessage": "!!!Changelog", "end": { "column": 3, - "line": 37 + "line": 40 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonChangelog", "start": { "column": 13, - "line": 34 + "line": 37 } }, { "defaultMessage": "!!!Restart & install update", "end": { "column": 3, - "line": 41 + "line": 44 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonInstallUpdate", "start": { "column": 23, - "line": 38 + "line": 41 } }, { "defaultMessage": "!!!Could not load services and user information", "end": { "column": 3, - "line": 45 + "line": 48 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.requiredRequestsFailed", "start": { "column": 26, - "line": 42 + "line": 45 } } ], @@ -708,52 +708,78 @@ "defaultMessage": "!!!Settings", "end": { "column": 3, - "line": 14 + "line": 16 }, "file": "src/components/layout/Sidebar.js", "id": "sidebar.settings", "start": { "column": 12, - "line": 11 + "line": 13 } }, { "defaultMessage": "!!!Add new service", "end": { "column": 3, - "line": 18 + "line": 20 }, "file": "src/components/layout/Sidebar.js", "id": "sidebar.addNewService", "start": { "column": 17, - "line": 15 + "line": 17 } }, { "defaultMessage": "!!!Disable notifications & audio", "end": { "column": 3, - "line": 22 + "line": 24 }, "file": "src/components/layout/Sidebar.js", "id": "sidebar.muteApp", "start": { "column": 8, - "line": 19 + "line": 21 } }, { "defaultMessage": "!!!Enable notifications & audio", "end": { "column": 3, - "line": 26 + "line": 28 }, "file": "src/components/layout/Sidebar.js", "id": "sidebar.unmuteApp", "start": { "column": 10, - "line": 23 + "line": 25 + } + }, + { + "defaultMessage": "!!!Open workspace drawer", + "end": { + "column": 3, + "line": 32 + }, + "file": "src/components/layout/Sidebar.js", + "id": "sidebar.openWorkspaceDrawer", + "start": { + "column": 23, + "line": 29 + } + }, + { + "defaultMessage": "!!!Close workspace drawer", + "end": { + "column": 3, + "line": 36 + }, + "file": "src/components/layout/Sidebar.js", + "id": "sidebar.closeWorkspaceDrawer", + "start": { + "column": 24, + "line": 33 } } ], @@ -1276,78 +1302,91 @@ "defaultMessage": "!!!Available services", "end": { "column": 3, - "line": 12 + "line": 15 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.availableServices", "start": { "column": 21, - "line": 9 + "line": 12 } }, { "defaultMessage": "!!!Your services", "end": { "column": 3, - "line": 16 + "line": 19 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.yourServices", "start": { "column": 16, - "line": 13 + "line": 16 } }, { - "defaultMessage": "!!!Account", + "defaultMessage": "!!!Your workspaces", "end": { "column": 3, + "line": 23 + }, + "file": "src/components/settings/navigation/SettingsNavigation.js", + "id": "settings.navigation.yourWorkspaces", + "start": { + "column": 18, "line": 20 + } + }, + { + "defaultMessage": "!!!Account", + "end": { + "column": 3, + "line": 27 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.account", "start": { "column": 11, - "line": 17 + "line": 24 } }, { "defaultMessage": "!!!Settings", "end": { "column": 3, - "line": 24 + "line": 31 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.settings", "start": { "column": 12, - "line": 21 + "line": 28 } }, { "defaultMessage": "!!!Invite Friends", "end": { "column": 3, - "line": 28 + "line": 35 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.inviteFriends", "start": { "column": 17, - "line": 25 + "line": 32 } }, { "defaultMessage": "!!!Logout", "end": { "column": 3, - "line": 32 + "line": 39 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.logout", "start": { "column": 10, - "line": 29 + "line": 36 } } ], @@ -2496,18 +2535,36 @@ "defaultMessage": "!!!Upgrade account", "end": { "column": 3, - "line": 17 + "line": 18 }, "file": "src/components/ui/PremiumFeatureContainer/index.js", "id": "premiumFeature.button.upgradeAccount", "start": { "column": 10, - "line": 14 + "line": 15 } } ], "path": "src/components/ui/PremiumFeatureContainer/index.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!Loading", + "end": { + "column": 3, + "line": 14 + }, + "file": "src/components/ui/WebviewLoader/index.js", + "id": "service.webviewLoader.loading", + "start": { + "column": 11, + "line": 11 + } + } + ], + "path": "src/components/ui/WebviewLoader/index.json" + }, { "descriptors": [ { @@ -3147,7 +3204,7 @@ } }, { - "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", + "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", "end": { "column": 3, "line": 42 @@ -3165,767 +3222,1200 @@ { "descriptors": [ { - "defaultMessage": "!!!Field is required", + "defaultMessage": "!!!Create workspace", "end": { "column": 3, - "line": 7 + "line": 16 }, - "file": "src/helpers/validation-helpers.js", - "id": "validation.required", + "file": "src/features/workspaces/components/CreateWorkspaceForm.js", + "id": "settings.workspace.add.form.submitButton", "start": { - "column": 12, - "line": 4 + "column": 16, + "line": 13 } }, { - "defaultMessage": "!!!Email not valid", + "defaultMessage": "!!!Name", "end": { "column": 3, - "line": 11 + "line": 20 }, - "file": "src/helpers/validation-helpers.js", - "id": "validation.email", + "file": "src/features/workspaces/components/CreateWorkspaceForm.js", + "id": "settings.workspace.add.form.name", "start": { - "column": 9, - "line": 8 + "column": 8, + "line": 17 } - }, + } + ], + "path": "src/features/workspaces/components/CreateWorkspaceForm.json" + }, + { + "descriptors": [ { - "defaultMessage": "!!!Not a valid URL", + "defaultMessage": "!!!Delete workspace", "end": { "column": 3, - "line": 15 + "line": 22 }, - "file": "src/helpers/validation-helpers.js", - "id": "validation.url", + "file": "src/features/workspaces/components/EditWorkspaceForm.js", + "id": "settings.workspace.form.buttonDelete", "start": { - "column": 7, - "line": 12 - } - }, - { - "defaultMessage": "!!!Too few characters", - "end": { - "column": 3, + "column": 16, "line": 19 - }, - "file": "src/helpers/validation-helpers.js", - "id": "validation.minLength", - "start": { - "column": 13, - "line": 16 } }, { - "defaultMessage": "!!!At least one is required", + "defaultMessage": "!!!Save workspace", "end": { "column": 3, - "line": 23 + "line": 26 }, - "file": "src/helpers/validation-helpers.js", - "id": "validation.oneRequired", + "file": "src/features/workspaces/components/EditWorkspaceForm.js", + "id": "settings.workspace.form.buttonSave", "start": { - "column": 15, - "line": 20 + "column": 14, + "line": 23 } - } - ], - "path": "src/helpers/validation-helpers.json" - }, - { - "descriptors": [ + }, { - "defaultMessage": "!!!Can't connect to Franz Online Services", + "defaultMessage": "!!!Name", "end": { "column": 3, - "line": 7 + "line": 30 }, - "file": "src/i18n/globalMessages.js", - "id": "global.api.unhealthy", + "file": "src/features/workspaces/components/EditWorkspaceForm.js", + "id": "settings.workspace.form.name", "start": { - "column": 16, - "line": 4 + "column": 8, + "line": 27 } }, { - "defaultMessage": "!!!You are not connected to the internet.", + "defaultMessage": "!!!Your workspaces", "end": { "column": 3, - "line": 11 + "line": 34 }, - "file": "src/i18n/globalMessages.js", - "id": "global.notConnectedToTheInternet", + "file": "src/features/workspaces/components/EditWorkspaceForm.js", + "id": "settings.workspace.form.yourWorkspaces", "start": { - "column": 29, - "line": 8 + "column": 18, + "line": 31 } }, { - "defaultMessage": "!!!Spell checking language", + "defaultMessage": "!!!Services in this Workspace", "end": { "column": 3, - "line": 15 + "line": 38 }, - "file": "src/i18n/globalMessages.js", - "id": "global.spellchecking.language", + "file": "src/features/workspaces/components/EditWorkspaceForm.js", + "id": "settings.workspace.form.servicesInWorkspaceHeadline", "start": { - "column": 24, - "line": 12 + "column": 31, + "line": 35 } - }, + } + ], + "path": "src/features/workspaces/components/EditWorkspaceForm.json" + }, + { + "descriptors": [ { - "defaultMessage": "!!!Use System Default ({default})", + "defaultMessage": "!!!Workspaces", "end": { "column": 3, "line": 19 }, - "file": "src/i18n/globalMessages.js", - "id": "global.spellchecker.useDefault", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "id": "workspaceDrawer.headline", "start": { - "column": 29, + "column": 12, "line": 16 } }, { - "defaultMessage": "!!!Detect language automatically", + "defaultMessage": "!!!All services", "end": { "column": 3, "line": 23 }, - "file": "src/i18n/globalMessages.js", - "id": "global.spellchecking.autodetect", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "id": "workspaceDrawer.allServices", "start": { - "column": 34, + "column": 15, "line": 20 } }, { - "defaultMessage": "!!!Automatic", + "defaultMessage": "!!!Workspaces settings", "end": { "column": 3, "line": 27 }, - "file": "src/i18n/globalMessages.js", - "id": "global.spellchecking.autodetect.short", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "id": "workspaceDrawer.workspacesSettingsTooltip", "start": { - "column": 39, + "column": 29, "line": 24 } - } - ], - "path": "src/i18n/globalMessages.json" - }, - { - "descriptors": [ + }, { - "defaultMessage": "!!!Edit", + "defaultMessage": "!!!Info about workspace feature", "end": { "column": 3, - "line": 13 + "line": 31 }, - "file": "src/lib/Menu.js", - "id": "menu.edit", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "id": "workspaceDrawer.workspaceFeatureInfo", "start": { - "column": 8, - "line": 10 + "column": 24, + "line": 28 } }, { - "defaultMessage": "!!!Undo", + "defaultMessage": "!!!Create your first workspace", "end": { "column": 3, - "line": 17 + "line": 35 }, - "file": "src/lib/Menu.js", - "id": "menu.edit.undo", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "id": "workspaceDrawer.premiumCtaButtonLabel", "start": { - "column": 8, - "line": 14 + "column": 25, + "line": 32 } }, { - "defaultMessage": "!!!Redo", + "defaultMessage": "!!!Reactivate premium account", "end": { "column": 3, - "line": 21 + "line": 39 }, - "file": "src/lib/Menu.js", - "id": "menu.edit.redo", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "id": "workspaceDrawer.reactivatePremiumAccountLabel", + "start": { + "column": 28, + "line": 36 + } + }, + { + "defaultMessage": "!!!add new workspace", + "end": { + "column": 3, + "line": 43 + }, + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "id": "workspaceDrawer.addNewWorkspaceLabel", + "start": { + "column": 24, + "line": 40 + } + }, + { + "defaultMessage": "!!!Premium feature", + "end": { + "column": 3, + "line": 47 + }, + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "id": "workspaceDrawer.proFeatureBadge", + "start": { + "column": 23, + "line": 44 + } + } + ], + "path": "src/features/workspaces/components/WorkspaceDrawer.json" + }, + { + "descriptors": [ + { + "defaultMessage": "!!!No services added yet", + "end": { + "column": 3, + "line": 15 + }, + "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", + "id": "workspaceDrawer.item.noServicesAddedYet", + "start": { + "column": 22, + "line": 12 + } + }, + { + "defaultMessage": "!!!edit", + "end": { + "column": 3, + "line": 19 + }, + "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", + "id": "workspaceDrawer.item.contextMenuEdit", + "start": { + "column": 19, + "line": 16 + } + } + ], + "path": "src/features/workspaces/components/WorkspaceDrawerItem.json" + }, + { + "descriptors": [ + { + "defaultMessage": "!!!Your workspaces", + "end": { + "column": 3, + "line": 20 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.headline", + "start": { + "column": 12, + "line": 17 + } + }, + { + "defaultMessage": "!!!You haven't added any workspaces yet.", + "end": { + "column": 3, + "line": 24 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.noWorkspacesAdded", + "start": { + "column": 19, + "line": 21 + } + }, + { + "defaultMessage": "!!!Could not load your workspaces", + "end": { + "column": 3, + "line": 28 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.workspacesRequestFailed", + "start": { + "column": 27, + "line": 25 + } + }, + { + "defaultMessage": "!!!Try again", + "end": { + "column": 3, + "line": 32 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.tryReloadWorkspaces", + "start": { + "column": 23, + "line": 29 + } + }, + { + "defaultMessage": "!!!Your changes have been saved", + "end": { + "column": 3, + "line": 36 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.updatedInfo", + "start": { + "column": 15, + "line": 33 + } + }, + { + "defaultMessage": "!!!Workspace has been deleted", + "end": { + "column": 3, + "line": 40 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.deletedInfo", + "start": { + "column": 15, + "line": 37 + } + }, + { + "defaultMessage": "!!!Info about workspace feature", + "end": { + "column": 3, + "line": 44 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.workspaceFeatureInfo", + "start": { + "column": 24, + "line": 41 + } + }, + { + "defaultMessage": "!!!Less is More: Introducing Franz Workspaces", + "end": { + "column": 3, + "line": 48 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.workspaceFeatureHeadline", + "start": { + "column": 28, + "line": 45 + } + } + ], + "path": "src/features/workspaces/components/WorkspacesDashboard.json" + }, + { + "descriptors": [ + { + "defaultMessage": "!!!Switching to", + "end": { + "column": 3, + "line": 15 + }, + "file": "src/features/workspaces/components/WorkspaceSwitchingIndicator.js", + "id": "workspaces.switchingIndicator.switchingTo", + "start": { + "column": 15, + "line": 12 + } + } + ], + "path": "src/features/workspaces/components/WorkspaceSwitchingIndicator.json" + }, + { + "descriptors": [ + { + "defaultMessage": "!!!Field is required", + "end": { + "column": 3, + "line": 7 + }, + "file": "src/helpers/validation-helpers.js", + "id": "validation.required", + "start": { + "column": 12, + "line": 4 + } + }, + { + "defaultMessage": "!!!Email not valid", + "end": { + "column": 3, + "line": 11 + }, + "file": "src/helpers/validation-helpers.js", + "id": "validation.email", + "start": { + "column": 9, + "line": 8 + } + }, + { + "defaultMessage": "!!!Not a valid URL", + "end": { + "column": 3, + "line": 15 + }, + "file": "src/helpers/validation-helpers.js", + "id": "validation.url", + "start": { + "column": 7, + "line": 12 + } + }, + { + "defaultMessage": "!!!Too few characters", + "end": { + "column": 3, + "line": 19 + }, + "file": "src/helpers/validation-helpers.js", + "id": "validation.minLength", + "start": { + "column": 13, + "line": 16 + } + }, + { + "defaultMessage": "!!!At least one is required", + "end": { + "column": 3, + "line": 23 + }, + "file": "src/helpers/validation-helpers.js", + "id": "validation.oneRequired", + "start": { + "column": 15, + "line": 20 + } + } + ], + "path": "src/helpers/validation-helpers.json" + }, + { + "descriptors": [ + { + "defaultMessage": "!!!Can't connect to Franz Online Services", + "end": { + "column": 3, + "line": 7 + }, + "file": "src/i18n/globalMessages.js", + "id": "global.api.unhealthy", + "start": { + "column": 16, + "line": 4 + } + }, + { + "defaultMessage": "!!!You are not connected to the internet.", + "end": { + "column": 3, + "line": 11 + }, + "file": "src/i18n/globalMessages.js", + "id": "global.notConnectedToTheInternet", + "start": { + "column": 29, + "line": 8 + } + }, + { + "defaultMessage": "!!!Spell checking language", + "end": { + "column": 3, + "line": 15 + }, + "file": "src/i18n/globalMessages.js", + "id": "global.spellchecking.language", + "start": { + "column": 24, + "line": 12 + } + }, + { + "defaultMessage": "!!!Use System Default ({default})", + "end": { + "column": 3, + "line": 19 + }, + "file": "src/i18n/globalMessages.js", + "id": "global.spellchecker.useDefault", + "start": { + "column": 29, + "line": 16 + } + }, + { + "defaultMessage": "!!!Detect language automatically", + "end": { + "column": 3, + "line": 23 + }, + "file": "src/i18n/globalMessages.js", + "id": "global.spellchecking.autodetect", + "start": { + "column": 34, + "line": 20 + } + }, + { + "defaultMessage": "!!!Automatic", + "end": { + "column": 3, + "line": 27 + }, + "file": "src/i18n/globalMessages.js", + "id": "global.spellchecking.autodetect.short", + "start": { + "column": 39, + "line": 24 + } + } + ], + "path": "src/i18n/globalMessages.json" + }, + { + "descriptors": [ + { + "defaultMessage": "!!!Edit", + "end": { + "column": 3, + "line": 16 + }, + "file": "src/lib/Menu.js", + "id": "menu.edit", "start": { "column": 8, - "line": 18 + "line": 13 + } + }, + { + "defaultMessage": "!!!Undo", + "end": { + "column": 3, + "line": 20 + }, + "file": "src/lib/Menu.js", + "id": "menu.edit.undo", + "start": { + "column": 8, + "line": 17 + } + }, + { + "defaultMessage": "!!!Redo", + "end": { + "column": 3, + "line": 24 + }, + "file": "src/lib/Menu.js", + "id": "menu.edit.redo", + "start": { + "column": 8, + "line": 21 } }, { "defaultMessage": "!!!Cut", "end": { "column": 3, - "line": 25 + "line": 28 }, "file": "src/lib/Menu.js", "id": "menu.edit.cut", "start": { "column": 7, - "line": 22 + "line": 25 } }, { "defaultMessage": "!!!Copy", "end": { "column": 3, - "line": 29 + "line": 32 }, "file": "src/lib/Menu.js", "id": "menu.edit.copy", "start": { "column": 8, - "line": 26 + "line": 29 } }, { "defaultMessage": "!!!Paste", "end": { "column": 3, - "line": 33 + "line": 36 }, "file": "src/lib/Menu.js", "id": "menu.edit.paste", "start": { "column": 9, - "line": 30 + "line": 33 } }, { "defaultMessage": "!!!Paste And Match Style", "end": { "column": 3, - "line": 37 + "line": 40 }, "file": "src/lib/Menu.js", "id": "menu.edit.pasteAndMatchStyle", "start": { "column": 22, - "line": 34 + "line": 37 } }, { "defaultMessage": "!!!Delete", "end": { "column": 3, - "line": 41 + "line": 44 }, "file": "src/lib/Menu.js", "id": "menu.edit.delete", "start": { "column": 10, - "line": 38 + "line": 41 } }, { "defaultMessage": "!!!Select All", "end": { "column": 3, - "line": 45 + "line": 48 }, "file": "src/lib/Menu.js", "id": "menu.edit.selectAll", "start": { "column": 13, - "line": 42 + "line": 45 } }, { "defaultMessage": "!!!Speech", "end": { "column": 3, - "line": 49 + "line": 52 }, "file": "src/lib/Menu.js", "id": "menu.edit.speech", "start": { "column": 10, - "line": 46 + "line": 49 } }, { "defaultMessage": "!!!Start Speaking", "end": { "column": 3, - "line": 53 + "line": 56 }, "file": "src/lib/Menu.js", "id": "menu.edit.startSpeaking", "start": { "column": 17, - "line": 50 + "line": 53 } }, { "defaultMessage": "!!!Stop Speaking", "end": { "column": 3, - "line": 57 + "line": 60 }, "file": "src/lib/Menu.js", "id": "menu.edit.stopSpeaking", "start": { "column": 16, - "line": 54 + "line": 57 } }, { "defaultMessage": "!!!Start Dictation", "end": { "column": 3, - "line": 61 + "line": 64 }, "file": "src/lib/Menu.js", "id": "menu.edit.startDictation", "start": { "column": 18, - "line": 58 + "line": 61 } }, { "defaultMessage": "!!!Emoji & Symbols", "end": { "column": 3, - "line": 65 + "line": 68 }, "file": "src/lib/Menu.js", "id": "menu.edit.emojiSymbols", "start": { "column": 16, - "line": 62 + "line": 65 } }, { "defaultMessage": "!!!Actual Size", "end": { "column": 3, - "line": 69 + "line": 72 }, "file": "src/lib/Menu.js", "id": "menu.view.resetZoom", "start": { "column": 13, - "line": 66 + "line": 69 } }, { "defaultMessage": "!!!Zoom In", "end": { "column": 3, - "line": 73 + "line": 76 }, "file": "src/lib/Menu.js", "id": "menu.view.zoomIn", "start": { "column": 10, - "line": 70 + "line": 73 } }, { "defaultMessage": "!!!Zoom Out", "end": { "column": 3, - "line": 77 + "line": 80 }, "file": "src/lib/Menu.js", "id": "menu.view.zoomOut", "start": { "column": 11, - "line": 74 + "line": 77 } }, { "defaultMessage": "!!!Enter Full Screen", "end": { "column": 3, - "line": 81 + "line": 84 }, "file": "src/lib/Menu.js", "id": "menu.view.enterFullScreen", "start": { "column": 19, - "line": 78 + "line": 81 } }, { "defaultMessage": "!!!Exit Full Screen", "end": { "column": 3, - "line": 85 + "line": 88 }, "file": "src/lib/Menu.js", "id": "menu.view.exitFullScreen", "start": { "column": 18, - "line": 82 + "line": 85 } }, { "defaultMessage": "!!!Toggle Full Screen", "end": { "column": 3, - "line": 89 + "line": 92 }, "file": "src/lib/Menu.js", "id": "menu.view.toggleFullScreen", "start": { "column": 20, - "line": 86 + "line": 89 } }, { "defaultMessage": "!!!Toggle Developer Tools", "end": { "column": 3, - "line": 93 + "line": 96 }, "file": "src/lib/Menu.js", "id": "menu.view.toggleDevTools", "start": { "column": 18, - "line": 90 + "line": 93 } }, { "defaultMessage": "!!!Toggle Service Developer Tools", "end": { "column": 3, - "line": 97 + "line": 100 }, "file": "src/lib/Menu.js", "id": "menu.view.toggleServiceDevTools", "start": { "column": 25, - "line": 94 + "line": 97 } }, { "defaultMessage": "!!!Reload Service", "end": { "column": 3, - "line": 101 + "line": 104 }, "file": "src/lib/Menu.js", "id": "menu.view.reloadService", "start": { "column": 17, - "line": 98 + "line": 101 } }, { "defaultMessage": "!!!Reload Franz", "end": { "column": 3, - "line": 105 + "line": 108 }, "file": "src/lib/Menu.js", "id": "menu.view.reloadFranz", "start": { "column": 15, - "line": 102 + "line": 105 } }, { "defaultMessage": "!!!Minimize", "end": { "column": 3, - "line": 109 + "line": 112 }, "file": "src/lib/Menu.js", "id": "menu.window.minimize", "start": { "column": 12, - "line": 106 + "line": 109 } }, { "defaultMessage": "!!!Close", "end": { "column": 3, - "line": 113 + "line": 116 }, "file": "src/lib/Menu.js", "id": "menu.window.close", "start": { "column": 9, - "line": 110 + "line": 113 } }, { "defaultMessage": "!!!Learn More", "end": { "column": 3, - "line": 117 + "line": 120 }, "file": "src/lib/Menu.js", "id": "menu.help.learnMore", "start": { "column": 13, - "line": 114 + "line": 117 } }, { "defaultMessage": "!!!Changelog", "end": { "column": 3, - "line": 121 + "line": 124 }, "file": "src/lib/Menu.js", "id": "menu.help.changelog", "start": { "column": 13, - "line": 118 + "line": 121 } }, { "defaultMessage": "!!!Support", "end": { "column": 3, - "line": 125 + "line": 128 }, "file": "src/lib/Menu.js", "id": "menu.help.support", "start": { "column": 11, - "line": 122 + "line": 125 } }, { "defaultMessage": "!!!Terms of Service", "end": { "column": 3, - "line": 129 + "line": 132 }, "file": "src/lib/Menu.js", "id": "menu.help.tos", "start": { "column": 7, - "line": 126 + "line": 129 } }, { "defaultMessage": "!!!Privacy Statement", "end": { "column": 3, - "line": 133 + "line": 136 }, "file": "src/lib/Menu.js", "id": "menu.help.privacy", "start": { "column": 11, - "line": 130 + "line": 133 } }, { "defaultMessage": "!!!File", "end": { "column": 3, - "line": 137 + "line": 140 }, "file": "src/lib/Menu.js", "id": "menu.file", "start": { "column": 8, - "line": 134 + "line": 137 } }, { "defaultMessage": "!!!View", "end": { "column": 3, - "line": 141 + "line": 144 }, "file": "src/lib/Menu.js", "id": "menu.view", "start": { "column": 8, - "line": 138 + "line": 141 } }, { "defaultMessage": "!!!Services", "end": { "column": 3, - "line": 145 + "line": 148 }, "file": "src/lib/Menu.js", "id": "menu.services", "start": { "column": 12, - "line": 142 + "line": 145 } }, { "defaultMessage": "!!!Window", "end": { "column": 3, - "line": 149 + "line": 152 }, "file": "src/lib/Menu.js", "id": "menu.window", "start": { "column": 10, - "line": 146 + "line": 149 } }, { "defaultMessage": "!!!Help", "end": { "column": 3, - "line": 153 + "line": 156 }, "file": "src/lib/Menu.js", "id": "menu.help", "start": { "column": 8, - "line": 150 + "line": 153 } }, { "defaultMessage": "!!!About Franz", "end": { "column": 3, - "line": 157 + "line": 160 }, "file": "src/lib/Menu.js", "id": "menu.app.about", "start": { "column": 9, - "line": 154 + "line": 157 } }, { "defaultMessage": "!!!Settings", "end": { "column": 3, - "line": 161 + "line": 164 }, "file": "src/lib/Menu.js", "id": "menu.app.settings", "start": { "column": 12, - "line": 158 + "line": 161 } }, { "defaultMessage": "!!!Hide", "end": { "column": 3, - "line": 165 + "line": 168 }, "file": "src/lib/Menu.js", "id": "menu.app.hide", "start": { "column": 8, - "line": 162 + "line": 165 } }, { "defaultMessage": "!!!Hide Others", "end": { "column": 3, - "line": 169 + "line": 172 }, "file": "src/lib/Menu.js", "id": "menu.app.hideOthers", "start": { "column": 14, - "line": 166 + "line": 169 } }, { "defaultMessage": "!!!Unhide", "end": { "column": 3, - "line": 173 + "line": 176 }, "file": "src/lib/Menu.js", "id": "menu.app.unhide", "start": { "column": 10, - "line": 170 + "line": 173 } }, { "defaultMessage": "!!!Quit", "end": { "column": 3, - "line": 177 + "line": 180 }, "file": "src/lib/Menu.js", "id": "menu.app.quit", "start": { "column": 8, - "line": 174 + "line": 177 } }, { "defaultMessage": "!!!Add New Service...", "end": { "column": 3, - "line": 181 + "line": 184 }, "file": "src/lib/Menu.js", "id": "menu.services.addNewService", "start": { "column": 17, - "line": 178 + "line": 181 } }, { - "defaultMessage": "!!!Activate next service...", + "defaultMessage": "!!!Add New Workspace...", "end": { "column": 3, + "line": 188 + }, + "file": "src/lib/Menu.js", + "id": "menu.workspaces.addNewWorkspace", + "start": { + "column": 19, "line": 185 + } + }, + { + "defaultMessage": "!!!Open workspace drawer", + "end": { + "column": 3, + "line": 192 + }, + "file": "src/lib/Menu.js", + "id": "menu.workspaces.openWorkspaceDrawer", + "start": { + "column": 23, + "line": 189 + } + }, + { + "defaultMessage": "!!!Close workspace drawer", + "end": { + "column": 3, + "line": 196 + }, + "file": "src/lib/Menu.js", + "id": "menu.workspaces.closeWorkspaceDrawer", + "start": { + "column": 24, + "line": 193 + } + }, + { + "defaultMessage": "!!!Activate next service...", + "end": { + "column": 3, + "line": 200 }, "file": "src/lib/Menu.js", "id": "menu.services.setNextServiceActive", "start": { "column": 23, - "line": 182 + "line": 197 } }, { "defaultMessage": "!!!Activate previous service...", "end": { "column": 3, - "line": 189 + "line": 204 }, "file": "src/lib/Menu.js", "id": "menu.services.activatePreviousService", "start": { "column": 27, - "line": 186 + "line": 201 } }, { "defaultMessage": "!!!Disable notifications & audio", "end": { "column": 3, - "line": 193 + "line": 208 }, "file": "src/lib/Menu.js", "id": "sidebar.muteApp", "start": { "column": 11, - "line": 190 + "line": 205 } }, { "defaultMessage": "!!!Enable notifications & audio", "end": { "column": 3, - "line": 197 + "line": 212 }, "file": "src/lib/Menu.js", "id": "sidebar.unmuteApp", "start": { "column": 13, - "line": 194 + "line": 209 + } + }, + { + "defaultMessage": "!!!Workspaces", + "end": { + "column": 3, + "line": 216 + }, + "file": "src/lib/Menu.js", + "id": "menu.workspaces", + "start": { + "column": 14, + "line": 213 + } + }, + { + "defaultMessage": "!!!Default", + "end": { + "column": 3, + "line": 220 + }, + "file": "src/lib/Menu.js", + "id": "menu.workspaces.defaultWorkspace", + "start": { + "column": 20, + "line": 217 } } ], 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 @@ "menu.window": "Window", "menu.window.close": "Close", "menu.window.minimize": "Minimize", + "menu.workspaces": "Workspaces", + "menu.workspaces.addNewWorkspace": "Add New Workspace...", + "menu.workspaces.closeWorkspaceDrawer": "Close workspace drawer", + "menu.workspaces.defaultWorkspace": "All services", + "menu.workspaces.openWorkspaceDrawer": "Open workspace drawer", "password.email.label": "Email address", "password.headline": "Reset password", "password.link.login": "Sign in to your account", @@ -110,6 +115,7 @@ "service.errorHandler.headline": "Oh no!", "service.errorHandler.message": "Error", "service.errorHandler.text": "{name} has failed to load.", + "service.webviewLoader.loading": "Loading", "services.getStarted": "Get started", "services.welcome": "Welcome to Franz", "settings.account.account.editButton": "Edit account", @@ -169,6 +175,7 @@ "settings.navigation.logout": "Logout", "settings.navigation.settings": "Settings", "settings.navigation.yourServices": "Your services", + "settings.navigation.yourWorkspaces": "Your workspaces", "settings.recipes.all": "All services", "settings.recipes.dev": "Development", "settings.recipes.headline": "Available services", @@ -235,8 +242,25 @@ "settings.user.form.firstname": "First Name", "settings.user.form.lastname": "Last Name", "settings.user.form.newPassword": "New password", + "settings.workspace.add.form.name": "Name", + "settings.workspace.add.form.submitButton": "Create workspace", + "settings.workspace.form.buttonDelete": "Delete workspace", + "settings.workspace.form.buttonSave": "Save workspace", + "settings.workspace.form.name": "Name", + "settings.workspace.form.servicesInWorkspaceHeadline": "Services in this Workspace", + "settings.workspace.form.yourWorkspaces": "Your workspaces", + "settings.workspaces.deletedInfo": "Workspace has been deleted", + "settings.workspaces.headline": "Your workspaces", + "settings.workspaces.noWorkspacesAdded": "You haven't added any workspaces yet.", + "settings.workspaces.tryReloadWorkspaces": "Try again", + "settings.workspaces.updatedInfo": "Your changes have been saved", + "settings.workspaces.workspaceFeatureHeadline": "Less is More: Introducing Franz Workspaces", + "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.", + "settings.workspaces.workspacesRequestFailed": "Could not load your workspaces", "sidebar.addNewService": "Add new service", + "sidebar.closeWorkspaceDrawer": "Close workspace drawer", "sidebar.muteApp": "Disable notifications & audio", + "sidebar.openWorkspaceDrawer": "Open workspace drawer", "sidebar.settings": "Settings", "sidebar.unmuteApp": "Enable notifications & audio", "signup.company.label": "Company", @@ -281,5 +305,16 @@ "validation.required": "{field} is required", "validation.url": "{field} is not a valid URL", "welcome.loginButton": "Login to your account", - "welcome.signupButton": "Create a free account" -} + "welcome.signupButton": "Create a free account", + "workspaceDrawer.addNewWorkspaceLabel": "Add new workspace", + "workspaceDrawer.allServices": "All services", + "workspaceDrawer.headline": "Workspaces", + "workspaceDrawer.item.contextMenuEdit": "edit", + "workspaceDrawer.item.noServicesAddedYet": "No services added yet", + "workspaceDrawer.premiumCtaButtonLabel": "Create your first workspace", + "workspaceDrawer.proFeatureBadge": "Premium feature", + "workspaceDrawer.reactivatePremiumAccountLabel": "Reactivate premium account", + "workspaceDrawer.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.

", + "workspaceDrawer.workspacesSettingsTooltip": "Edit workspaces settings", + "workspaces.switchingIndicator.switchingTo": "Switching to" +} \ 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 @@ "defaultMessage": "!!!Your services have been updated.", "file": "src/components/layout/AppLayout.js", "start": { - "line": 22, + "line": 25, "column": 19 }, "end": { - "line": 25, + "line": 28, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!A new update for Franz is available.", "file": "src/components/layout/AppLayout.js", "start": { - "line": 26, + "line": 29, "column": 19 }, "end": { - "line": 29, + "line": 32, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Reload services", "file": "src/components/layout/AppLayout.js", "start": { - "line": 30, + "line": 33, "column": 24 }, "end": { - "line": 33, + "line": 36, "column": 3 } }, @@ -43,11 +43,11 @@ "defaultMessage": "!!!Changelog", "file": "src/components/layout/AppLayout.js", "start": { - "line": 34, + "line": 37, "column": 13 }, "end": { - "line": 37, + "line": 40, "column": 3 } }, @@ -56,11 +56,11 @@ "defaultMessage": "!!!Restart & install update", "file": "src/components/layout/AppLayout.js", "start": { - "line": 38, + "line": 41, "column": 23 }, "end": { - "line": 41, + "line": 44, "column": 3 } }, @@ -69,11 +69,11 @@ "defaultMessage": "!!!Could not load services and user information", "file": "src/components/layout/AppLayout.js", "start": { - "line": 42, + "line": 45, "column": 26 }, "end": { - "line": 45, + "line": 48, "column": 3 } } 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 @@ "defaultMessage": "!!!Settings", "file": "src/components/layout/Sidebar.js", "start": { - "line": 11, + "line": 13, "column": 12 }, "end": { - "line": 14, + "line": 16, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!Add new service", "file": "src/components/layout/Sidebar.js", "start": { - "line": 15, + "line": 17, "column": 17 }, "end": { - "line": 18, + "line": 20, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Disable notifications & audio", "file": "src/components/layout/Sidebar.js", "start": { - "line": 19, + "line": 21, "column": 8 }, "end": { - "line": 22, + "line": 24, "column": 3 } }, @@ -43,11 +43,37 @@ "defaultMessage": "!!!Enable notifications & audio", "file": "src/components/layout/Sidebar.js", "start": { - "line": 23, + "line": 25, "column": 10 }, "end": { - "line": 26, + "line": 28, + "column": 3 + } + }, + { + "id": "sidebar.openWorkspaceDrawer", + "defaultMessage": "!!!Open workspace drawer", + "file": "src/components/layout/Sidebar.js", + "start": { + "line": 29, + "column": 23 + }, + "end": { + "line": 32, + "column": 3 + } + }, + { + "id": "sidebar.closeWorkspaceDrawer", + "defaultMessage": "!!!Close workspace drawer", + "file": "src/components/layout/Sidebar.js", + "start": { + "line": 33, + "column": 24 + }, + "end": { + "line": 36, "column": 3 } } 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 @@ "defaultMessage": "!!!Available services", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 9, + "line": 12, "column": 21 }, "end": { - "line": 12, + "line": 15, "column": 3 } }, @@ -17,11 +17,24 @@ "defaultMessage": "!!!Your services", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 13, + "line": 16, "column": 16 }, "end": { - "line": 16, + "line": 19, + "column": 3 + } + }, + { + "id": "settings.navigation.yourWorkspaces", + "defaultMessage": "!!!Your workspaces", + "file": "src/components/settings/navigation/SettingsNavigation.js", + "start": { + "line": 20, + "column": 18 + }, + "end": { + "line": 23, "column": 3 } }, @@ -30,11 +43,11 @@ "defaultMessage": "!!!Account", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 17, + "line": 24, "column": 11 }, "end": { - "line": 20, + "line": 27, "column": 3 } }, @@ -43,11 +56,11 @@ "defaultMessage": "!!!Settings", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 21, + "line": 28, "column": 12 }, "end": { - "line": 24, + "line": 31, "column": 3 } }, @@ -56,11 +69,11 @@ "defaultMessage": "!!!Invite Friends", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 25, + "line": 32, "column": 17 }, "end": { - "line": 28, + "line": 35, "column": 3 } }, @@ -69,11 +82,11 @@ "defaultMessage": "!!!Logout", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 29, + "line": 36, "column": 10 }, "end": { - "line": 32, + "line": 39, "column": 3 } } 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 @@ "defaultMessage": "!!!Upgrade account", "file": "src/components/ui/PremiumFeatureContainer/index.js", "start": { - "line": 14, + "line": 15, "column": 10 }, "end": { - "line": 17, + "line": 18, "column": 3 } } 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 @@ +[ + { + "id": "service.webviewLoader.loading", + "defaultMessage": "!!!Loading", + "file": "src/components/ui/WebviewLoader/index.js", + "start": { + "line": 11, + "column": 11 + }, + "end": { + "line": 14, + "column": 3 + } + } +] \ 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 @@ +[ + { + "id": "settings.workspace.add.form.submitButton", + "defaultMessage": "!!!Create workspace", + "file": "src/features/workspaces/components/CreateWorkspaceForm.js", + "start": { + "line": 13, + "column": 16 + }, + "end": { + "line": 16, + "column": 3 + } + }, + { + "id": "settings.workspace.add.form.name", + "defaultMessage": "!!!Name", + "file": "src/features/workspaces/components/CreateWorkspaceForm.js", + "start": { + "line": 17, + "column": 8 + }, + "end": { + "line": 20, + "column": 3 + } + } +] \ 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 @@ +[ + { + "id": "settings.workspace.form.buttonDelete", + "defaultMessage": "!!!Delete workspace", + "file": "src/features/workspaces/components/EditWorkspaceForm.js", + "start": { + "line": 19, + "column": 16 + }, + "end": { + "line": 22, + "column": 3 + } + }, + { + "id": "settings.workspace.form.buttonSave", + "defaultMessage": "!!!Save workspace", + "file": "src/features/workspaces/components/EditWorkspaceForm.js", + "start": { + "line": 23, + "column": 14 + }, + "end": { + "line": 26, + "column": 3 + } + }, + { + "id": "settings.workspace.form.name", + "defaultMessage": "!!!Name", + "file": "src/features/workspaces/components/EditWorkspaceForm.js", + "start": { + "line": 27, + "column": 8 + }, + "end": { + "line": 30, + "column": 3 + } + }, + { + "id": "settings.workspace.form.yourWorkspaces", + "defaultMessage": "!!!Your workspaces", + "file": "src/features/workspaces/components/EditWorkspaceForm.js", + "start": { + "line": 31, + "column": 18 + }, + "end": { + "line": 34, + "column": 3 + } + }, + { + "id": "settings.workspace.form.servicesInWorkspaceHeadline", + "defaultMessage": "!!!Services in this Workspace", + "file": "src/features/workspaces/components/EditWorkspaceForm.js", + "start": { + "line": 35, + "column": 31 + }, + "end": { + "line": 38, + "column": 3 + } + } +] \ 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 @@ +[ + { + "id": "workspaceDrawer.headline", + "defaultMessage": "!!!Workspaces", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "start": { + "line": 16, + "column": 12 + }, + "end": { + "line": 19, + "column": 3 + } + }, + { + "id": "workspaceDrawer.allServices", + "defaultMessage": "!!!All services", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "start": { + "line": 20, + "column": 15 + }, + "end": { + "line": 23, + "column": 3 + } + }, + { + "id": "workspaceDrawer.workspacesSettingsTooltip", + "defaultMessage": "!!!Workspaces settings", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "start": { + "line": 24, + "column": 29 + }, + "end": { + "line": 27, + "column": 3 + } + }, + { + "id": "workspaceDrawer.workspaceFeatureInfo", + "defaultMessage": "!!!Info about workspace feature", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "start": { + "line": 28, + "column": 24 + }, + "end": { + "line": 31, + "column": 3 + } + }, + { + "id": "workspaceDrawer.premiumCtaButtonLabel", + "defaultMessage": "!!!Create your first workspace", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "start": { + "line": 32, + "column": 25 + }, + "end": { + "line": 35, + "column": 3 + } + }, + { + "id": "workspaceDrawer.reactivatePremiumAccountLabel", + "defaultMessage": "!!!Reactivate premium account", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "start": { + "line": 36, + "column": 28 + }, + "end": { + "line": 39, + "column": 3 + } + }, + { + "id": "workspaceDrawer.addNewWorkspaceLabel", + "defaultMessage": "!!!add new workspace", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "start": { + "line": 40, + "column": 24 + }, + "end": { + "line": 43, + "column": 3 + } + }, + { + "id": "workspaceDrawer.proFeatureBadge", + "defaultMessage": "!!!Premium feature", + "file": "src/features/workspaces/components/WorkspaceDrawer.js", + "start": { + "line": 44, + "column": 23 + }, + "end": { + "line": 47, + "column": 3 + } + } +] \ 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 @@ +[ + { + "id": "workspaceDrawer.item.noServicesAddedYet", + "defaultMessage": "!!!No services added yet", + "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", + "start": { + "line": 12, + "column": 22 + }, + "end": { + "line": 15, + "column": 3 + } + }, + { + "id": "workspaceDrawer.item.contextMenuEdit", + "defaultMessage": "!!!edit", + "file": "src/features/workspaces/components/WorkspaceDrawerItem.js", + "start": { + "line": 16, + "column": 19 + }, + "end": { + "line": 19, + "column": 3 + } + } +] \ 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 @@ +[ + { + "id": "workspaces.switchingIndicator.switchingTo", + "defaultMessage": "!!!Switching to", + "file": "src/features/workspaces/components/WorkspaceSwitchingIndicator.js", + "start": { + "line": 12, + "column": 15 + }, + "end": { + "line": 15, + "column": 3 + } + } +] \ 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 @@ +[ + { + "id": "settings.workspaces.headline", + "defaultMessage": "!!!Your workspaces", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 17, + "column": 12 + }, + "end": { + "line": 20, + "column": 3 + } + }, + { + "id": "settings.workspaces.noWorkspacesAdded", + "defaultMessage": "!!!You haven't added any workspaces yet.", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 21, + "column": 19 + }, + "end": { + "line": 24, + "column": 3 + } + }, + { + "id": "settings.workspaces.workspacesRequestFailed", + "defaultMessage": "!!!Could not load your workspaces", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 25, + "column": 27 + }, + "end": { + "line": 28, + "column": 3 + } + }, + { + "id": "settings.workspaces.tryReloadWorkspaces", + "defaultMessage": "!!!Try again", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 29, + "column": 23 + }, + "end": { + "line": 32, + "column": 3 + } + }, + { + "id": "settings.workspaces.updatedInfo", + "defaultMessage": "!!!Your changes have been saved", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 33, + "column": 15 + }, + "end": { + "line": 36, + "column": 3 + } + }, + { + "id": "settings.workspaces.deletedInfo", + "defaultMessage": "!!!Workspace has been deleted", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 37, + "column": 15 + }, + "end": { + "line": 40, + "column": 3 + } + }, + { + "id": "settings.workspaces.workspaceFeatureInfo", + "defaultMessage": "!!!Info about workspace feature", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 41, + "column": 24 + }, + "end": { + "line": 44, + "column": 3 + } + }, + { + "id": "settings.workspaces.workspaceFeatureHeadline", + "defaultMessage": "!!!Less is More: Introducing Franz Workspaces", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 45, + "column": 28 + }, + "end": { + "line": 48, + "column": 3 + } + } +] \ 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 @@ "defaultMessage": "!!!Edit", "file": "src/lib/Menu.js", "start": { - "line": 10, + "line": 13, "column": 8 }, "end": { - "line": 13, + "line": 16, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!Undo", "file": "src/lib/Menu.js", "start": { - "line": 14, + "line": 17, "column": 8 }, "end": { - "line": 17, + "line": 20, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Redo", "file": "src/lib/Menu.js", "start": { - "line": 18, + "line": 21, "column": 8 }, "end": { - "line": 21, + "line": 24, "column": 3 } }, @@ -43,11 +43,11 @@ "defaultMessage": "!!!Cut", "file": "src/lib/Menu.js", "start": { - "line": 22, + "line": 25, "column": 7 }, "end": { - "line": 25, + "line": 28, "column": 3 } }, @@ -56,11 +56,11 @@ "defaultMessage": "!!!Copy", "file": "src/lib/Menu.js", "start": { - "line": 26, + "line": 29, "column": 8 }, "end": { - "line": 29, + "line": 32, "column": 3 } }, @@ -69,11 +69,11 @@ "defaultMessage": "!!!Paste", "file": "src/lib/Menu.js", "start": { - "line": 30, + "line": 33, "column": 9 }, "end": { - "line": 33, + "line": 36, "column": 3 } }, @@ -82,11 +82,11 @@ "defaultMessage": "!!!Paste And Match Style", "file": "src/lib/Menu.js", "start": { - "line": 34, + "line": 37, "column": 22 }, "end": { - "line": 37, + "line": 40, "column": 3 } }, @@ -95,11 +95,11 @@ "defaultMessage": "!!!Delete", "file": "src/lib/Menu.js", "start": { - "line": 38, + "line": 41, "column": 10 }, "end": { - "line": 41, + "line": 44, "column": 3 } }, @@ -108,11 +108,11 @@ "defaultMessage": "!!!Select All", "file": "src/lib/Menu.js", "start": { - "line": 42, + "line": 45, "column": 13 }, "end": { - "line": 45, + "line": 48, "column": 3 } }, @@ -121,11 +121,11 @@ "defaultMessage": "!!!Speech", "file": "src/lib/Menu.js", "start": { - "line": 46, + "line": 49, "column": 10 }, "end": { - "line": 49, + "line": 52, "column": 3 } }, @@ -134,11 +134,11 @@ "defaultMessage": "!!!Start Speaking", "file": "src/lib/Menu.js", "start": { - "line": 50, + "line": 53, "column": 17 }, "end": { - "line": 53, + "line": 56, "column": 3 } }, @@ -147,11 +147,11 @@ "defaultMessage": "!!!Stop Speaking", "file": "src/lib/Menu.js", "start": { - "line": 54, + "line": 57, "column": 16 }, "end": { - "line": 57, + "line": 60, "column": 3 } }, @@ -160,11 +160,11 @@ "defaultMessage": "!!!Start Dictation", "file": "src/lib/Menu.js", "start": { - "line": 58, + "line": 61, "column": 18 }, "end": { - "line": 61, + "line": 64, "column": 3 } }, @@ -173,11 +173,11 @@ "defaultMessage": "!!!Emoji & Symbols", "file": "src/lib/Menu.js", "start": { - "line": 62, + "line": 65, "column": 16 }, "end": { - "line": 65, + "line": 68, "column": 3 } }, @@ -186,11 +186,11 @@ "defaultMessage": "!!!Actual Size", "file": "src/lib/Menu.js", "start": { - "line": 66, + "line": 69, "column": 13 }, "end": { - "line": 69, + "line": 72, "column": 3 } }, @@ -199,11 +199,11 @@ "defaultMessage": "!!!Zoom In", "file": "src/lib/Menu.js", "start": { - "line": 70, + "line": 73, "column": 10 }, "end": { - "line": 73, + "line": 76, "column": 3 } }, @@ -212,11 +212,11 @@ "defaultMessage": "!!!Zoom Out", "file": "src/lib/Menu.js", "start": { - "line": 74, + "line": 77, "column": 11 }, "end": { - "line": 77, + "line": 80, "column": 3 } }, @@ -225,11 +225,11 @@ "defaultMessage": "!!!Enter Full Screen", "file": "src/lib/Menu.js", "start": { - "line": 78, + "line": 81, "column": 19 }, "end": { - "line": 81, + "line": 84, "column": 3 } }, @@ -238,11 +238,11 @@ "defaultMessage": "!!!Exit Full Screen", "file": "src/lib/Menu.js", "start": { - "line": 82, + "line": 85, "column": 18 }, "end": { - "line": 85, + "line": 88, "column": 3 } }, @@ -251,11 +251,11 @@ "defaultMessage": "!!!Toggle Full Screen", "file": "src/lib/Menu.js", "start": { - "line": 86, + "line": 89, "column": 20 }, "end": { - "line": 89, + "line": 92, "column": 3 } }, @@ -264,11 +264,11 @@ "defaultMessage": "!!!Toggle Developer Tools", "file": "src/lib/Menu.js", "start": { - "line": 90, + "line": 93, "column": 18 }, "end": { - "line": 93, + "line": 96, "column": 3 } }, @@ -277,11 +277,11 @@ "defaultMessage": "!!!Toggle Service Developer Tools", "file": "src/lib/Menu.js", "start": { - "line": 94, + "line": 97, "column": 25 }, "end": { - "line": 97, + "line": 100, "column": 3 } }, @@ -290,11 +290,11 @@ "defaultMessage": "!!!Reload Service", "file": "src/lib/Menu.js", "start": { - "line": 98, + "line": 101, "column": 17 }, "end": { - "line": 101, + "line": 104, "column": 3 } }, @@ -303,11 +303,11 @@ "defaultMessage": "!!!Reload Franz", "file": "src/lib/Menu.js", "start": { - "line": 102, + "line": 105, "column": 15 }, "end": { - "line": 105, + "line": 108, "column": 3 } }, @@ -316,11 +316,11 @@ "defaultMessage": "!!!Minimize", "file": "src/lib/Menu.js", "start": { - "line": 106, + "line": 109, "column": 12 }, "end": { - "line": 109, + "line": 112, "column": 3 } }, @@ -329,11 +329,11 @@ "defaultMessage": "!!!Close", "file": "src/lib/Menu.js", "start": { - "line": 110, + "line": 113, "column": 9 }, "end": { - "line": 113, + "line": 116, "column": 3 } }, @@ -342,11 +342,11 @@ "defaultMessage": "!!!Learn More", "file": "src/lib/Menu.js", "start": { - "line": 114, + "line": 117, "column": 13 }, "end": { - "line": 117, + "line": 120, "column": 3 } }, @@ -355,11 +355,11 @@ "defaultMessage": "!!!Changelog", "file": "src/lib/Menu.js", "start": { - "line": 118, + "line": 121, "column": 13 }, "end": { - "line": 121, + "line": 124, "column": 3 } }, @@ -368,11 +368,11 @@ "defaultMessage": "!!!Support", "file": "src/lib/Menu.js", "start": { - "line": 122, + "line": 125, "column": 11 }, "end": { - "line": 125, + "line": 128, "column": 3 } }, @@ -381,11 +381,11 @@ "defaultMessage": "!!!Terms of Service", "file": "src/lib/Menu.js", "start": { - "line": 126, + "line": 129, "column": 7 }, "end": { - "line": 129, + "line": 132, "column": 3 } }, @@ -394,11 +394,11 @@ "defaultMessage": "!!!Privacy Statement", "file": "src/lib/Menu.js", "start": { - "line": 130, + "line": 133, "column": 11 }, "end": { - "line": 133, + "line": 136, "column": 3 } }, @@ -407,11 +407,11 @@ "defaultMessage": "!!!File", "file": "src/lib/Menu.js", "start": { - "line": 134, + "line": 137, "column": 8 }, "end": { - "line": 137, + "line": 140, "column": 3 } }, @@ -420,11 +420,11 @@ "defaultMessage": "!!!View", "file": "src/lib/Menu.js", "start": { - "line": 138, + "line": 141, "column": 8 }, "end": { - "line": 141, + "line": 144, "column": 3 } }, @@ -433,11 +433,11 @@ "defaultMessage": "!!!Services", "file": "src/lib/Menu.js", "start": { - "line": 142, + "line": 145, "column": 12 }, "end": { - "line": 145, + "line": 148, "column": 3 } }, @@ -446,11 +446,11 @@ "defaultMessage": "!!!Window", "file": "src/lib/Menu.js", "start": { - "line": 146, + "line": 149, "column": 10 }, "end": { - "line": 149, + "line": 152, "column": 3 } }, @@ -459,11 +459,11 @@ "defaultMessage": "!!!Help", "file": "src/lib/Menu.js", "start": { - "line": 150, + "line": 153, "column": 8 }, "end": { - "line": 153, + "line": 156, "column": 3 } }, @@ -472,11 +472,11 @@ "defaultMessage": "!!!About Franz", "file": "src/lib/Menu.js", "start": { - "line": 154, + "line": 157, "column": 9 }, "end": { - "line": 157, + "line": 160, "column": 3 } }, @@ -485,11 +485,11 @@ "defaultMessage": "!!!Settings", "file": "src/lib/Menu.js", "start": { - "line": 158, + "line": 161, "column": 12 }, "end": { - "line": 161, + "line": 164, "column": 3 } }, @@ -498,11 +498,11 @@ "defaultMessage": "!!!Hide", "file": "src/lib/Menu.js", "start": { - "line": 162, + "line": 165, "column": 8 }, "end": { - "line": 165, + "line": 168, "column": 3 } }, @@ -511,11 +511,11 @@ "defaultMessage": "!!!Hide Others", "file": "src/lib/Menu.js", "start": { - "line": 166, + "line": 169, "column": 14 }, "end": { - "line": 169, + "line": 172, "column": 3 } }, @@ -524,11 +524,11 @@ "defaultMessage": "!!!Unhide", "file": "src/lib/Menu.js", "start": { - "line": 170, + "line": 173, "column": 10 }, "end": { - "line": 173, + "line": 176, "column": 3 } }, @@ -537,11 +537,11 @@ "defaultMessage": "!!!Quit", "file": "src/lib/Menu.js", "start": { - "line": 174, + "line": 177, "column": 8 }, "end": { - "line": 177, + "line": 180, "column": 3 } }, @@ -550,11 +550,50 @@ "defaultMessage": "!!!Add New Service...", "file": "src/lib/Menu.js", "start": { - "line": 178, + "line": 181, "column": 17 }, "end": { - "line": 181, + "line": 184, + "column": 3 + } + }, + { + "id": "menu.workspaces.addNewWorkspace", + "defaultMessage": "!!!Add New Workspace...", + "file": "src/lib/Menu.js", + "start": { + "line": 185, + "column": 19 + }, + "end": { + "line": 188, + "column": 3 + } + }, + { + "id": "menu.workspaces.openWorkspaceDrawer", + "defaultMessage": "!!!Open workspace drawer", + "file": "src/lib/Menu.js", + "start": { + "line": 189, + "column": 23 + }, + "end": { + "line": 192, + "column": 3 + } + }, + { + "id": "menu.workspaces.closeWorkspaceDrawer", + "defaultMessage": "!!!Close workspace drawer", + "file": "src/lib/Menu.js", + "start": { + "line": 193, + "column": 24 + }, + "end": { + "line": 196, "column": 3 } }, @@ -563,11 +602,11 @@ "defaultMessage": "!!!Activate next service...", "file": "src/lib/Menu.js", "start": { - "line": 182, + "line": 197, "column": 23 }, "end": { - "line": 185, + "line": 200, "column": 3 } }, @@ -576,11 +615,11 @@ "defaultMessage": "!!!Activate previous service...", "file": "src/lib/Menu.js", "start": { - "line": 186, + "line": 201, "column": 27 }, "end": { - "line": 189, + "line": 204, "column": 3 } }, @@ -589,11 +628,11 @@ "defaultMessage": "!!!Disable notifications & audio", "file": "src/lib/Menu.js", "start": { - "line": 190, + "line": 205, "column": 11 }, "end": { - "line": 193, + "line": 208, "column": 3 } }, @@ -602,11 +641,37 @@ "defaultMessage": "!!!Enable notifications & audio", "file": "src/lib/Menu.js", "start": { - "line": 194, + "line": 209, "column": 13 }, "end": { - "line": 197, + "line": 212, + "column": 3 + } + }, + { + "id": "menu.workspaces", + "defaultMessage": "!!!Workspaces", + "file": "src/lib/Menu.js", + "start": { + "line": 213, + "column": 14 + }, + "end": { + "line": 216, + "column": 3 + } + }, + { + "id": "menu.workspaces.defaultWorkspace", + "defaultMessage": "!!!Default", + "file": "src/lib/Menu.js", + "start": { + "line": 217, + "column": 20 + }, + "end": { + "line": 220, "column": 3 } } 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'; import { defineMessages } from 'react-intl'; import { isMac, ctrlKey, cmdKey } from '../environment'; +import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../features/workspaces/index'; +import { workspaceActions } from '../features/workspaces/actions'; +import { gaEvent } from './analytics'; const { app, Menu, dialog } = remote; @@ -179,6 +182,18 @@ const menuItems = defineMessages({ id: 'menu.services.addNewService', defaultMessage: '!!!Add New Service...', }, + addNewWorkspace: { + id: 'menu.workspaces.addNewWorkspace', + defaultMessage: '!!!Add New Workspace...', + }, + openWorkspaceDrawer: { + id: 'menu.workspaces.openWorkspaceDrawer', + defaultMessage: '!!!Open workspace drawer', + }, + closeWorkspaceDrawer: { + id: 'menu.workspaces.closeWorkspaceDrawer', + defaultMessage: '!!!Close workspace drawer', + }, activateNextService: { id: 'menu.services.setNextServiceActive', defaultMessage: '!!!Activate next service...', @@ -195,6 +210,14 @@ const menuItems = defineMessages({ id: 'sidebar.unmuteApp', defaultMessage: '!!!Enable notifications & audio', }, + workspaces: { + id: 'menu.workspaces', + defaultMessage: '!!!Workspaces', + }, + defaultWorkspace: { + id: 'menu.workspaces.defaultWorkspace', + defaultMessage: '!!!Default', + }, }); function getActiveWebview() { @@ -297,6 +320,11 @@ const _templateFactory = intl => [ label: intl.formatMessage(menuItems.services), submenu: [], }, + { + label: intl.formatMessage(menuItems.workspaces), + submenu: [], + visible: workspaceStore.isFeatureEnabled, + }, { label: intl.formatMessage(menuItems.window), role: 'window', @@ -669,7 +697,7 @@ export default class FranzMenu { }, ); - tpl[4].submenu.unshift(about, { + tpl[5].submenu.unshift(about, { type: 'separator', }); } else { @@ -704,6 +732,10 @@ export default class FranzMenu { tpl[3].submenu = serviceTpl; } + if (workspaceStore.isFeatureEnabled) { + tpl[4].submenu = this.workspacesMenu(); + } + this.currentTemplate = tpl; const menu = Menu.buildFromTemplate(tpl); Menu.setApplicationMenu(menu); @@ -754,6 +786,66 @@ export default class FranzMenu { return menu; } + workspacesMenu() { + const { workspaces, activeWorkspace, isWorkspaceDrawerOpen } = workspaceStore; + const { intl } = window.franz; + const menu = []; + + // Add new workspace item: + menu.push({ + label: intl.formatMessage(menuItems.addNewWorkspace), + accelerator: `${cmdKey}+Shift+N`, + click: () => { + workspaceActions.openWorkspaceSettings(); + }, + enabled: this.stores.user.isLoggedIn, + }); + + // Open workspace drawer: + const drawerLabel = ( + isWorkspaceDrawerOpen ? menuItems.closeWorkspaceDrawer : menuItems.openWorkspaceDrawer + ); + menu.push({ + label: intl.formatMessage(drawerLabel), + accelerator: `${cmdKey}+D`, + click: () => { + workspaceActions.toggleWorkspaceDrawer(); + gaEvent(GA_CATEGORY_WORKSPACES, 'toggleDrawer', 'menu'); + }, + enabled: this.stores.user.isLoggedIn, + }, { + type: 'separator', + }); + + // Default workspace + menu.push({ + label: intl.formatMessage(menuItems.defaultWorkspace), + accelerator: `${cmdKey}+Alt+0`, + type: 'radio', + checked: !activeWorkspace, + click: () => { + workspaceActions.deactivate(); + gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'menu'); + }, + }); + + // Workspace items + if (this.stores.user.isPremium) { + workspaces.forEach((workspace, i) => menu.push({ + label: workspace.name, + accelerator: i < 9 ? `${cmdKey}+Alt+${i + 1}` : null, + type: 'radio', + checked: activeWorkspace ? workspace.id === activeWorkspace.id : false, + click: () => { + workspaceActions.activate({ workspace }); + gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'menu'); + }, + })); + } + + return menu; + } + _getServiceName(service) { if (service.name) { 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'); export function gaPage(page) { ga('send', 'pageview', page); - debug('GA track page', page); } export function gaEvent(category, action, label) { ga('send', 'event', category, action, label); - - debug('GA track event', category, action); + debug('GA track event', category, action, label); } 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 @@ -import { computed, observable, reaction } from 'mobx'; +import { + computed, + observable, + reaction, + runInAction, +} from 'mobx'; import Store from './lib/Store'; import CachedRequest from './lib/CachedRequest'; @@ -7,6 +12,7 @@ import delayApp from '../features/delayApp'; import spellchecker from '../features/spellchecker'; import serviceProxy from '../features/serviceProxy'; import basicAuth from '../features/basicAuth'; +import workspaces from '../features/workspaces'; import shareFranz from '../features/shareFranz'; import { DEFAULT_FEATURES_CONFIG } from '../config'; @@ -16,13 +22,16 @@ export default class FeaturesStore extends Store { @observable featuresRequest = new CachedRequest(this.api.features, 'features'); + @observable features = Object.assign({}, DEFAULT_FEATURES_CONFIG); + async setup() { this.registerReactions([ + this._updateFeatures, this._monitorLoginStatus.bind(this), ]); await this.featuresRequest._promise; - setTimeout(this._enableFeatures.bind(this), 1); + setTimeout(this._setupFeatures.bind(this), 1); // single key reaction reaction(() => this.stores.user.data.isPremium, () => { @@ -36,13 +45,16 @@ export default class FeaturesStore extends Store { return this.defaultFeaturesRequest.execute().result || DEFAULT_FEATURES_CONFIG; } - @computed get features() { + _updateFeatures = () => { + const features = Object.assign({}, DEFAULT_FEATURES_CONFIG); if (this.stores.user.isLoggedIn) { - return this.featuresRequest.execute().result || DEFAULT_FEATURES_CONFIG; + const requestResult = this.featuresRequest.execute().result; + Object.assign(features, requestResult); } - - return DEFAULT_FEATURES_CONFIG; - } + runInAction('FeaturesStore::_updateFeatures', () => { + this.features = features; + }); + }; _monitorLoginStatus() { if (this.stores.user.isLoggedIn) { @@ -52,11 +64,12 @@ export default class FeaturesStore extends Store { } } - _enableFeatures() { + _setupFeatures() { delayApp(this.stores, this.actions); spellchecker(this.stores, this.actions); serviceProxy(this.stores, this.actions); basicAuth(this.stores, this.actions); + workspaces(this.stores, this.actions); shareFranz(this.stores, this.actions); } } 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'; import CachedRequest from './lib/CachedRequest'; import { matchRoute } from '../helpers/routing-helpers'; import { gaEvent } from '../lib/analytics'; +import { workspaceStore } from '../features/workspaces'; const debug = require('debug')('Franz:ServiceStore'); @@ -99,7 +100,6 @@ export default class ServicesStore extends Store { return observable(services.slice().slice().sort((a, b) => a.order - b.order)); } } - return []; } @@ -108,13 +108,16 @@ export default class ServicesStore extends Store { } @computed get allDisplayed() { - return this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; + const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; + return workspaceStore.filterServicesByActiveWorkspace(services); } // This is just used to avoid unnecessary rerendering of resource-heavy webviews @computed get allDisplayedUnordered() { + const { showDisabledServices } = this.stores.settings.all.app; const services = this.allServicesRequest.execute().result || []; - return this.stores.settings.all.app.showDisabledServices ? services : services.filter(service => service.isEnabled); + const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled); + return workspaceStore.filterServicesByActiveWorkspace(filteredServices); } @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 { return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.isAppMuted; } - @computed get theme() { - if (this.stores.settings.all.app.darkMode) { - return theme('dark'); - } + @computed get isDarkThemeActive() { + return this.stores.settings.all.app.darkMode; + } + @computed get theme() { + if (this.isDarkThemeActive) return theme('dark'); return theme('default'); } 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 { return this.getUserInfoRequest.execute().result || {}; } + @computed get isPremium() { + return !!this.data.isPremium; + } + @computed get legacyServices() { return this.getLegacyServicesRequest.execute() || {}; } 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 { return this.execute(...this._currentApiCall.args); } + retry = () => this.reload(); + isExecutingWithArgs(...args) { return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args); } @@ -107,7 +109,7 @@ export default class Request { Request._hooks.forEach(hook => hook(this)); } - reset() { + reset = () => { this.result = null; this.isExecuting = false; this.isError = false; @@ -116,5 +118,5 @@ export default class Request { this._promise = Promise; return this; - } + }; } 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; } font-size: 22px; &:hover, - &:active { color: $dark-theme-gray-smoke; } - &.is-muted { color: $theme-brand-primary; } + &:active { + color: $dark-theme-gray-smoke; + } + + &.is-muted, + &.is-active { + color: $theme-brand-primary; + } } } @@ -33,6 +39,7 @@ html { overflow: hidden; } .app__content { display: flex; } .app__service { + position: relative; display: flex; flex: 1; flex-direction: column; @@ -84,7 +91,7 @@ html { overflow: hidden; } &:hover, &:active { color: lighten($theme-gray-light, 10%); } - &.is-muted { color: $theme-brand-primary; } + &.is-muted, &.is-active { color: $theme-brand-primary; } &--new-service { padding-bottom: 6px; } } 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 @@ } } - .premium-info { + .premium-info { background: $dark-theme-gray-darker; border: 2px solid $theme-brand-primary; } @@ -414,6 +414,7 @@ .settings-navigation__link { align-items: center; + justify-content: space-between; color: $theme-text-color; display: flex; flex-shrink: 0; @@ -425,7 +426,9 @@ &:hover { background: darken($theme-gray-lightest, 5%); - .badge { background: #FFF; } + .badge { + background: #FFF; + } } &.is-active { @@ -442,8 +445,8 @@ .settings-navigation__expander { flex: 1; } .badge { + display: initial; - margin-left: 5px; transition: background $theme-transition-time, color $theme-transition-time; } 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 @@ import React from 'react'; -import { Badge } from '@meetfranz/ui'; +import { Badge, ProBadge } from '@meetfranz/ui'; import { storiesOf } from '../stores/stories'; storiesOf('Badge') @@ -18,4 +18,14 @@ storiesOf('Badge') danger inverted + )) + .add('Pro Badge', () => ( + <> + + + )) + .add('Pro Badge inverted', () => ( + <> + + )); -- cgit v1.2.3-54-g00ecf