hippo-fw 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/client/hippo/access/subscription-choice-layer.jsx +170 -0
  3. data/client/hippo/access/subscription-choice-layer/cancel-subscription.jsx +111 -0
  4. data/client/hippo/access/subscription-choice-layer/payment-form.jsx +154 -0
  5. data/client/hippo/access/subscription-choice-layer/subscription-choice.scss +29 -0
  6. data/client/hippo/boot.jsx +1 -1
  7. data/client/hippo/components/asset.jsx +1 -1
  8. data/client/hippo/components/asset.scss +1 -0
  9. data/client/hippo/components/data-table.jsx +36 -38
  10. data/client/hippo/components/date-time.jsx +25 -9
  11. data/client/hippo/components/form/api.js +1 -0
  12. data/client/hippo/components/form/fields.jsx +3 -2
  13. data/client/hippo/components/form/fields/checkbox-wrapper.jsx +1 -1
  14. data/client/hippo/components/form/fields/date-wrapper.jsx +1 -1
  15. data/client/hippo/components/form/fields/email-wrapper.jsx +31 -0
  16. data/client/hippo/components/form/fields/form-field.scss +8 -0
  17. data/client/hippo/components/form/fields/label.jsx +5 -7
  18. data/client/hippo/components/form/fields/select-wrapper.jsx +1 -1
  19. data/client/hippo/components/form/fields/tags-wrapper.jsx +1 -1
  20. data/client/hippo/components/form/fields/text-wrapper.jsx +1 -1
  21. data/client/hippo/components/form/fields/textarea-wrapper.jsx +1 -1
  22. data/client/hippo/components/form/wrapper.jsx +1 -1
  23. data/client/hippo/components/grid.js +1 -0
  24. data/client/hippo/components/master-detail.jsx +1 -1
  25. data/client/hippo/components/network-activity-overlay.jsx +6 -6
  26. data/client/hippo/components/payments/field.jsx +64 -0
  27. data/client/hippo/components/payments/field.scss +18 -0
  28. data/client/hippo/components/popout-window.jsx +1 -1
  29. data/client/hippo/components/query-builder.jsx +1 -1
  30. data/client/hippo/components/query-builder/boolean-picker.jsx +1 -1
  31. data/client/hippo/components/query-builder/clause-filter.jsx +2 -2
  32. data/client/hippo/components/query-builder/clause.jsx +16 -10
  33. data/client/hippo/components/query-builder/date-picker.jsx +1 -1
  34. data/client/hippo/components/query-builder/query-builder.scss +23 -2
  35. data/client/hippo/components/record-finder.jsx +5 -2
  36. data/client/hippo/components/record-finder/query-layer.jsx +1 -1
  37. data/client/hippo/components/save-button.jsx +1 -1
  38. data/client/hippo/components/screen.jsx +1 -1
  39. data/client/hippo/components/text-editor.jsx +82 -40
  40. data/client/hippo/components/text-editor/renderer.jsx +15 -35
  41. data/client/hippo/components/text-editor/renderer.scss +15 -0
  42. data/client/hippo/components/text-editor/text-editor.scss +2 -15
  43. data/client/hippo/components/text-editor/upload-adapter.js +66 -0
  44. data/client/hippo/components/time-zone-select.jsx +1 -1
  45. data/client/hippo/components/tool-tip.jsx +9 -14
  46. data/client/hippo/components/toolbar.jsx +16 -0
  47. data/client/hippo/components/warning-notification.jsx +1 -1
  48. data/client/hippo/extensions/base.js +3 -2
  49. data/client/hippo/extensions/index.js +1 -1
  50. data/client/hippo/lib/action_cable.js +8 -0
  51. data/client/hippo/lib/action_cable/cable.js +47 -0
  52. data/client/hippo/lib/action_cable/connection.js +192 -0
  53. data/client/hippo/lib/action_cable/connection_monitor.js +135 -0
  54. data/client/hippo/lib/action_cable/consumer.js +56 -0
  55. data/client/hippo/lib/action_cable/subscription.js +98 -0
  56. data/client/hippo/lib/action_cable/subscriptions.js +129 -0
  57. data/client/hippo/lib/date-range.js +22 -5
  58. data/client/hippo/lib/lazy-getter.js +31 -0
  59. data/client/hippo/lib/util.js +6 -1
  60. data/client/hippo/models/base.js +8 -3
  61. data/client/hippo/models/config.js +7 -2
  62. data/client/hippo/models/date-type.js +14 -0
  63. data/client/hippo/models/decorators.js +5 -5
  64. data/client/hippo/models/pub_sub.js +1 -1
  65. data/client/hippo/models/query/array-result.js +4 -1
  66. data/client/hippo/models/subscription.js +35 -0
  67. data/client/hippo/models/sync.js +1 -1
  68. data/client/hippo/models/tenant.js +14 -1
  69. data/client/hippo/react/component-not-found.jsx +14 -18
  70. data/client/hippo/screens/async-loading.jsx +46 -0
  71. data/client/hippo/screens/definition.js +2 -1
  72. data/client/hippo/screens/preferences.jsx +57 -0
  73. data/client/hippo/screens/system-settings.jsx +4 -6
  74. data/client/hippo/screens/system-settings/mailer-config.jsx +1 -1
  75. data/client/hippo/screens/system-settings/tenant.jsx +57 -4
  76. data/client/hippo/screens/user-management.jsx +2 -2
  77. data/client/hippo/screens/user-management/edit-form.jsx +1 -1
  78. data/client/hippo/styles/global/fancy-header.scss +2 -1
  79. data/client/hippo/styles/global/mixins.scss +14 -1
  80. data/client/hippo/testing/index.js +7 -0
  81. data/client/hippo/user.js +9 -2
  82. data/client/hippo/workspace/index.jsx +29 -8
  83. data/client/hippo/workspace/menu-group.jsx +1 -1
  84. data/client/hippo/workspace/menu-option.jsx +2 -0
  85. data/client/hippo/workspace/menu.jsx +30 -6
  86. data/client/hippo/workspace/screen.jsx +5 -1
  87. data/client/hippo/workspace/styles.scss +22 -3
  88. data/command-reference-files/initial/Gemfile +1 -1
  89. data/config/routes.rb +6 -0
  90. data/config/screens.rb +9 -17
  91. data/db/migrate/20171129024737_create_subscriptions.rb +12 -0
  92. data/fixtures/vcr_cassettes/Tenant_changes/sends_email_when_tenant_identifier_changes.yml +72 -0
  93. data/fixtures/vcr_cassettes/Tenant_isoloation/disallows_using_a_user_s_token_on_incorrect_domain.yml +141 -0
  94. data/fixtures/vcr_cassettes/Tenant_isoloation/isolates_bar_s_tenant_data_from_foo.yml +141 -0
  95. data/fixtures/vcr_cassettes/Tenant_isoloation/isolates_foo_s_tenant_data_from_bar.yml +141 -0
  96. data/hippo-fw.gemspec +4 -3
  97. data/lib/hippo.rb +1 -0
  98. data/lib/hippo/access/roles/basic_user.rb +1 -0
  99. data/lib/hippo/api/authentication_provider.rb +4 -5
  100. data/lib/hippo/api/handlers/asset.rb +9 -4
  101. data/lib/hippo/api/handlers/subscription.rb +39 -0
  102. data/lib/hippo/api/handlers/user_session.rb +0 -1
  103. data/lib/hippo/api/helper_methods.rb +8 -4
  104. data/lib/hippo/api/request_wrapper.rb +12 -1
  105. data/lib/hippo/command/console.rb +3 -3
  106. data/lib/hippo/concerns/asset_uploader.rb +8 -2
  107. data/lib/hippo/concerns/pub_sub.rb +8 -8
  108. data/lib/hippo/configuration.rb +6 -4
  109. data/lib/hippo/extension.rb +3 -2
  110. data/lib/hippo/model.rb +1 -0
  111. data/lib/hippo/models/subscription.rb +7 -0
  112. data/lib/hippo/payments.rb +129 -0
  113. data/lib/hippo/screen.rb +4 -2
  114. data/lib/hippo/screen/definition.rb +4 -0
  115. data/lib/hippo/spec_helper.rb +4 -4
  116. data/lib/hippo/templates/liquid/precision.rb +9 -0
  117. data/lib/hippo/tenant.rb +4 -5
  118. data/lib/hippo/user.rb +5 -5
  119. data/lib/hippo/version.rb +1 -1
  120. data/lib/hippo/webpack.rb +5 -1
  121. data/package-lock.json +437 -881
  122. data/package.json +19 -5
  123. data/spec/client/components/__snapshots__/query-builder.spec.jsx.snap +34 -1
  124. data/spec/client/components/__snapshots__/record-finder.spec.jsx.snap +1 -0
  125. data/spec/client/models/base.spec.js +7 -0
  126. data/spec/client/models/subscription.spec.js +8 -0
  127. data/spec/client/screens/__snapshots__/preferences.spec.jsx.snap +223 -0
  128. data/spec/client/screens/preferences.spec.jsx +10 -0
  129. data/spec/client/test-models.js +1 -1
  130. data/spec/client/workspace/__snapshots__/menu.spec.jsx.snap +12 -0
  131. data/spec/factories/subscription.rb +6 -0
  132. data/spec/factories/tenant.rb +1 -1
  133. data/spec/factories/user.rb +1 -1
  134. data/spec/server/api/tenant_change_spec.rb +11 -8
  135. data/spec/server/api/tenant_isolation_spec.rb +11 -8
  136. data/spec/server/api/user_sessions_spec.rb +10 -7
  137. data/spec/server/api/user_spec.rb +45 -0
  138. data/spec/server/models/subscription_spec.rb +10 -0
  139. data/spec/server/payment_helpers.rb +13 -0
  140. data/spec/server/print/form_spec.rb +1 -1
  141. data/spec/server/spec_helper.rb +3 -11
  142. data/templates/js/screen-definitions.js +4 -1
  143. data/templates/spec/factories/model.rb +1 -1
  144. data/views/hippo_root_view.erb +5 -5
  145. metadata +84 -25
  146. data/client/hippo/components/grid/config.json +0 -3
  147. data/client/hippo/components/grid/editors.scss +0 -78
  148. data/client/hippo/components/grid/index.js +0 -2
  149. data/client/hippo/components/grid/row-editor.scss +0 -74
  150. data/client/hippo/components/grid/styles.scss +0 -118
  151. data/client/hippo/components/text-editor/display-modes/Button.jsx +0 -20
  152. data/client/hippo/components/text-editor/display-modes/ToggleEdit.jsx +0 -23
  153. data/client/hippo/components/text-editor/display-modes/ToggleInsert.jsx +0 -22
  154. data/client/hippo/components/text-editor/display-modes/ToggleLayout.jsx +0 -22
  155. data/client/hippo/components/text-editor/display-modes/TogglePreview.jsx +0 -22
  156. data/client/hippo/components/text-editor/display-modes/ToggleResize.jsx +0 -22
  157. data/client/hippo/components/text-editor/display-modes/index.css +0 -0
  158. data/client/hippo/components/text-editor/display-modes/index.js +0 -30
  159. data/client/hippo/components/text-editor/image-plugin/Component/Display/index.js +0 -82
  160. data/client/hippo/components/text-editor/image-plugin/Component/Form/index.js +0 -42
  161. data/client/hippo/components/text-editor/image-plugin/Component/index.js +0 -16
  162. data/client/hippo/components/text-editor/image-plugin/Component/index.scss +0 -0
  163. data/client/hippo/components/text-editor/image-plugin/index.js +0 -32
  164. data/client/hippo/components/text-editor/image-plugin/index.scss +0 -25
  165. data/client/hippo/components/toolbar/changes-notification.scss +0 -63
  166. data/client/hippo/components/toolbar/index.js +0 -3
  167. data/client/hippo/components/toolbar/styles.scss +0 -74
