hippo-fw 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.js +0 -3
  3. data/.nvmrc +1 -2
  4. data/.ruby-version +1 -1
  5. data/Rakefile +1 -1
  6. data/client/hippo/access/login-dialog.jsx +2 -0
  7. data/client/hippo/boot.jsx +12 -0
  8. data/client/hippo/components/asset.jsx +5 -1
  9. data/client/hippo/components/asset.scss +1 -0
  10. data/client/hippo/components/data-list.jsx +6 -4
  11. data/client/hippo/components/data-table.jsx +7 -6
  12. data/client/hippo/components/data-table/header-cell.jsx +2 -0
  13. data/client/hippo/components/date-time.jsx +12 -2
  14. data/client/hippo/components/form/api.js +12 -0
  15. data/client/hippo/components/form/fields.jsx +8 -1
  16. data/client/hippo/components/form/fields/checkbox-wrapper.jsx +2 -0
  17. data/client/hippo/components/form/fields/date-wrapper.jsx +2 -0
  18. data/client/hippo/components/form/fields/form-field.scss +2 -0
  19. data/client/hippo/components/form/fields/label.jsx +2 -0
  20. data/client/hippo/components/form/fields/react-tags.scss +170 -0
  21. data/client/hippo/components/form/fields/select-wrapper.jsx +2 -0
  22. data/client/hippo/components/form/fields/tags-wrapper.jsx +46 -0
  23. data/client/hippo/components/form/fields/text-wrapper.jsx +9 -1
  24. data/client/hippo/components/form/fields/textarea-wrapper.jsx +15 -0
  25. data/client/hippo/components/form/wrapper.jsx +21 -1
  26. data/client/hippo/components/help.jsx +21 -0
  27. data/client/hippo/components/master-detail.jsx +2 -0
  28. data/client/hippo/components/network-activity-overlay.jsx +3 -1
  29. data/client/hippo/components/popout-window.jsx +126 -0
  30. data/client/hippo/components/query-builder.jsx +9 -117
  31. data/client/hippo/components/query-builder/boolean-picker.jsx +28 -0
  32. data/client/hippo/components/query-builder/clause-filter.jsx +58 -0
  33. data/client/hippo/components/query-builder/clause.jsx +98 -0
  34. data/client/hippo/components/query-builder/date-picker.jsx +23 -0
  35. data/client/hippo/components/query-builder/query-builder.scss +7 -0
  36. data/client/hippo/components/record-finder.jsx +2 -0
  37. data/client/hippo/components/record-finder/query-layer.jsx +5 -1
  38. data/client/hippo/components/save-button.jsx +7 -1
  39. data/client/hippo/components/screen.jsx +2 -0
  40. data/client/hippo/components/text-editor.jsx +4 -5
  41. data/client/hippo/components/text-editor/display-modes/Button.jsx +5 -2
  42. data/client/hippo/components/text-editor/display-modes/ToggleEdit.jsx +2 -1
  43. data/client/hippo/components/text-editor/display-modes/ToggleInsert.jsx +2 -1
  44. data/client/hippo/components/text-editor/display-modes/ToggleLayout.jsx +2 -1
  45. data/client/hippo/components/text-editor/display-modes/TogglePreview.jsx +2 -1
  46. data/client/hippo/components/text-editor/display-modes/ToggleResize.jsx +2 -1
  47. data/client/hippo/components/text-editor/display-modes/index.js +7 -7
  48. data/client/hippo/components/text-editor/image-plugin/Component/Display/index.js +4 -2
  49. data/client/hippo/components/text-editor/image-plugin/Component/Form/index.js +2 -0
  50. data/client/hippo/components/text-editor/renderer.jsx +2 -0
  51. data/client/hippo/components/text-editor/text-editor.scss +13 -4
  52. data/client/hippo/components/time-zone-select.jsx +2 -0
  53. data/client/hippo/components/tool-tip.jsx +2 -0
  54. data/client/hippo/extensions/base.js +2 -0
  55. data/client/hippo/extensions/hippo.js +2 -0
  56. data/client/hippo/lib/__mocks__/request-assets.js +1 -2
  57. data/client/hippo/lib/bootstrap.js +2 -0
  58. data/client/hippo/lib/computed-properties.js +24 -0
  59. data/client/hippo/lib/date-range.js +13 -2
  60. data/client/hippo/lib/request-assets.js +3 -2
  61. data/client/hippo/lib/smooth-scroll.js +2 -0
  62. data/client/hippo/lib/util.js +1 -2
  63. data/client/hippo/models/asset.js +2 -0
  64. data/client/hippo/models/base.js +5 -2
  65. data/client/hippo/models/collection.js +2 -0
  66. data/client/hippo/models/config.js +3 -1
  67. data/client/hippo/models/pub_sub.js +2 -0
  68. data/client/hippo/models/pub_sub/channel.js +2 -0
  69. data/client/hippo/models/pub_sub/map.js +2 -0
  70. data/client/hippo/models/query.js +21 -12
  71. data/client/hippo/models/query/array-result.js +52 -16
  72. data/client/hippo/models/query/clause.js +11 -4
  73. data/client/hippo/models/query/field.js +2 -1
  74. data/client/hippo/models/query/info.js +7 -1
  75. data/client/hippo/models/query/operator.js +2 -0
  76. data/client/hippo/models/query/result.js +2 -0
  77. data/client/hippo/models/query/types.js +4 -0
  78. data/client/hippo/models/sync.js +2 -0
  79. data/client/hippo/models/system-setting.js +1 -15
  80. data/client/hippo/models/tenant.js +23 -7
  81. data/client/hippo/react/Root.jsx +2 -0
  82. data/client/hippo/react/component-not-found.jsx +2 -0
  83. data/client/hippo/screens/definition.js +2 -0
  84. data/client/hippo/screens/group.js +2 -0
  85. data/client/hippo/screens/instance.js +5 -1
  86. data/client/hippo/screens/system-settings.jsx +19 -5
  87. data/client/hippo/screens/system-settings/mailer-config.jsx +4 -2
  88. data/client/hippo/screens/system-settings/system-settings.scss +1 -1
  89. data/client/hippo/screens/system-settings/tenant.jsx +4 -2
  90. data/client/hippo/screens/user-management.jsx +2 -0
  91. data/client/hippo/screens/user-management/edit-form.jsx +2 -0
  92. data/client/hippo/testing/screens.js +10 -2
  93. data/client/hippo/user.js +4 -0
  94. data/client/hippo/workspace/index.jsx +3 -1
  95. data/client/hippo/workspace/menu-group.jsx +2 -0
  96. data/client/hippo/workspace/menu-option.jsx +2 -0
  97. data/client/hippo/workspace/menu.jsx +6 -3
  98. data/client/hippo/workspace/navbar.jsx +2 -0
  99. data/client/hippo/workspace/screen.jsx +2 -0
  100. data/client/hippo/workspace/styles.scss +5 -1
  101. data/command-reference-files/initial/.eslintrc.js +0 -3
  102. data/command-reference-files/initial/Gemfile +1 -1
  103. data/config/routes.rb +3 -1
  104. data/db/seed.rb +1 -1
  105. data/hippo-fw.gemspec +2 -2
  106. data/lib/hippo/api/controller_base.rb +2 -1
  107. data/lib/hippo/api/handlers/asset.rb +3 -2
  108. data/lib/hippo/api/handlers/tenant.rb +4 -6
  109. data/lib/hippo/api/helper_methods.rb +5 -7
  110. data/lib/hippo/api/route_set.rb +3 -2
  111. data/lib/hippo/asset.rb +4 -0
  112. data/lib/hippo/command/console.rb +1 -1
  113. data/lib/hippo/concerns/asset_uploader.rb +9 -4
  114. data/lib/hippo/concerns/queries.rb +3 -3
  115. data/lib/hippo/extension.rb +14 -4
  116. data/lib/hippo/extension/definition.rb +5 -1
  117. data/lib/hippo/logger.rb +26 -27
  118. data/lib/hippo/mailer.rb +19 -7
  119. data/lib/hippo/system_settings.rb +10 -4
  120. data/lib/hippo/templates/liquid.rb +1 -0
  121. data/lib/hippo/templates/liquid/pluralize.rb +16 -0
  122. data/lib/hippo/tenant.rb +17 -1
  123. data/lib/hippo/user.rb +10 -9
  124. data/lib/hippo/version.rb +1 -1
  125. data/lib/hippo/webpack.rb +22 -2
  126. data/package-lock.json +5462 -883
  127. data/package.json +9 -11
  128. data/spec/client/access/login-dialog.spec.jsx +2 -2
  129. data/spec/client/components/__snapshots__/query-builder.spec.jsx.snap +1 -1
  130. data/spec/client/components/__snapshots__/record-finder.spec.jsx.snap +1 -1
  131. data/spec/client/components/master-detail.spec.jsx +2 -1
  132. data/spec/client/components/query-builder.spec.jsx +3 -3
  133. data/spec/client/extension/base.spec.js +2 -0
  134. data/spec/client/models/base.spec.js +5 -3
  135. data/spec/client/models/query.spec.js +18 -6
  136. data/spec/client/models/sync.spec.js +2 -1
  137. data/spec/client/models/system-setting.spec.js +0 -12
  138. data/spec/client/screens/user-management.spec.jsx +1 -2
  139. data/spec/client/test-models.js +10 -0
  140. data/spec/server/api/tenant_change_spec.rb +1 -2
  141. data/spec/server/asset_spec.rb +2 -2
  142. data/templates/js/config-data.js +1 -1
  143. data/templates/spec/factories/model.rb +1 -1
  144. data/views/hippo_root_view.erb +1 -3
  145. metadata +17 -6
  146. data/client/hippo/components/text-editor/display-modes/SaveState.jsx +0 -17
