hippo-fw 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +2 -3
  4. data/Gemfile +1 -1
  5. data/client/hippo/__mocks__/config.js +4 -4
  6. data/client/hippo/boot.jsx +1 -0
  7. data/client/hippo/components/asset.jsx +4 -4
  8. data/client/hippo/components/date-time.jsx +169 -0
  9. data/client/hippo/components/date-time.scss +38 -0
  10. data/client/hippo/components/date-time/calendar.jsx +115 -0
  11. data/client/hippo/components/date-time/date-time-drop.jsx +77 -0
  12. data/client/hippo/components/date-time/date-time.scss +157 -0
  13. data/client/hippo/components/date-time/time.jsx +120 -0
  14. data/client/hippo/components/form/fields/date-wrapper.jsx +4 -3
  15. data/client/hippo/components/form/model.js +0 -2
  16. data/client/hippo/components/text-editor.jsx +85 -0
  17. data/client/hippo/components/text-editor/display-modes/Button.jsx +17 -0
  18. data/client/hippo/components/text-editor/display-modes/SaveState.jsx +17 -0
  19. data/client/hippo/components/text-editor/display-modes/ToggleEdit.jsx +22 -0
  20. data/client/hippo/components/text-editor/display-modes/ToggleInsert.jsx +21 -0
  21. data/client/hippo/components/text-editor/display-modes/ToggleLayout.jsx +21 -0
  22. data/client/hippo/components/text-editor/display-modes/TogglePreview.jsx +21 -0
  23. data/client/hippo/components/text-editor/display-modes/ToggleResize.jsx +21 -0
  24. data/client/hippo/components/text-editor/display-modes/index.css +0 -0
  25. data/client/hippo/components/text-editor/display-modes/index.js +30 -0
  26. data/client/hippo/components/text-editor/image-plugin/Component/Display/index.js +80 -0
  27. data/client/hippo/components/text-editor/image-plugin/Component/Form/index.js +40 -0
  28. data/client/hippo/components/text-editor/image-plugin/Component/index.js +16 -0
  29. data/client/hippo/components/text-editor/image-plugin/Component/index.scss +0 -0
  30. data/client/hippo/components/text-editor/image-plugin/index.js +32 -0
  31. data/client/hippo/components/text-editor/image-plugin/index.scss +25 -0
  32. data/client/hippo/components/text-editor/plugins.js +22 -0
  33. data/client/hippo/components/text-editor/renderer.jsx +37 -0
  34. data/client/hippo/components/text-editor/text-editor.scss +49 -0
  35. data/client/hippo/extensions/base.js +5 -2
  36. data/client/hippo/extensions/hippo.js +5 -2
  37. data/client/hippo/extensions/index.js +9 -3
  38. data/client/hippo/lib/__mocks__/request-assets.js +2 -2
  39. data/client/hippo/lib/bootstrap.js +0 -1
  40. data/client/hippo/lib/smooth-scroll.js +0 -1
  41. data/client/hippo/lib/util.js +4 -4
  42. data/client/hippo/models/asset.js +43 -23
  43. data/client/hippo/models/base.js +1 -2
  44. data/client/hippo/models/config.js +0 -1
  45. data/client/hippo/models/decorators.js +3 -3
  46. data/client/hippo/models/pub_sub.js +2 -5
  47. data/client/hippo/models/pub_sub/channel.js +0 -1
  48. data/client/hippo/models/query.js +0 -1
  49. data/client/hippo/models/query/array-result.js +0 -1
  50. data/client/hippo/models/query/clause.js +0 -1
  51. data/client/hippo/models/query/field.js +0 -1
  52. data/client/hippo/models/query/info.js +0 -3
  53. data/client/hippo/models/query/operator.js +0 -2
  54. data/client/hippo/models/query/result.js +0 -1
  55. data/client/hippo/models/sync.js +1 -1
  56. data/client/hippo/models/system-setting.js +0 -2
  57. data/client/hippo/models/tenant.js +0 -1
  58. data/client/hippo/screens/definition.js +0 -2
  59. data/client/hippo/screens/group.js +0 -2
  60. data/client/hippo/screens/instance.js +0 -3
  61. data/client/hippo/screens/system-settings/mailer-config.jsx +0 -3
  62. data/client/hippo/styles/global.scss +3 -0
  63. data/client/hippo/testing/mocks/fetch.js +6 -6
  64. data/client/hippo/user.js +4 -4
  65. data/client/hippo/workspace/index.jsx +12 -5
  66. data/client/hippo/workspace/root-view.jsx +10 -0
  67. data/command-reference-files/initial/.gitignore +1 -0
  68. data/command-reference-files/initial/Gemfile +1 -1
  69. data/command-reference-files/initial/client/appy-app/extension.js +4 -0
  70. data/command-reference-files/initial/config/webpack.config.js +25 -23
  71. data/config/routes.rb +4 -1
  72. data/config/webpack.config.js +1 -2
  73. data/hippo-fw.gemspec +4 -2
  74. data/lib/hippo.rb +0 -1
  75. data/lib/hippo/api/handlers/asset.rb +9 -0
  76. data/lib/hippo/api/updates.rb +2 -2
  77. data/lib/hippo/configuration.rb +0 -4
  78. data/lib/hippo/db.rb +2 -2
  79. data/lib/hippo/{rails.rb → db/fake_rails.rb} +0 -0
  80. data/lib/hippo/extension.rb +1 -2
  81. data/lib/hippo/mailer.rb +1 -0
  82. data/lib/hippo/spec_helper.rb +1 -0
  83. data/lib/hippo/version.rb +1 -1
  84. data/package-lock.json +626 -68
  85. data/package.json +14 -13
  86. data/spec/client/access/login-dialog.spec.jsx +1 -0
  87. data/spec/client/components/asset.spec.jsx +1 -2
  88. data/spec/client/components/date-time.spec.jsx +20 -0
  89. data/spec/client/extension/base.spec.js +0 -2
  90. data/spec/client/models/base.spec.js +4 -4
  91. data/spec/client/models/query.spec.js +2 -2
  92. data/spec/client/models/sync.spec.js +1 -1
  93. data/spec/client/screens/system-settings.spec.jsx +2 -1
  94. data/spec/client/setup.js +3 -0
  95. data/spec/client/test-models.js +0 -2
  96. data/spec/server/api/tenant_change_spec.rb +1 -1
  97. data/spec/server/api/tenant_isolation_spec.rb +6 -3
  98. data/spec/server/concerns/api_path_spec.rb +2 -2
  99. data/templates/.gitignore +1 -0
  100. data/templates/client/extension.js +4 -0
  101. data/templates/config/webpack.config.js +25 -23
  102. metadata +48 -12
  103. data/client/hippo/lib/pub_sub.js +0 -34
  104. data/client/hippo/react/viewport-root.jsx +0 -44
  105. data/spec/client/components/__snapshots__/asset.spec.jsx.snap +0 -48
  106. data/spec/client/screens/__snapshots__/system-settings.spec.jsx.snap +0 -443
  107. data/templates/gitignore +0 -4