@@ -7,7 +7,7 @@ import Heading from 'grommet/components/Heading';
7
7
  import { Form, Field, FormState, nonBlank, validEmail } from 'hippo/components/form';
8
8
 
9
9
  @observer
10
- export default class MailerConfig extends React.PureComponent {
10
+ export default class MailerConfig extends React.Component {
11
11
 
12
12
  formState = new FormState()
13
13
 
@@ -1,19 +1,21 @@
1
1
  import React from 'react';
2
2
  import { observer } from 'mobx-react';
3
- import { observable } from 'mobx';
3
+ import { observable, action } from 'mobx';
4
4
  import { Row } from 'react-flexbox-grid';
5
5
  import Heading from 'grommet/components/Heading';
6
6
  import Box from 'grommet/components/Box';
7
7
  import { Form, Field, FormState, nonBlank } from 'hippo/components/form';
8
-
9
8
  import Notification from 'grommet/components/Notification';
10
9
  import Layer from 'grommet/components/Layer';
11
10
  import Paragraph from 'grommet/components/Paragraph';
12
11
  import Anchor from 'grommet/components/Anchor';
12
+ import Button from 'grommet/components/Button';
13
13
  import LinkIcon from 'grommet/components/icons/base/Link';
14
-
14
+ import UserAdminIcon from 'grommet/components/icons/base/UserAdmin';
15
+ import ClearIcon from 'grommet/components/icons/base/Clear';
15
16
  import Tenant from '../../models/tenant';
16
17
  import Config from '../../config';
18
+ import SubscriptionChoiceLayer from '../../access/subscription-choice-layer';
17
19
 
18
20
  function onTenantSlugChangeClose() { }
19
21
 
@@ -33,11 +35,13 @@ function TenantSlugChange({ oldSlug }) {
33
35
  }
34
36
 
35
37
  @observer
36
- export default class TenantConfig extends React.PureComponent {
38
+ export default class TenantConfig extends React.Component {
37
39
 
38
40
  formState = new FormState()
39
41
 
40
42
  @observable slugChangedFrom;
43
+ @observable isCanceling;
44
+ @observable showSubscriptionChoice = false;
41
45
 
42
46
  onSave() {
43
47
  const originalSlug = Tenant.current.slug;
@@ -51,10 +55,37 @@ export default class TenantConfig extends React.PureComponent {
51
55
  });
52
56
  }
53
57
 
58
+ @action.bound onSubscriptionChange() {
59
+ this.showSubscriptionChoice = {
60
+ isCanceling: false,
61
+ };
62
+ }
63
+
64
+ @action.bound onSubscriptionCancel() {
65
+ this.showSubscriptionChoice = {
66
+ isCanceling: true,
67
+ };
68
+ }
69
+
70
+ @action.bound hideSubscriptionChoice() {
71
+ this.showSubscriptionChoice = false;
72
+ }
73
+
54
74
  componentWillMount() {
55
75
  this.formState.setFromModel(Tenant.current);
56
76
  }
57
77
 
78
+ renderSubscriptionChoice() {
79
+ if (!this.showSubscriptionChoice) { return null; }
80
+ return (
81
+ <SubscriptionChoiceLayer
82
+ displayCancel
83
+ {...this.showSubscriptionChoice}
84
+ onCancel={this.hideSubscriptionChoice}
85
+ onChoice={this.hideSubscriptionChoice} />
86
+ );
87
+ }
88
+
58
89
  renderIdChangeWarning() {
59
90
  const slug = this.formState.get('slug.value');
60
91
  if (!Tenant.current.slug || slug === Tenant.current.slug) {
@@ -71,6 +102,7 @@ export default class TenantConfig extends React.PureComponent {
71
102
  render() {
72
103
  return (
73
104
  <Form tag="div" className="tenant-edit-form" state={this.formState}>
105
+ {this.renderSubscriptionChoice()}
74
106
  <TenantSlugChange oldSlug={this.slugChangedFrom} />
75
107
  <Heading tag="h3">Account</Heading>
76
108
  <Row>
@@ -81,6 +113,27 @@ export default class TenantConfig extends React.PureComponent {
81
113
  />
82
114
  <Field xs={6} name="name" validate={nonBlank} />
83
115
  </Row>
116
+ <Row>
117
+ <Field
118
+ xs={12} type="label"
119
+ label="Subscription"
120
+ name="subscription_name"
121
+ value={
122
+ <Box direction="row" justify="between">
123
+ <Button
124
+ plain label={Tenant.current.subscription.nameAndCost}
125
+ icon={<UserAdminIcon />}
126
+ onClick={this.onSubscriptionChange}
127
+ />
128
+ <Button
129
+ plain label="Cancel"
130
+ icon={<ClearIcon />}
131
+ onClick={this.onSubscriptionCancel}
132
+ />
133
+ </Box>
134
+ }
135
+ />
136
+ </Row>
84
137
  {this.renderIdChangeWarning()}
85
138
  </Form>
86
139
  );
@@ -14,7 +14,7 @@ import Query from 'hippo/models/query';
14
14
  import Editor from './user-management/edit-form';
15
15
 
16
16
  @observer
17
- export default class UserManagement extends React.PureComponent {
17
+ export default class UserManagement extends React.Component {
18
18
 
19
19
  @observable editingId;
20
20
 
@@ -53,7 +53,7 @@ export default class UserManagement extends React.PureComponent {
53
53
  render() {
54
54
  return (
55
55
  <Screen {...this.props}>
56
- <Heading>Manage User</Heading>
56
+ <Heading tag="h3">User Management</Heading>
57
57
  <DataTable
58
58
  canCreate
59
59
  query={this.query}
@@ -14,7 +14,7 @@ import Query from '../../models/query';
14
14
  import { Form, Field, FormState, nonBlank, validEmail, booleanValue } from '../../components/form';
15
15
 
16
16
  @observer
17
- export default class EditForm extends React.PureComponent {
17
+ export default class EditForm extends React.Component {
18
18
 
19
19
  static propTypes = {
20
20
  query: PropTypes.instanceOf(Query).isRequired,
@@ -3,7 +3,8 @@
3
3
  align-items: center;
4
4
  justify-content: center;
5
5
  flex-direction: column;
6
- min-height: 400px;
6
+ margin: 50px 0;
7
+
7
8
  :first-child {
8
9
  border: 1px solid black;
9
10
  border-width: 1px 0;
@@ -1,5 +1,18 @@
1
- @mixin hidden-print {
1
+ @mixin hidden-print() {
2
2
  @media print {
3
3
  display: none !important;
4
4
  }
5
5
  }
6
+
7
+ @mixin workspace-screen-padding($exclude: '.grommetux-header__container--fixed') {
8
+
9
+ > *:not(#{$exclude}) {
10
+ margin: 0.5rem;
11
+ }
12
+ > .row {
13
+ & + .row {
14
+ margin-top: 0;
15
+ }
16
+ }
17
+
18
+ }
@@ -1,7 +1,14 @@
1
1
  /* global jest */
2
+ import Tenant from 'hippo/models/tenant';
3
+ import Config from 'hippo/config';
4
+ import Subscription from 'hippo/models/subscription';
5
+
2
6
  export React from 'react';
3
7
 
4
8
  export { getScreenInstance, Snapshot } from './screens';
5
9
 
10
+ Config.subscription_plans = [new Subscription({ id: 1, price: '42.42' })];
11
+ Tenant.current.subscription_id = Subscription.all[0].id;
12
+
6
13
  jest.mock('hippo/config');
7
14
  jest.mock('hippo/models/sync');
@@ -1,6 +1,6 @@
1
1
  import { pick, merge, includes, omit } from 'lodash';
2
-
3
2
  import { action } from 'mobx';
3
+ import Sync from './models/sync';
4
4
 
5
5
  import {
6
6
  BaseModel, identifiedBy, field, identifier, computed,
@@ -30,7 +30,7 @@ const ADMIN = 'administrator';
30
30
  @identifiedBy('hippo/user')
31
31
  export class UserModel extends BaseModel {
32
32
 
33
- @identifier({ type: 'number' }) id;
33
+ @identifier id;
34
34
  @field login;
35
35
  @field name;
36
36
  @field email;
@@ -87,6 +87,13 @@ export class UserModel extends BaseModel {
87
87
  return merge(this.serialize(), { is_admin: this.is_admin });
88
88
  }
89
89
 
90
+ fetch() {
91
+ return Sync.forModel(this, {
92
+ url: `${Config.api_path}/${UserModel.identifiedBy}/current`,
93
+ method: 'GET',
94
+ });
95
+ }
96
+
90
97
  }
91
98
 
92
99
  const current_user = new UserModel();
@@ -14,10 +14,13 @@ import 'hippo/config-data';
14
14
  import 'hippo/screen-definitions';
15
15
  import Button from 'grommet/components/Button';
16
16
  import CirclePlayIcon from 'grommet/components/icons/base/CirclePlay';
17
+ import User from '../user';
18
+ import Tenant from '../models/tenant';
17
19
  import Extensions from '../extensions';
18
20
  import Menu from './menu';
19
21
  import Screen from './screen';
20
22
  import LoginDialog from '../access/login-dialog';
23
+ import SubscriptionChoiceLayer from '../access/subscription-choice-layer';
21
24
 
22
25
  import './styles.scss';
23
26
 
@@ -65,28 +68,46 @@ class Workspace extends React.Component {
65
68
  this.sidebarOpen = !this.sidebarOpen;
66
69
  }
67
70
 
71
+ get isDragSupported() {
72
+ return Boolean('ontouchstart' in window);
73
+ }
74
+
75
+ renderOpenButton() {
76
+ if (this.isDragSupported) { return null; }
77
+ return (
78
+ <Button
79
+ primary
80
+ icon={<CirclePlayIcon />}
81
+ onClick={this.toggleSidebarDocked}
82
+ className={cn('sidebar-toggle', { 'is-open': this.sidebarOpen })}
83
+ />
84
+ );
85
+ }
86
+
87
+ renderSubscriptionChoice() {
88
+ if (!User.isLoggedIn || Tenant.current.hasSubscription) { return null; }
89
+ return <SubscriptionChoiceLayer />;
90
+ }
91
+
68
92
  render() {
69
93
  return (
70
94
  <App centered={false}>
71
95
  <LoginDialog />
96
+ {this.renderSubscriptionChoice()}
72
97
  <Sidebar
73
98
  styles={{ sidebar: { zIndex: 60 }, overlay: { zIndex: 59 } }}
74
99
  sidebar={<Menu
75
100
  isOpen={this.sidebarOpen}
76
101
  isDocked={this.sidebarDocked}
77
102
  onCloseMenu={this.toggleSidebarDocked}
78
- onDockToggle={this.toggleSidebarDocked}
79
- />}
103
+ onDockToggle={this.toggleSidebarDocked} />
104
+ }
80
105
  open={this.sidebarOpen}
81
106
  docked={this.sidebarDocked}
82
107
  onSetOpen={this.onSetSidebarOpen}
83
108
  >
84
- <Button
85
- primary
86
- icon={<CirclePlayIcon />}
87
- onClick={this.toggleSidebarDocked}
88
- className={cn('sidebar-toggle', { 'is-open': this.sidebarOpen })}
89
- />
109
+ {this.renderOpenButton()}
110
+
90
111
  <Switch>
91
112
  <Route name='screen' path="/:screenId/:identifier?" component={Screen} />
92
113
  <Route component={NoMatch} />
@@ -10,7 +10,7 @@ import MenuOption from './menu-option';
10
10
  import Icon from '../components/icon';
11
11
 
12
12
  @observer
13
- export default class Group extends React.PureComponent {
13
+ export default class Group extends React.Component {
14
14
 
15
15
  static propTypes = {
16
16
  group: PropTypes.shape({
@@ -9,6 +9,7 @@ import Icon from '../components/icon';
9
9
  export default class MenuOption extends React.Component {
10
10
 
11
11
  static propTypes = {
12
+ onSelection: PropTypes.func.isRequired,
12
13
  screen: PropTypes.shape({
13
14
  title: PropTypes.string.isRequired,
14
15
  icon: PropTypes.string.isRequired,
@@ -22,6 +23,7 @@ export default class MenuOption extends React.Component {
22
23
 
23
24
  @action.bound
24
25
  activateScreen() {
26
+ this.props.onSelection(this.props.screen);
25
27
  this.props.screen.display();
26
28
  }
27
29
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { observer } from 'mobx-react';
3
- import { action } from 'mobx';
3
+ import { action, computed, observe } from 'mobx';
4
4
  import { isEmpty, get } from 'lodash';
5
5
  import PropTypes from 'prop-types';
6
6
  import Box from 'grommet/components/Box';
@@ -18,18 +18,31 @@ import Config from '../config';
18
18
  import Asset from '../models/asset';
19
19
 
20
20
  @observer
21
- class Logout extends React.PureComponent {
21
+ class Logout extends React.Component {
22
22
 
23
23
  static contextTypes = {
24
24
  router: PropTypes.object,
25
25
  }
26
26
 
27
+ componentWillUnmount() {
28
+ this.stopObserving();
29
+ }
30
+
31
+ componentWillMount() {
32
+ this.stopObserving = observe(
33
+ User, 'isLoggedIn', (change) => {
34
+ if (change.oldValue && !change.newValue) {
35
+ this.context.router.history.replace('/');
36
+ }
37
+ },
38
+ );
39
+ }
40
+
27
41
  @action.bound
28
42
  onLogoutClick(ev) {
29
43
  ev.stopPropagation();
30
44
  ev.preventDefault();
31
45
  User.logout();
32
- this.context.router.history.push('/');
33
46
  }
34
47
 
35
48
  render() {
@@ -59,19 +72,30 @@ const Logo = observer(() => {
59
72
  });
60
73
 
61
74
  @observer
62
- export default class WorkspaceMenu extends React.PureComponent {
75
+ export default class WorkspaceMenu extends React.Component {
63
76
 
64
77
  renderUnGrouped() {
65
78
  if (!User.isLoggedIn || isEmpty(Screens.unGrouped)) { return null; }
66
79
  return (
67
80
  <Menu direction="column" align="start" justify="between" primary>
68
- {Screens.unGrouped.map(s => <MenuOption key={s.id} screen={s} />)}
81
+ {Screens.unGrouped.map(s =>
82
+ <MenuOption
83
+ onSelection={this.onMenuSelection}
84
+ key={s.id} screen={s} />)}
69
85
  </Menu>
70
86
  );
71
87
  }
72
88
 
89
+ @action.bound onMenuSelection() {
90
+ if (this.canClose) { this.props.onCloseMenu(); }
91
+ }
92
+
93
+ @computed get canClose() {
94
+ return (this.props.isOpen && !this.props.isDocked);
95
+ }
96
+
73
97
  renderClose() {
74
- if (this.props.isOpen && !this.props.isDocked) {
98
+ if (this.canClose) {
75
99
  return <Button icon={<CloseIcon />} onClick={this.props.onCloseMenu} plain />;
76
100
  }
77
101
  return null;
@@ -42,7 +42,11 @@ export default class Screen extends React.Component {
42
42
  flex="grow"
43
43
  >
44
44
  {displaying.models.map(s =>
45
- <ScreenView key={s.id} {...this.props.match.params} screen={s} />)}
45
+ <ScreenView
46
+ key={s.id}
47
+ {...this.props.match.params}
48
+ screen={s}
49
+ />)}
46
50
  </Box>
47
51
 
48
52
  );
@@ -1,12 +1,13 @@
1
1
  @import '../styles/global';
2
2
  @import '~grommet/scss/aruba/index';
3
3
 
4
- .screen {
4
+ .hippo-screen {
5
5
  flex: 1;
6
6
  flex-direction: column;
7
- padding: 0.5rem;
7
+
8
8
  display: none;
9
9
 
10
+ @include workspace-screen-padding();
10
11
 
11
12
  min-height: min-content; min-height: -moz-min-content; min-height: -webkit-min-content;
12
13
 
@@ -40,5 +41,23 @@
40
41
  }
41
42
 
42
43
  .screens-wrapper {
43
- overflow: auto
44
+ overflow: auto;
45
+ -webkit-overflow-scrolling: touch;
46
+ .async-loading {
47
+ align-items: center;
48
+ font-weight: bold;
49
+ font-size: 28px;
50
+ justify-content: center;
51
+ .content {
52
+ margin-bottom: 20vh;
53
+ display: flex;
54
+ flex-direction: column;
55
+ align-items: center;
56
+ h4 {
57
+ display: flex;
58
+ align-items: center;
59
+ svg { margin-right: 1rem; }
60
+ }
61
+ }
62
+ }
44
63
  }