@@ -10,6 +10,7 @@ import Display from '../Display';
10
10
  @inject('model', 'images_attribute')
11
11
  @observer
12
12
  export default class Form extends React.PureComponent {
13
+
13
14
  get assets() {
14
15
  return this.props.model[this.props.images_attribute];
15
16
  }
@@ -37,4 +38,5 @@ export default class Form extends React.PureComponent {
37
38
  </div>
38
39
  );
39
40
  }
41
+
40
42
  }
@@ -9,6 +9,7 @@ import 'ory-editor-core/lib/index.css';
9
9
 
10
10
  @observer
11
11
  export default class TextEditorRenderer extends React.PureComponent {
12
+
12
13
  static defaultProps = {
13
14
  assets: observable.array(),
14
15
  }
@@ -34,4 +35,5 @@ export default class TextEditorRenderer extends React.PureComponent {
34
35
  </Provider>
35
36
  );
36
37
  }
38
+
37
39
  }
@@ -8,6 +8,7 @@
8
8
 
9
9
  .text-editor-content {
10
10
  flex: 1;
11
+ overflow: auto;
11
12
  }
12
13
 
13
14
 
@@ -25,12 +26,17 @@
25
26
  margin-bottom: 0;
26
27
  }
27
28
 
28
- .content-image {
29
- width: 100%;
30
- height: auto;
29
+ .content-image-wrapper {
30
+ .image-drop-zone {
31
+ text-align: center;
32
+ }
33
+ .content-image {
34
+ max-width: 100%;
35
+ width: auto;
36
+ height: auto;
37
+ }
31
38
  }
32
39
 
33
-
34
40
  button:not(.grommetux-button) {
35
41
  min-width: inherit;
36
42
  padding: 0;
@@ -44,5 +50,8 @@
44
50
  .active {
45
51
  border-bottom: 2px solid $focus-border-color;
46
52
  }
53
+ button + button {
54
+ margin-left: 0.5rem;
55
+ }
47
56
  }
48
57
  }
