hippo-fw 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
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
  }