hippo-fw 0.9.6 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/client/hippo/components/asset.jsx +22 -2
  3. data/client/hippo/components/asset.scss +1 -1
  4. data/client/hippo/components/data-list.jsx +38 -27
  5. data/client/hippo/components/data-list/data-list.scss +2 -0
  6. data/client/hippo/components/data-table.jsx +4 -2
  7. data/client/hippo/components/data-table/formatters.js +7 -0
  8. data/client/hippo/components/data-table/table-styles.scss +10 -0
  9. data/client/hippo/components/date-time.jsx +93 -133
  10. data/client/hippo/components/date-time.scss +1 -36
  11. data/client/hippo/components/form/api.js +4 -3
  12. data/client/hippo/components/form/fields.jsx +7 -1
  13. data/client/hippo/components/form/fields/date-wrapper.jsx +4 -4
  14. data/client/hippo/components/icon.jsx +10 -1
  15. data/client/hippo/components/query-builder.jsx +6 -1
  16. data/client/hippo/components/record-finder/query-layer.jsx +1 -1
  17. data/client/hippo/components/save-button.jsx +55 -0
  18. data/client/hippo/components/text-editor.jsx +10 -9
  19. data/client/hippo/components/text-editor/text-editor.scss +0 -1
  20. data/client/hippo/components/time-zone-select.jsx +60 -0
  21. data/client/hippo/models/asset.js +10 -5
  22. data/client/hippo/models/base.js +1 -1
  23. data/client/hippo/models/pub_sub.js +22 -67
  24. data/client/hippo/models/pub_sub/channel.js +28 -8
  25. data/client/hippo/models/pub_sub/map.js +57 -0
  26. data/client/hippo/models/query/array-result.js +5 -4
  27. data/client/hippo/models/query/field.js +19 -3
  28. data/client/hippo/models/system-setting.js +16 -1
  29. data/client/hippo/models/tenant.js +8 -7
  30. data/client/hippo/screens/system-settings.jsx +10 -12
  31. data/client/hippo/screens/user-management/edit-form.jsx +10 -11
  32. data/client/hippo/workspace/index.jsx +6 -3
  33. data/client/hippo/workspace/menu-option.jsx +2 -5
  34. data/client/hippo/workspace/menu.jsx +13 -1
  35. data/client/hippo/workspace/styles.scss +11 -26
  36. data/command-reference-files/initial/Gemfile +1 -1
  37. data/command-reference-files/model/client/appy-app/models/test_test.js +1 -1
  38. data/command-reference-files/model/spec/server/models/test_test_spec.rb +10 -0
  39. data/lib/hippo/api/cable.rb +0 -2
  40. data/lib/hippo/command/generate_model.rb +2 -3
  41. data/lib/hippo/spec_helper.rb +4 -2
  42. data/lib/hippo/tenant.rb +7 -1
  43. data/lib/hippo/version.rb +1 -1
  44. data/package-lock.json +60 -46
  45. data/package.json +5 -2
  46. data/spec/client/components/__snapshots__/record-finder.spec.jsx.snap +1 -0
  47. data/spec/client/components/__snapshots__/time-zone-select.spec.jsx.snap +48 -0
  48. data/spec/client/components/form.spec.jsx +7 -0
  49. data/spec/client/components/time-zone-select.spec.jsx +11 -0
  50. data/spec/client/models/pub_sub.spec.js +1 -3
  51. data/spec/client/models/pub_sub/channel.spec.js +45 -0
  52. data/spec/client/models/system-setting.spec.js +14 -0
  53. data/spec/client/workspace/__snapshots__/menu.spec.jsx.snap +9 -9
  54. data/spec/server/api/tenant_change_spec.rb +1 -1
  55. data/templates/client/models/model.js +1 -1
  56. data/templates/spec/factories/model.rb +6 -0
  57. data/templates/spec/server/model_spec.rb +4 -4
  58. data/views/hippo_root_view.erb +4 -0
  59. metadata +11 -10
  60. data/client/hippo/components/date-time/calendar.jsx +0 -113
  61. data/client/hippo/components/date-time/date-time-drop.jsx +0 -75
  62. data/client/hippo/components/date-time/date-time.scss +0 -157
  63. data/client/hippo/components/date-time/time.jsx +0 -119
  64. data/client/hippo/workspace/foo.js +0 -0
  65. data/command-reference-files/model/spec/fixtures/appy-app/test_test.yml +0 -11
  66. data/command-reference-files/model/spec/server/test_test_spec.rb +0 -10
  67. data/spec/client/components/date-time.spec.jsx +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ea4e1fab04ebb80ec4cadd61c6eb529e71cb9da7