@@ -9,6 +9,7 @@ import moment from 'moment-timezone';
9
9
 
10
10
  @observer
11
11
  export default class TimeZoneSelect extends React.PureComponent {
12
+
12
13
  static propTypes = {
13
14
  value: PropTypes.string,
14
15
  onChange: PropTypes.func.isRequired,
@@ -57,4 +58,5 @@ export default class TimeZoneSelect extends React.PureComponent {
57
58
  />
58
59
  );
59
60
  }
61
+
60
62
  }
@@ -6,6 +6,7 @@ import 'react-tippy/dist/tippy.css';
6
6
  // Use a wrapper component even though it doesn't really add any functionality
7
7
  // In the future we'll add a Manager wrapper so that multiple tooltips cannot be shown at once
8
8
  export default class ToolTip extends React.PureComponent {
9
+
9
10
  render() {
10
11
  const { children, ...tipProps } = this.props;
11
12
  return (
@@ -16,4 +17,5 @@ export default class ToolTip extends React.PureComponent {
16
17
  </Tippy>
17
18
  );
18
19
  }
20
+
19
21
  }
@@ -6,6 +6,7 @@ import RootView from '../workspace/root-view';
6
6
  export { identifiedBy, identifier } from '../models/base';
7
7
 
8
8
  export class BaseExtension {
9
+
9
10
  @observable data;
10
11
 
11
12
  static register() {
@@ -27,4 +28,5 @@ export class BaseExtension {
27
28
  rootView() {
28
29
  return RootView;
29
30
  }
31
+
30
32
  }
@@ -7,7 +7,9 @@ import Extensions from './index';
7
7
 
8
8
  @identifiedBy('extensions/hippo')
9
9
  export default class HippoExtension extends BaseExtension {
10
+
10
11
  @identifier id = 'hippo';
12
+
11
13
  }
12
14
 
13
15
 
@@ -1,6 +1,5 @@
1
1
  /* global jest */
2
2
 
3
3
  const MockedRequestAssets = jest.fn((...urls) =>
4
- new Promise(resolve => resolve(urls)),
5
- );
4
+ new Promise(resolve => resolve(urls)));
6
5
  export default MockedRequestAssets;
@@ -7,6 +7,7 @@ const ERROR = Symbol('ERROR');
7
7
  const COMPLETE = Symbol('COMPLETE');
8
8
 
9
9
  export default class Bootstrap {
10
+
10
11
  constructor(options = {}) {
11
12
  this.options = options;
12
13
  this.callbacks = { onReady: [] };
@@ -41,4 +42,5 @@ export default class Bootstrap {
41
42
  this.status = COMPLETE;
42
43
  this.callbacks.onReady.map(cb => cb(host, data));
43
44
  }
45
+
44
46
  }
@@ -0,0 +1,24 @@
1
+ import { forIn } from 'lodash';
2
+ import { computed } from 'mobx';
3
+
4
+ export function addComputedProperty(obj, name, fn) {
5
+ Object.defineProperty(obj, name, {
6
+ configurable: true,
7
+ get() {
8
+ const getter = computed(fn, { context: this });
9
+ Object.defineProperty(this, name, {
10
+ configurable: false,
11
+ get: () => getter.get(),
12
+ });
13
+ return getter.get();
14
+ },
15
+ });
16
+ }
17
+
18
+ export function addMethods(properties) {
19
+ return (collection) => {
20
+ forIn(properties, (fn, prop) => {
21
+ addComputedProperty(collection, prop, fn);
22
+ });
23
+ };
24
+ }
@@ -1,10 +1,16 @@
1
- import { isString, isEmpty, map } from 'lodash';
1
+ import moment from 'moment';
2
+ import { observable, computed } from 'mobx';
3
+ import { isString, isEmpty, map, isNaN } from 'lodash';
2
4
  import {
3
5
  identifiedBy,
4
6
  } from 'mobx-decorated-models';
5
7
 
6
8
  @identifiedBy('date-range')
7
9
  export default class DateRange {
10
+
11
+ @observable start;
12
+ @observable end;
13
+
8
14
  constructor(range) {
9
15
  if (isString(range) && !isEmpty(range)) {
10
16
  [this.start, this.end] = map(range.split('...'), d => new Date(d));
@@ -15,7 +21,7 @@ export default class DateRange {
15
21
  }
16
22
 
17
23
  toJSON() {
18
- if (this.start && this.end) {
24
+ if (this.start && this.end && !isNaN(this.start) && !isNaN(this.end)) {
19
25
  // this strange format is what PG user and is therefore what active record expects
20
26
  return `[${this.start.toISOString()},${this.end.toISOString()})`;
21
27
  }
@@ -25,4 +31,9 @@ export default class DateRange {
25
31
  static serialize(range) {
26
32
  return (range ? range.toJSON() : '');
27
33
  }
34
+
35
+ @computed get isCurrent() {
36
+ return moment(this.end).isAfter() && moment(this.start).isBefore();
37
+ }
38
+
28
39
  }
@@ -3,6 +3,7 @@ import { loadCSS, loadJS } from './loader';
3
3
  import { logger } from './util';
4
4
 
5
5
  class AssetLoader {
6
+
6
7
  constructor(urls, cb) {
7
8
  let finished = 0;
8
9
  const completed = {};
@@ -21,6 +22,7 @@ class AssetLoader {
21
22
  }
22
23
  });
23
24
  }
25
+
24
26
  }
25
27
 
26
28
  export default function RequestAssets(...urlArgs) {
@@ -33,6 +35,5 @@ export default function RequestAssets(...urlArgs) {
33
35
  }
34
36
  logger.warn(`${keys(failures).join(',')} failed to load`);
35
37
  return reject(failures);
36
- }),
37
- );
38
+ }));
38
39
  }