@@ -3,9 +3,12 @@ import {
3
3
  BaseExtension, identifiedBy, identifier,
4
4
  } from './base';
5
5
 
6
+ import Extensions from './index';
7
+
6
8
  @identifiedBy('extensions/hippo')
7
9
  export default class HippoExtension extends BaseExtension {
8
-
9
10
  @identifier id = 'hippo';
10
-
11
11
  }
12
+
13
+
14
+ Extensions.register(HippoExtension);
@@ -1,7 +1,7 @@
1
1
  import { invoke, each } from 'lodash';
2
2
  import { observable } from 'mobx';
3
3
 
4
- export default {
4
+ const Extensions = {
5
5
 
6
6
  instances: observable.map(),
7
7
 
@@ -38,10 +38,16 @@ export default {
38
38
  },
39
39
 
40
40
  setBootstrapData(bootstrapData) {
41
- this.controlling_id = bootstrapData.controlling_extension;
41
+ if (bootstrapData.controlling_extension) {
42
+ this.controlling_id = bootstrapData.controlling_extension;
43
+ }
42
44
  this.instances.forEach((instance, key) => {
43
- invoke(instance, 'setBootstrapData', bootstrapData[key]);
45
+ if (bootstrapData[key]) {
46
+ invoke(instance, 'setBootstrapData', bootstrapData[key]);
47
+ }
44
48
  });
45
49
  },
46
50
 
47
51
  };
52
+
53
+ export default Extensions;
@@ -1,6 +1,6 @@
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)),
5
+ );
6
6
  export default MockedRequestAssets;