4
- data.tar.gz: 2f8567b02e13defacc31d703b526739b6f964551
3
+ metadata.gz: cf25cc10af49e41c6a3b48817f8bc1b50853484e
4
+ data.tar.gz: 7afc125a95df6077aa39e6f6acec0d42c3d926a5
5
5
  SHA512:
6
- metadata.gz: 8007c42f669e7e8dde8b2696fa9a5e2d10a2690c4d7cff9a32e69e214429375c750b6ebbbccdeab401c10bf4f7d1f52f636df2ae5938ec6ee93880e699ef60b8
7
- data.tar.gz: 7e9cc5649badcc65a8ec65ceae4a5e6f960ca35690c8f4656b911f9e41a8267558ff42744150307284a310631306b3e04aee8a4584c30a12330dc5cb130754d8
6
+ metadata.gz: 98ee0dc3e62a19bf1d3aa0503781a0b374061a0490c06a73039cb8f3040b455cfb9a18b157ae54c1940ecb27f9cb19f054656e9bd185a5276058809deaf6edad
7
+ data.tar.gz: 3217e075db4cac207c3b5cad67a8cb269114ca7f59a8396b8112472ce2acef5e347d808ee888baa4f61257d3e5ceaa5d61ba029689ec0505cbef6b6aa1d9aacf
@@ -18,6 +18,7 @@ export default class Asset extends React.PureComponent {
18
18
  static defaultProps = {
19
19
  label: '',
20
20
  className: '',
21
+ tabIndex: 0,
21
22
  }
22
23
 
23
24
  static propTypes = {
@@ -25,6 +26,7 @@ export default class Asset extends React.PureComponent {
25
26
  name: PropTypes.string.isRequired,
26
27
  label: PropTypes.string,
27
28
  className: PropTypes.string,
29
+ tabIndex: PropTypes.number,
28
30
  }
29
31
 
30
32
  @action.bound
@@ -43,6 +45,18 @@ export default class Asset extends React.PureComponent {
43
45
  return this.props.label || titleize(this.props.name);
44
46
  }
45
47
 
48
+ @action.bound
49
+ setDZRef(dz) {
50
+ this.dropZone = dz;
51
+ }
52
+
53
+ @action.bound
54
+ onKey(ev) {
55
+ if ('Enter' === ev.key) {
56
+ this.dropZone.open();
57
+ }
58
+ }
59
+
46
60
  preview() {
47
61
  if (this.asset && this.asset.exists) {
48
62
  return this.asset.isImage ?
@@ -56,20 +70,26 @@ export default class Asset extends React.PureComponent {
56
70
  );
57
71
  }
58
72
 
73
+
59
74
  render() {
60
- const { model: _, label: __, name: ___, className, ...col } = this.props;
75
+ const { model: _, label: __, name: ___, className, tabIndex, ...col } = this.props;
61
76
 
62
77
  return (
63
78
  <Col
64
79
  {...col}
65
80
  className={classnames(className, 'asset', 'form-field')}
66
81
  >
67
- <Field label={this.label} >
82
+ <Field
83
+ label={this.label}
84
+ tabIndex={tabIndex}
85
+ onKeyPress={this.onKey}
86
+ >
68
87
  <Dropzone
69
88
  className="drop-zone"
70
89
  activeClassName="drop-zone-active"
71
90
  onDrop={this.onDrop}
72
91
  multiple={false}
92
+ ref={this.setDZRef}
73
93
  >
74
94
  {this.preview()}
75
95
  </Dropzone>
@@ -6,7 +6,7 @@
6
6
  height: 200px;
7
7
  padding: 0.5rem;
8
8
  cursor: pointer;
9
- overflow: scroll;
9
+ overflow: auto;
10
10
  &-active {
11
11
  background-color: $selected-background-color;
12
12
  }
@@ -69,37 +69,48 @@ export default class DataList extends React.Component {
69
69
  render() {
70
70
  const { query } = this;
71
71
  const { rowHeight, rowRenderer, ...listProps } = this.props;
72
+
72
73
  return (
73
74
  <Box
74
75
  className={cn('data-list', { selectable: this.props.onRowClick })}
75
- align='stretch' direction='row' flex
76
+ align='stretch' flex
76
77
  >
77
- <InfiniteLoader
78
- keyChange={query.results.updateKey}
79
- minimumBatchSize={query.pageSize}
80
- isRowLoaded={this.isRowLoaded}
81
- loadMoreRows={this.loadMoreRows}
82
- rowCount={query.results.totalCount}
83
- >
84
- {({ onRowsRendered, registerChild }) =>
85
- <AutoSizer
86
- updateKey={query.results.updateKey}
87
- >
88
- {({ height, width }) =>
89
- <List
90
- {...listProps}
91
- height={height}
92
- width={width}
93
- ref={(list) => { registerChild(list); this.listRef = list; }}
94
- rowHeight={rowHeight}
95
- headerHeight={50}
96
- onRowsRendered={onRowsRendered}
97
- rowRenderer={rowRenderer || this.rowRenderer}
98
- rowCount={query.results.rows.length}
99
- keyChange={query.results.updateKey}
100
- />}
101
- </AutoSizer>}
102
- </InfiniteLoader>
78
+
79
+ {this.props.header}
80
+
81
+ <Box className="body" align="stretch" flex>
82
+ <InfiniteLoader
83
+ keyChange={query.results.updateKey}
84
+ minimumBatchSize={query.pageSize}
85
+ isRowLoaded={this.isRowLoaded}
86
+ loadMoreRows={this.loadMoreRows}
87
+ rowCount={query.results.totalCount}
88
+ keyChange={query.results.fingerprint}
89
+ >
90
+ {({ onRowsRendered, registerChild }) =>
91
+ <AutoSizer
92
+ keyChange={query.results.fingerprint}
93
+ updateKey={query.results.updateKey}
94
+ >
95
+ {({ height, width }) =>
96
+ <List
97
+ {...listProps}
98
+ height={height}
99
+ width={width}
100
+ ref={(list) => {
101
+ registerChild(list);
102
+ this.listRef = list;
103
+ }}
104
+ rowHeight={rowHeight}
105
+ headerHeight={50}
106
+ onRowsRendered={onRowsRendered}
107
+ rowRenderer={rowRenderer || this.rowRenderer}
108
+ rowCount={query.results.rows.length}
109
+ keyChange={query.results.fingerprint}
110
+ />}
111
+ </AutoSizer>}
112
+ </InfiniteLoader>
113
+ </Box>
103
114
  </Box>
104
115
  );
105
116
  }
@@ -5,6 +5,8 @@
5
5
  .ReactVirtualized__Grid__innerScrollContainer {
6
6
  cursor: pointer;
7
7
  }
8
+
8
9
  }
10
+
9
11
  }
10
12
  }
@@ -119,7 +119,7 @@ export default class DataTable extends React.Component {
119
119
  }
120
120
 
121
121
  @computed get gridRenderKey() {
122
- return `${this.query.results.updateKey}-${this.editIndex}`;
122
+ return `${this.query.results.fingerprint}-${this.editIndex}`;
123
123
  }
124
124
 
125
125
  @computed get columnDefinitions() {
@@ -129,7 +129,9 @@ export default class DataTable extends React.Component {
129
129
  columnData: f,
130
130
  dataKey: f.dataIndex || f.id,
131
131
  headerRenderer: this.headerRenderer,
132
- }, pick(f, 'width', 'label', 'flexGrow', 'flexShrink', 'cellRenderer')),
132
+ }, pick(f, 'width', 'label', 'flexGrow', 'flexShrink',
133
+ 'cellRenderer', 'className', 'headerClassName'),
134
+ ),
133
135
  );