@@ -17,6 +17,7 @@ const POSITION = function(start, end, elapsed, duration) {
17
17
  };
18
18
 
19
19
  export default class SmoothScroll {
20
+
20
21
  constructor(link, destination, options = {}) {
21
22
  this.link = link;
22
23
  this.destination = destination;
@@ -65,4 +66,5 @@ export default class SmoothScroll {
65
66
  }
66
67
  return step();
67
68
  }
69
+
68
70
  }
@@ -34,8 +34,7 @@ export function titleize(words) {
34
34
  if ('string' !== typeof words) { return words; }
35
35
  return (words)
36
36
  .replace(/[\W_]/g, ' ')
37
- .replace(/\S+/g, word => (word.charAt(0).toUpperCase() + word.slice(1)),
38
- );
37
+ .replace(/\S+/g, word => (word.charAt(0).toUpperCase() + word.slice(1)));
39
38
  }
40
39
 
41
40
  export function underscored(str) {
@@ -15,6 +15,7 @@ const UPDATE_METHODS = { POST: true, PUT: true, PATCH: true };
15
15
 
16
16
  @identifiedBy('hippo/asset')
17
17
  export default class Asset extends BaseModel {
18
+
18
19
  @identifier id;
19
20
  @field order;
20
21
 
@@ -127,4 +128,5 @@ export default class Asset extends BaseModel {
127
128
  return json;
128
129
  });
129
130
  }