@@ -7,7 +7,6 @@ const ERROR = Symbol('ERROR');
7
7
  const COMPLETE = Symbol('COMPLETE');
8
8
 
9
9
  export default class Bootstrap {
10
-
11
10
  constructor(options = {}) {
12
11
  this.options = options;
13
12
  this.callbacks = { onReady: [] };
@@ -17,7 +17,6 @@ const POSITION = function(start, end, elapsed, duration) {
17
17
  };
18
18
 
19
19
  export default class SmoothScroll {
20
-
21
20
  constructor(link, destination, options = {}) {
22
21
  this.link = link;
23
22
  this.destination = destination;
@@ -29,7 +29,7 @@ export function titleize(words) {
29
29
  return (words)
30
30
  .replace(/[\W_]/g, ' ')
31
31
  .replace(/\S+/g, word => (word.charAt(0).toUpperCase() + word.slice(1)),
32
- );
32
+ );
33
33
  }
34
34
 
35
35
  export function underscored(str) {
@@ -41,15 +41,15 @@ export function underscored(str) {
41
41
 
42
42
  export function humanize(str) {
43
43
  return capitalize(trim(underscored(str)
44
- .replace(/_id$/, '')
45
- .replace(/_/g, ' ')));
44
+ .replace(/_id$/, '')
45
+ .replace(/_/g, ' ')));
46
46
  }
47
47
 
48
48
  export function renameProperties(object, keyMap) {
49
49
  each(keyMap, (to, from) => {
50
50
  if (object[from]) {
51
51
  object[to] = object[from]; // eslint-disable-line no-param-reassign
52
- delete object[from]; // eslint-disable-line no-param-reassign
52
+ delete object[from]; // eslint-disable-line no-param-reassign
53
53
  }
54
54
  });
55
55
  return object;
@@ -1,5 +1,5 @@
1
- import { includes, get } from 'lodash';
2
- import { observe } from 'mobx';
1
+ import { includes, get, extend } from 'lodash';
2
+ import { observe, action } from 'mobx';
3
3
  import {
4
4
  BaseModel, identifiedBy, field, session, identifier, computed,
5
5
  } from './base';
@@ -15,10 +15,8 @@ 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
-
19
18
  @identifier id;
20
19
  @field order;
21
- @session file_data;
22
20
 
23
21
  @session file;
24
22
 
@@ -30,27 +28,50 @@ export default class Asset extends BaseModel {
30
28
 
31
29
  constructor(props) {
32
30
  super(props);
33
- observe(this, 'owner', ({ newValue: owner }) => {
34
- if (this.ownerSaveDisposer) { this.ownerSaveDisposer(); }
35
- if (!owner || !owner.isModel) { return; }
36
- this.ownerSaveDisposer = observe(owner, 'syncInProgress', ({ newValue, oldValue }) => {
37
- if (this.isDirty &&
38
- !this.syncInProgress &&
39
- !oldValue &&
40
- newValue && newValue.isUpdate
41
- ) {
42
- newValue.whenComplete(() => this.save());
43
- }
44
- });
45
- }, true);
31
+ observe(this, 'owner', this.onOwnerChange, true);
32
+ }
33
+
34
+ static get syncUrl() {
35
+ return Config.api_path + Config.assets_path_prefix;
46
36
  }
47
37
 
48
- @computed get baseUrl() {
49
- return Config.api_host + Config.api_path + Config.assets_path_prefix;
38
+
39
+ @action.bound
40
+ onOwnerChange({ newValue: owner }) {
41
+ if (this.ownerSaveDisposer) { this.ownerSaveDisposer(); }
42
+ if (!owner || !owner.isModel) { return; }
43
+ this.ownerSaveDisposer = observe(owner, 'syncInProgress', ({ newValue, oldValue }) => {
44
+ if (this.isDirty &&
45
+ !this.syncInProgress &&
46
+ !oldValue &&
47
+ newValue && newValue.isUpdate
48
+ ) {
49
+ newValue.whenComplete(() => this.save());
50
+ }
51
+ });
50
52
  }
51
53
 
54
+ setFile(file) {
55
+ if (file.preview) {
56
+ this.file = file;
57
+ return Promise.resolve(this);
58
+ }
59
+ return new Promise((resolve) => {
60
+ const reader = new FileReader();
61
+ reader.onloadend = (ev) => {
62
+ if ('loadend' === ev.type) {
63
+ extend(file, { preview: reader.result });
64
+ this.file = file;
65
+ resolve(this);
66
+ }
67
+ };
68
+ reader.readAsDataURL(file);
69
+ });
70
+ }
71
+
72
+
52
73
  @computed get isDirty() {
53
- return !!this.file;
74
+ return Boolean(this.isNew || !this.file);
54
75
  }
55
76
 
56
77
  @computed get exists() {
@@ -68,13 +89,13 @@ export default class Asset extends BaseModel {
68
89
  );
69
90
  }
70
91
 
71
- @computed get previewUrl() {
92
+ get previewUrl() {
72
93
  return get(this, 'file.preview', this.urlFor('thumbnail'));
73
94
  }
74
95
 
75
96
  urlFor(type = 'original') {
76
97
  const url = get(this, `file_data.${type}.id`);
77
- return url ? `${this.baseUrl}/${url}` : null;
98
+ return url ? `${Config.api_host}${this.constructor.syncUrl}/${url}` : null;
78
99
  }
79
100
 
80
101
  save() {
@@ -98,5 +119,4 @@ export default class Asset extends BaseModel {
98
119
  return json;
99
120
  }).then(json => this.set(json.data));
100
121
  }
101
-
102
122
  }
@@ -29,7 +29,6 @@ export {
29
29
  };
30
30
 
31
31
  export class BaseModel {
32
-
33
32
  static get propType() {
34
33
  return PropTypes.instanceOf(this);
35
34
  }
@@ -122,7 +121,7 @@ export class BaseModel {
122
121
 
123
122
  fetch(options = {}) {
124
123
  invariant((!this.isNew || options.query),
125
- 'Unable to fetch record without it’s identifier being set or a query');
124
+ 'Unable to fetch record without it’s identifier being set or a query');
126
125
  const fetchOptions = extend(options, { limit: 1, method: 'GET' });
127
126
  if (!fetchOptions.query) {
128
127
  fetchOptions.query = { [`${this.constructor.identifierFieldName}`]: this.identifierFieldValue };
@@ -56,5 +56,4 @@ export default class Config {
56
56
  this.access_token = null;
57
57
  if (this.user) { this.user.reset(); }
58
58
  }
59
-
60
59
  }
@@ -36,20 +36,20 @@ function validateChangeType(validator, propertyName, change) {
36
36
  invariant(VALIDATORS[validator], `Unknown TypeCheck: ${validator} does not exist`);
37
37
  if (COERCE[validator]) { COERCE[validator](change); }
38
38
  invariant(VALIDATORS[validator](change.newValue),
39
- `Bad Type: Attempted to set ${propertyName} to '${change.newValue}' which is not ${validator}`);
39
+ `Bad Type: Attempted to set ${propertyName} to '${change.newValue}' which is not ${validator}`);
40
40
  return change;
41
41
  }
42
42
 
43
43
  function addTypeSafeInterceptor(target, property, type) {
44
44
  if (isEmpty(type)) { return; }
45
45
  addLazyInitializer(target, (partial(intercept, partial.placeholder, property,
46
- partial(validateChangeType, type, property))));
46
+ partial(validateChangeType, type, property))));
47
47
  }
48
48
 
49
49
  function decoratorWrapper(decorator, defaultOptions = {}) {
50
50
  return (targetOrOptions, ...args) => {
51
51
  const options = isEmpty(args) ?
52
- defaults(targetOrOptions, defaultOptions) : defaultOptions;
52
+ defaults(targetOrOptions, defaultOptions) : defaultOptions;
53
53
 
54
54
  const wrap = (target, property, descriptor) => {
55
55
  const decorationFn = decorator(options);
@@ -1,9 +1,8 @@
1
1
  import { Atom, when, reaction } from 'mobx';
2
2
  import ActionCable from 'actioncable';
3
3
  import invariant from 'invariant';
4
- import { omit, invoke, mapValues } from 'lodash';
4
+ import { invoke, mapValues } from 'lodash';
5
5
 
6
- import { logger } from '../lib/util';
7
6
  import User from '../user';
8
7
  import Config from '../config';
9
8
 
@@ -12,7 +11,6 @@ import PubSubCableChannel from './pub_sub/channel';
12
11
  let PubSub;
13
12
 
14
13
  class PubSubMap {
15
-
16
14
  constructor(modelKlass) {
17
15
  this.channel_prefix = modelKlass.identifiedBy;
18
16
  this.map = Object.create(null);
@@ -53,7 +51,7 @@ class PubSubMap {
53
51
  remove(model) {
54
52
  const { id, models } = this.forModel(model);
55
53
  const indx = models.indexOf(model);
56
- invariant(-1 !== indx, "Asked to remove model from pubsub but it was never observed");
54
+ invariant(-1 !== indx, 'Asked to remove model from pubsub but it was never observed');
57
55
  if (1 === models.length) {
58
56
  delete this.map[id];
59
57
  PubSub.channel.unsubscribe(this.channelForId(id));
@@ -120,7 +118,6 @@ export function stop() {
120
118
  }
121
119
 
122
120
  export class PubSubAtom {
123
-
124
121
  constructor(model) {
125
122
  this.model = model;
126
123
  if (model.identifierFieldValue) {
@@ -6,7 +6,6 @@ import { BaseModel } from '../base';
6
6
  const CHANNEL_SPLITTER = new RegExp('^(.*):(.*)/([^/]+)$');
7
7
 
8
8
  export default class PubSubCableChannel {
9
-
10
9
  constructor(pub_sub) {
11
10
  this.channel = pub_sub.cable.subscriptions.create(
12
11
  'pubsub', this,
@@ -14,7 +14,6 @@ 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
-
18
17
  @belongsTo src;
19
18
 
20
19
  @identifier id;
@@ -11,7 +11,6 @@ import {
11
11
 
12
12
  @identifiedBy('hippo/query/array-result')
13
13
  export default class ArrayResult extends Result {
14
-
15
14
  @belongsTo query;
16
15
  @observable totalCount = 0;
17
16
  @observable rows;
@@ -5,7 +5,6 @@ import {
5
5
 
6
6
  @identifiedBy('hippo/query/clause')
7
7
  export default class Clause extends BaseModel {
8
-
9
8
  @observable id = uniqueId('clause');
10
9
 
11
10
  @observable value;
@@ -6,7 +6,6 @@ import {
6
6
 
7
7
  @identifiedBy('hippo/query/field')
8
8
  export default class Field extends BaseModel {
9
-
10
9
  @identifier({ type: 'string' }) id;
11
10
  @session title;
12
11
  @session selected = false;
@@ -2,7 +2,6 @@ import { filter, reduce, find } from 'lodash';
2
2
  import { computed } from 'mobx';
3
3
 
4
4
  export default class Info {
5
-
6
5
  constructor(query) {
7
6
  this.query = query;
8
7
  }
@@ -38,6 +37,4 @@ export default class Info {
38
37
  const property = this.query.src.identifierFieldName;
39
38
  return find(this.query.fields, { id: property });
40
39
  }
41
-
42
-
43
40
  }
@@ -4,7 +4,6 @@ import {
4
4
 
5
5
  @identifiedBy('hippo/query/operators/operator')
6
6
  export default class Operator extends BaseModel {
7
-
8
7
  @identifier({ type: 'string' }) id;
9
8
  @session label;
10
9
  @session selected;
@@ -17,5 +16,4 @@ export default class Operator extends BaseModel {
17
16
  if (!this.types.length) { return true; }
18
17
  return !!this.types.find(t => t === field.queryType);
19
18
  }
20
-
21
19
  }
@@ -8,7 +8,6 @@ import {
8
8
 
9
9
  @identifiedBy('hippo/query/result')
10
10
  export default class Result extends BaseModel {
11
-
12
11
  @belongsTo({ type: 'hippo/query' }) query;
13
12
 
14
13
  sortingFunction() {
@@ -82,7 +82,7 @@ function perform(urlPrefix, defaultOptions = {}) {
82
82
  }
83
83
 
84
84
  const peformMobxyRequest = action('SyncforModel', (mobxObj, options = {}) => {
85
- mobxObj.syncInProgress = new SyncProgess(options); // eslint-disable-line no-param-reassign
85
+ mobxObj.syncInProgress = new SyncProgess(options); // eslint-disable-line no-param-reassign
86
86
  return perform(options.url || mobxObj.syncUrl, options)
87
87
  .then(action('syncSuccessHandler', (json) => {
88
88
  extend(mobxObj, {
@@ -8,7 +8,6 @@ import Asset from './asset';
8
8
 
9
9
  @identifiedBy('hippo/system-settings')
10
10
  export default class SystemSettings extends BaseModel {
11
-
12
11
  @identifier id;
13
12
  @field({ type: 'object' }) settings;
14
13
 
@@ -22,5 +21,4 @@ export default class SystemSettings extends BaseModel {
22
21
  @computed get syncUrl() {
23
22
  return this.constructor.syncUrl;
24
23
  }
25
-
26
24
  }
@@ -10,7 +10,6 @@ const CACHE = observable({
10
10
 
11
11
  @identifiedBy('hippo/tenant')
12
12
  export default class Tenant extends BaseModel {
13
-
14
13
  @computed static get current() {
15
14
  if (!CACHE.Tenant) {
16
15
  CACHE.Tenant = new Tenant();
@@ -12,7 +12,6 @@ export { asyncComponent } from 'react-async-component';
12
12
 
13
13
  @identifiedBy('hippo/screen/definition')
14
14
  export default class ScreenDefinition extends BaseModel {
15
-
16
15
  @identifier({ type: 'string' }) id;
17
16
  @session title;
18
17
  @session url_prefix;
@@ -63,5 +62,4 @@ export default class ScreenDefinition extends BaseModel {
63
62
  instance.isActive = true;
64
63
  return instance;
65
64
  }
66
-
67
65
  }
@@ -7,7 +7,6 @@ const All = observable.array();
7
7
 
8
8
  @identifiedBy('ScreensGroup')
9
9
  export default class ScreensGroup extends BaseModel {
10
-
11
10
  static all = All;
12
11
 
13
12
  @identifier({ type: 'string' }) id;
@@ -31,5 +30,4 @@ export default class ScreensGroup extends BaseModel {
31
30
  }
32
31
  return group;
33
32
  }
34
-
35
33
  }
@@ -6,7 +6,6 @@ import {
6
6
  } from '../models/base';
7
7
 
8
8
  class InstanceCollection {
9
-
10
9
  @observable models = []
11
10
  @observable active;
12
11
 
@@ -33,7 +32,6 @@ class InstanceCollection {
33
32
 
34
33
  add(model) { this.models.push(model); }
35
34
  remove(model) { this.models.remove(model); }
36
-
37
35
  }
38
36
 
39
37
  const displaying = new InstanceCollection();
@@ -41,7 +39,6 @@ export { displaying };
41
39
 
42
40
  @identifiedBy('hippo/screen-view')
43
41
  export default class Instance extends BaseModel {
44
-
45
42
  static get displaying() {
46
43
  return displaying.models;
47
44
  }