134
136
  if (this.props.editor) {
135
137
  definitions.unshift({
@@ -0,0 +1,7 @@
1
+ import Big from 'big.js';
2
+ import { isNil, isNaN } from 'lodash';
3
+
4
+ export function currency({ cellData: value }) { // eslint-disable-line import/prefer-default-export
5
+ if (isNil(value) || isNaN(value)) { return ''; }
6
+ return Big(value).round(2).toFixed(2);
7
+ }
@@ -37,5 +37,15 @@
37
37
  }
38
38
  }
39
39
 
40
+ .ReactVirtualized__Table__rowColumn {
41
+ &.l { text-align: left; }
42
+ &.r { text-align: right; }
43
+ &.c { text-align: center; }
44
+ }
45
+ .ReactVirtualized__Table__headerColumn {
46
+ &.l { text-align: left; }
47
+ &.r { text-align: right; }
48
+ &.c { text-align: center; }
49
+ }
40
50
  }
41
51
  }
@@ -1,172 +1,132 @@
1
1
  import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { action, observable, computed } from 'mobx';
4
- import { observer } from 'mobx-react';
5
- import CalendarIcon from 'grommet/components/icons/base/Calendar';
6
- import ClockIcon from 'grommet/components/icons/base/Clock';
7
- import Button from 'grommet/components/Button';
8
- import CSSClassnames from 'grommet/utils/CSSClassnames';
9
- import { findAncestor, isDescendant } from 'grommet/utils/DOM';
10
- import KeyboardAccelerators from 'grommet/utils/KeyboardAccelerators';
11
- import Drop from 'grommet/utils/Drop';
12
- import moment from 'moment';
13
-
14
- import DateTimeDrop from './date-time/date-time-drop';
15
- import './date-time/date-time.scss';
16
-
17
- const INPUT = CSSClassnames.INPUT;
18
- const FORM_FIELD = CSSClassnames.FORM_FIELD;
19
- const CLASS_ROOT = CSSClassnames.DATE_TIME;
2
+ import PropTypes from 'prop-types';
3
+ import { observer } from 'mobx-react';
4
+ import { autobind } from 'core-decorators';
5
+ import Flatpickr from 'flatpickr';
6
+ import { defaults, has, omit } from 'lodash';
7
+ import { observable, action } from 'mobx';
8
+ import './date-time.scss';
9
+
10
+ const hooks = [
11
+ 'onOpen',
12
+ 'onMonthChange',
13
+ 'onYearChange',
14
+ 'onReady',
15
+ 'onValueUpdate',
16
+ 'onDayCreate',
17
+ 'onBlur',
18
+ 'onChange',
19
+ ];
20
+
21
+ const defaultOptions = {
22
+ enableTime: true,
23
+ format: 'M d Y h:iK',
24
+ };
20
25
 