131
+
130
132
  }
@@ -29,6 +29,7 @@ export {
29
29
  };
30
30
 
31
31
  export class BaseModel {
32
+
32
33
  static get propType() {
33
34
  return PropTypes.instanceOf(this);
34
35
  }
@@ -43,7 +44,7 @@ export class BaseModel {
43
44
 
44
45
  static get propertyOptions() {
45
46
  const options = {};
46
- this.$schema.forEach((val, name) => (options[name] = val.options));
47
+ this.$schema.forEach((val, name) => { options[name] = val.options; });
47
48
  return options;
48
49
  }
49
50
 
@@ -59,7 +60,8 @@ export class BaseModel {
59
60
  }
60
61
 
61
62
  static get Collection() {
62
- return this.$collection || (this.$collection = new ModelCollection(this));
63
+ if (!this.$collection) { this.$collection = new ModelCollection(this); }
64
+ return this.$collection;
63
65
  }
64
66
 
65
67
  constructor(attrs) {
@@ -136,6 +138,7 @@ export class BaseModel {
136
138
  destroy(options = {}) {
137
139
  return Sync.forModel(this, extend(options, { action: 'destroy' }));
138
140
  }
141
+
139
142
  }
140
143
 
141
144
  export default BaseModel;
@@ -44,6 +44,7 @@ function extendAry(modelClass, models = [], options = {}) {
44
44
  }
45
45
 
46
46
  export default class ModelCollection {
47
+
47
48
  constructor(model) {
48
49
  this.$model = model;
49
50
  this.$collection = extendAry(model, [], {}, this);
@@ -52,4 +53,5 @@ export default class ModelCollection {
52
53
  create(models = [], options = {}) {
53
54
  return extendAry(this.$model, models, options, this);
54
55
  }
56
+
55
57
  }
@@ -7,6 +7,7 @@ import Extensions from '../extensions';
7
7
  const STORAGE_KEY = 'hippo-user-data';
8
8
 
9
9
  export default class Config {
10
+
10
11
  @persist @observable api_host = get(window, 'location.origin', '');
11
12
  @persist @observable api_path = '/api';
12
13
  @persist @observable access_token;
@@ -23,7 +24,7 @@ export default class Config {
23
24
  static create(hydrationConfig) {
24
25
  const hydrate = createHydrator(hydrationConfig);
25
26
  const ConfigInstance = new Config();
26
- hydrate('config', ConfigInstance).then(() => (ConfigInstance.isIntialized = true));
27
+ hydrate('config', ConfigInstance).then(() => { ConfigInstance.isIntialized = true; });
27
28
  return ConfigInstance;
28
29
  }
29
30
 
@@ -56,4 +57,5 @@ export default class Config {
56
57
  this.access_token = null;
57
58
  if (this.user) { this.user.reset(); }
58
59
  }
60
+
59
61
  }
@@ -77,6 +77,7 @@ export function stop() {
77
77
  }
78
78
 
79
79
  export class PubSubAtom {
80
+
80
81
  constructor(model) {
81
82
  this.model = model;
82
83
  if (model.identifierFieldValue) {
@@ -100,6 +101,7 @@ export class PubSubAtom {
100
101
  reportObserved() {
101
102
  if (this.atom) { this.atom.reportObserved(); }
102
103
  }
104
+
103
105
  }
104
106
 
105
107
  export function observePubSub(...models) {
@@ -5,6 +5,7 @@ import { logger } from '../../lib/util';
5
5
  const CHANNEL_SPLITTER = new RegExp('^(.*):(.*)/([^/]+)$');
6
6
 
7
7
  export default class PubSubCableChannel {
8
+
8
9
  constructor(pub_sub) {
9
10
  this.callbacks = observable.map();
10
11
  this.channel = pub_sub.cable.subscriptions.create(
@@ -53,4 +54,5 @@ export default class PubSubCableChannel {
53
54
  this.pub_sub.onModelChange(modelId, id, omit(data, 'channel'));
54
55
  }
55
56
  }
57
+
56
58
  }
@@ -2,6 +2,7 @@ import invariant from 'invariant';
2
2
  import { isEmpty, mapValues } from 'lodash';
3
3
 
4
4
  export default class PubSubMap {
5
+
5
6
  constructor(modelKlass, channel) {
6
7
  this.channel = channel;
7
8
  this.channel_prefix = modelKlass.identifiedBy;
@@ -54,4 +55,5 @@ export default class PubSubMap {
54
55
  models.splice(indx, 1);
55
56
  }
56
57
  }
58
+
57
59
  }
@@ -1,9 +1,9 @@
1
1
  import { get, isString, find, extend, toPairs } from 'lodash';
2
- import { action, reaction, observe, observable } from 'mobx';
2
+ import { toJS, action, reaction, observe, observable } from 'mobx';
3
+ import objectHash from 'object-hash';
3
4
  import {
4
5
  BaseModel, identifiedBy, field, belongsTo, hasMany, identifier, computed, session,
5
6
  } from './base';
6
-
7
7
  import Info from './query/info';
8
8
  import Types from './query/types';
9
9
  import Operator from './query/operator';
@@ -14,6 +14,7 @@ import ArrayResult from './query/array-result';
14
14
  // needs to inherit from Base so network events will be listened to
15
15
  @identifiedBy('hippo/query')
16
16
  export default class Query extends BaseModel {
17
+
17
18
  @belongsTo src;
18
19
 
19
20
  @identifier id;
@@ -37,12 +38,6 @@ export default class Query extends BaseModel {
37
38
  @hasMany({ model: Operator, inverseOf: 'query' }) operators;
38
39
  @hasMany({ model: Field, inverseOf: 'query' }) fields;
39
40
 
40
- @action
41
- setSort({ field: f, ascending }) {
42
- this.sortField = f;
43
- this.sortAscending = ascending;
44
- }
45
-
46
41
  constructor(attrs = {}) {
47
42
  for (let i = 0; i < get(attrs, 'fields.length', 0); i += 1) {
48
43
  if (isString(attrs.fields[i])) {
@@ -52,10 +47,12 @@ export default class Query extends BaseModel {
52
47
  super(attrs);
53
48
  [
54
49
  { id: 'like', label: 'Starts With', types: Types.LIKE_QUERY_TYPES },
55
- { id: 'eq', label: 'Equals' },
50
+ { id: 'eq', label: 'Equals', types: Types.EQUAL_TYPES },
51
+ { id: 'between', label: 'Between', types: Types.BETWEEN_TYPES },
56
52
  { id: 'contains', label: 'Contains', types: Types.LIKE_QUERY_TYPES },
57
53
  { id: 'lt', label: 'Less Than', types: Types.LESS_THAN_QUERY_TYPES },
58
54
  { id: 'gt', label: 'More Than', types: Types.LESS_THAN_QUERY_TYPES },
55
+
59
56
  ].forEach(op => this.operators.push(op));
60
57
  this.info = new Info(this);
61
58
  this.results = new ArrayResult({ query: this });
@@ -68,13 +65,22 @@ export default class Query extends BaseModel {
68
65
  if (attrs.sort) {
69
66
  const [fieldId, dir] = toPairs(attrs.sort)[0];
70
67
  this.sortField = find(this.fields, { id: fieldId });
71
- this.sortAscending = ('ASC' === dir);
68
+ this.sortAscending = ('ASC' === dir.toUpperCase());
72
69
  }
73
70
  observe(this, 'autoFetch', this._onAutoFetchChange);
74
71
  }
75
72
 
73
+ @action
74
+ setSort({ field: f, ascending }) {
75
+ this.sortField = f;
76
+ this.sortAscending = ascending;
77
+ }
78
+
76
79
  @computed get fingerprint() {
77
- return this.clauses.map(o => o.fingerprint).join(';');
80
+ return objectHash({
81
+ so: toJS(this.syncOptions),
82
+ vc: this.info.valuedClauses.map(c => c.fingerprint),
83
+ });
78
84
  }
79
85
 
80
86
  open() {
@@ -123,8 +129,11 @@ export default class Query extends BaseModel {
123
129
  if (this.autoFetchDisposer) { return; }
124
130
  this.autoFetchDisposer = reaction(
125
131
  () => this.fingerprint,
126
- this.fetch,
132
+ () => {
133
+ this.results.reset().fetch();
134
+ },
127
135
  { delay: 100, compareStructural: true, fireImmediately: true },
128
136
  );
129
137
  }
138
+
130
139
  }