21
26
  @observer
22
- export default class DateTime extends React.Component {
27
+ export default class DateTimePicker extends React.PureComponent {
23
28
  static propTypes = {
24
- value: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
25
- onChange: PropTypes.func.isRequired,
26
- format: PropTypes.string,
29
+ defaultValue: PropTypes.string,
30
+ options: PropTypes.object,
31
+ onChange: PropTypes.func,
32
+ onOpen: PropTypes.func,
33
+ onClose: PropTypes.func,
34
+ onMonthChange: PropTypes.func,
35
+ onYearChange: PropTypes.func,
36
+ onReady: PropTypes.func,
37
+ onValueUpdate: PropTypes.func,
38
+ onDayCreate: PropTypes.func,
39
+ value: PropTypes.oneOfType([
40
+ PropTypes.string,
41
+ PropTypes.array,
42
+ PropTypes.object,
43
+ PropTypes.number,
44
+ ]),
45
+ children: PropTypes.node,
27
46
  }
28
47
 
29
48
  static defaultProps = {
30
- format: 'M/D/YYYY',
31
- onDropChange: () => {},
49
+ options: {},
32
50
  }
33
51
 
34
- static contextTypes = {
35
- onDropChange: PropTypes.func,
36
- }
37
-
38
- @observable drop;
39
- @observable dateValue;
52
+ @observable node;
40
53
 
41
- @observable editingValue
54
+ componentWillReceiveProps(props) {
55
+ const options = this.getOptions(props);
42
56
 
43
- @action.bound
44
- onClose(ev) {
45
- const dropElement = document.querySelector(`.${DateTimeDrop.className}`);
46
- if (!isDescendant(this.containerRef, ev.target) &&
47
- (!dropElement || !isDescendant(dropElement, ev.target))) {
48
- this.setDropActivation(false);
57
+ if (has(props, 'value')) {
58
+ this.flatpickr.setDate(props.value, false);
49
59
  }
50
- }
51
60
 
52
- @action.bound
53
- onForceClose() {
54
- this.setDropActivation(false);
55
- }
61
+ const optionsKeys = Object.getOwnPropertyNames(options);
56
62
 
57
- @action.bound
58
- setDropActivation(dropActive) {
59
- const { onDropChange } = this.context;
60
-
61
- const listeners = {
62
- esc: this.onForceClose,
63
- };
64
-
65
- if (dropActive && !this.dropActive) {
66
- document.addEventListener('click', this.onClose);
67
- KeyboardAccelerators.startListeningToKeyboard(this, listeners);
68
- // If this is inside a FormField, place the drop in reference to it.
69
- const control = findAncestor(this.containerRef, `.${FORM_FIELD}`) || this.containerRef;
70
- this.drop = new Drop(control, this.renderDrop(), {
71
- align: { top: 'bottom', left: 'left' },
72
- focusControl: true,
73
- context: this.context,
74
- });
75
- this.dropActive = true;
76
- this.props.onDropChange();
77
- } else if (!dropActive && this.dropActive) {
78
- document.removeEventListener('click', this.onClose);
79
- KeyboardAccelerators.stopListeningToKeyboard(this, listeners);
80
-
81
- if (this.drop) {
82
- this.drop.remove();
83
- this.drop = undefined;
84
- }
85
- this.dropActive = false;
86
- this.props.onDropChange();
87
- }
63
+ for (let index = optionsKeys.length - 1; index >= 0; index--) {
64
+ const key = optionsKeys[index];
65
+ let value = options[key];
88
66
 
89
- if (onDropChange) {
90
- onDropChange(dropActive);
67
+ // Hook handlers must be set as an array
68
+ if (hooks.indexOf(key) !== -1 && !Array.isArray(value)) {
69
+ value = [value];
70
+ }
71
+ this.flatpickr.set(key, value);
91
72
  }
92
73
  }
93
74
 
94
- @computed get value() {
95
- return this.dateValue || this.props.value ? moment(this.props.value) : '';
96
- }
97
-
98
- @action.bound
99
- onInputChange(ev) {
100
- this.editingValue = ev.target.value;
101
- }
75
+ componentDidMount() {
76
+ const options = this.getOptions(this.props);
77
+ this.flatpickr = new Flatpickr(this.node, options);
102
78
 
103
- @action.bound
104
- onInputBlur() {
105
- if (!this.editingValue) { return; }
106
- const value = moment(this.editingValue, this.props.format);
107
- this.editingValue = '';
108
- this.onForceClose();
109
- if (value.isValid()) {
110
- this.props.onChange(value);
111
- this.dateValue = value;
79
+ if (has(this.props, 'value')) {
80
+ this.flatpickr.setDate(this.props.value, false);
112
81
  }
113
82
  }
114
83
 
115
84
  componentWillUnmount() {
116
- this.setDropActivation(false);
85
+ this.flatpickr.destroy();
117
86
  }
118
87
 
119
- @action.bound
120
- onControlClick() {
121
- this.setDropActivation(true);
88
+
89
+ @autobind onClose() {
90
+ if (this.node.blur) { this.node.blur(); }
122
91
  }
123
92
 
124
- @action.bound
125
- onDateChange(date) {
126
- const value = moment(date);
127
- this.props.onChange(value.toDate());
128
- this.dateValue = value;
93
+ @autobind onChange(dates) {
94
+ this.props.onChange({ target: { value: dates[0] } });
129
95
  }
130
96
 
131
- renderDrop() {
132
- return (
133
- <DateTimeDrop
134
- {...this.props}
135
- onChange={this.onDateChange}
136
- value={this.value}
137
- />
138
- );
97
+ getOptions(props) {
98
+ const options = defaults({
99
+ ...this.props.options,
100
+ onClose: this.onClose,
101
+ onChange: this.onChange,
102
+ dateFormat: props.format,
103
+ }, defaultOptions);
104
+ // Add prop hooks to options
105
+ hooks.forEach((hook) => {
106
+ if (this[hook]) {
107
+ options[hook] = this[hook];
108
+ } else if (this.props[hook]) {
109
+ options[hook] = this.props[hook];
110
+ }
111
+ });
112
+ return options;
139
113
  }
140
114
 
141
- @computed get inputValue() {
142
- if (this.editingValue) { return this.editingValue; }
143
- if (this.value) { return this.value.format(this.props.format); }
144
- return '';
115
+ @action.bound
116
+ setNode(node) {
117
+ this.node = node;
145
118
  }
146
119
 
147
120
  render() {
148
- const { inputValue, props: { format, dateOnly } } = this;
149
- const Icon = dateOnly ? CalendarIcon : ClockIcon;
121
+ // eslint-disable-next-line no-unused-vars
122
+ const { options, defaultValue, value, children, ...props } = omit(this.props, hooks);
150
123
 
151
124
  return (
152
- <div
153
- ref={r => (this.containerRef = r)}
154
- className={CLASS_ROOT}
155
- >
156
- <input
157
- placeholder={format}
158
- className={`${INPUT} ${CLASS_ROOT}__input`}
159
- onClick={this.onControlClick}
160
- onChange={this.onInputChange}
161
- onBlur={this.onInputBlur}
162
- value={inputValue || ''}
163
- />
164
- <Button
165
- icon={<Icon />}
166
- onClick={this.onControlClick}
167
- className={`${CLASS_ROOT}__control`}
168
- />
169
- </div>
125
+ <input
126
+ {...props}
127
+ defaultValue={defaultValue}
128
+ ref={this.setNode}
129
+ />
170
130
  );
171
131
  }
172
132
  }