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.
- checksums.yaml +4 -4
- data/client/hippo/access/subscription-choice-layer.jsx +170 -0
- data/client/hippo/access/subscription-choice-layer/cancel-subscription.jsx +111 -0
- data/client/hippo/access/subscription-choice-layer/payment-form.jsx +154 -0
- data/client/hippo/access/subscription-choice-layer/subscription-choice.scss +29 -0
- data/client/hippo/boot.jsx +1 -1
- data/client/hippo/components/asset.jsx +1 -1
- data/client/hippo/components/asset.scss +1 -0
- data/client/hippo/components/data-table.jsx +36 -38
- data/client/hippo/components/date-time.jsx +25 -9
- data/client/hippo/components/form/api.js +1 -0
- data/client/hippo/components/form/fields.jsx +3 -2
- data/client/hippo/components/form/fields/checkbox-wrapper.jsx +1 -1
- data/client/hippo/components/form/fields/date-wrapper.jsx +1 -1
- data/client/hippo/components/form/fields/email-wrapper.jsx +31 -0
- data/client/hippo/components/form/fields/form-field.scss +8 -0
- data/client/hippo/components/form/fields/label.jsx +5 -7
- data/client/hippo/components/form/fields/select-wrapper.jsx +1 -1
- data/client/hippo/components/form/fields/tags-wrapper.jsx +1 -1
- data/client/hippo/components/form/fields/text-wrapper.jsx +1 -1
- data/client/hippo/components/form/fields/textarea-wrapper.jsx +1 -1
- data/client/hippo/components/form/wrapper.jsx +1 -1
- data/client/hippo/components/grid.js +1 -0
- data/client/hippo/components/master-detail.jsx +1 -1
- data/client/hippo/components/network-activity-overlay.jsx +6 -6
- data/client/hippo/components/payments/field.jsx +64 -0
- data/client/hippo/components/payments/field.scss +18 -0
- data/client/hippo/components/popout-window.jsx +1 -1
- data/client/hippo/components/query-builder.jsx +1 -1
- data/client/hippo/components/query-builder/boolean-picker.jsx +1 -1
- data/client/hippo/components/query-builder/clause-filter.jsx +2 -2
- data/client/hippo/components/query-builder/clause.jsx +16 -10
- data/client/hippo/components/query-builder/date-picker.jsx +1 -1
- data/client/hippo/components/query-builder/query-builder.scss +23 -2
- data/client/hippo/components/record-finder.jsx +5 -2
- data/client/hippo/components/record-finder/query-layer.jsx +1 -1
- data/client/hippo/components/save-button.jsx +1 -1
- data/client/hippo/components/screen.jsx +1 -1
- data/client/hippo/components/text-editor.jsx +82 -40
- data/client/hippo/components/text-editor/renderer.jsx +15 -35
- data/client/hippo/components/text-editor/renderer.scss +15 -0
- data/client/hippo/components/text-editor/text-editor.scss +2 -15
- data/client/hippo/components/text-editor/upload-adapter.js +66 -0
- data/client/hippo/components/time-zone-select.jsx +1 -1
- data/client/hippo/components/tool-tip.jsx +9 -14
- data/client/hippo/components/toolbar.jsx +16 -0
- data/client/hippo/components/warning-notification.jsx +1 -1
- data/client/hippo/extensions/base.js +3 -2
- data/client/hippo/extensions/index.js +1 -1
- data/client/hippo/lib/action_cable.js +8 -0
- data/client/hippo/lib/action_cable/cable.js +47 -0
- data/client/hippo/lib/action_cable/connection.js +192 -0
- data/client/hippo/lib/action_cable/connection_monitor.js +135 -0
- data/client/hippo/lib/action_cable/consumer.js +56 -0
- data/client/hippo/lib/action_cable/subscription.js +98 -0
- data/client/hippo/lib/action_cable/subscriptions.js +129 -0
- data/client/hippo/lib/date-range.js +22 -5
- data/client/hippo/lib/lazy-getter.js +31 -0
- data/client/hippo/lib/util.js +6 -1
- data/client/hippo/models/base.js +8 -3
- data/client/hippo/models/config.js +7 -2
- data/client/hippo/models/date-type.js +14 -0
- data/client/hippo/models/decorators.js +5 -5
- data/client/hippo/models/pub_sub.js +1 -1
- data/client/hippo/models/query/array-result.js +4 -1
- data/client/hippo/models/subscription.js +35 -0
- data/client/hippo/models/sync.js +1 -1
- data/client/hippo/models/tenant.js +14 -1
- data/client/hippo/react/component-not-found.jsx +14 -18
- data/client/hippo/screens/async-loading.jsx +46 -0
- data/client/hippo/screens/definition.js +2 -1
- data/client/hippo/screens/preferences.jsx +57 -0
- data/client/hippo/screens/system-settings.jsx +4 -6
- data/client/hippo/screens/system-settings/mailer-config.jsx +1 -1
- data/client/hippo/screens/system-settings/tenant.jsx +57 -4
- data/client/hippo/screens/user-management.jsx +2 -2
- data/client/hippo/screens/user-management/edit-form.jsx +1 -1
- data/client/hippo/styles/global/fancy-header.scss +2 -1
- data/client/hippo/styles/global/mixins.scss +14 -1
- data/client/hippo/testing/index.js +7 -0
- data/client/hippo/user.js +9 -2
- data/client/hippo/workspace/index.jsx +29 -8
- data/client/hippo/workspace/menu-group.jsx +1 -1
- data/client/hippo/workspace/menu-option.jsx +2 -0
- data/client/hippo/workspace/menu.jsx +30 -6
- data/client/hippo/workspace/screen.jsx +5 -1
- data/client/hippo/workspace/styles.scss +22 -3
- data/command-reference-files/initial/Gemfile +1 -1
- data/config/routes.rb +6 -0
- data/config/screens.rb +9 -17
- data/db/migrate/20171129024737_create_subscriptions.rb +12 -0
- data/fixtures/vcr_cassettes/Tenant_changes/sends_email_when_tenant_identifier_changes.yml +72 -0
- data/fixtures/vcr_cassettes/Tenant_isoloation/disallows_using_a_user_s_token_on_incorrect_domain.yml +141 -0
- data/fixtures/vcr_cassettes/Tenant_isoloation/isolates_bar_s_tenant_data_from_foo.yml +141 -0
- data/fixtures/vcr_cassettes/Tenant_isoloation/isolates_foo_s_tenant_data_from_bar.yml +141 -0
- data/hippo-fw.gemspec +4 -3
- data/lib/hippo.rb +1 -0
- data/lib/hippo/access/roles/basic_user.rb +1 -0
- data/lib/hippo/api/authentication_provider.rb +4 -5
- data/lib/hippo/api/handlers/asset.rb +9 -4
- data/lib/hippo/api/handlers/subscription.rb +39 -0
- data/lib/hippo/api/handlers/user_session.rb +0 -1
- data/lib/hippo/api/helper_methods.rb +8 -4
- data/lib/hippo/api/request_wrapper.rb +12 -1
- data/lib/hippo/command/console.rb +3 -3
- data/lib/hippo/concerns/asset_uploader.rb +8 -2
- data/lib/hippo/concerns/pub_sub.rb +8 -8
- data/lib/hippo/configuration.rb +6 -4
- data/lib/hippo/extension.rb +3 -2
- data/lib/hippo/model.rb +1 -0
- data/lib/hippo/models/subscription.rb +7 -0
- data/lib/hippo/payments.rb +129 -0
- data/lib/hippo/screen.rb +4 -2
- data/lib/hippo/screen/definition.rb +4 -0
- data/lib/hippo/spec_helper.rb +4 -4
- data/lib/hippo/templates/liquid/precision.rb +9 -0
- data/lib/hippo/tenant.rb +4 -5
- data/lib/hippo/user.rb +5 -5
- data/lib/hippo/version.rb +1 -1
- data/lib/hippo/webpack.rb +5 -1
- data/package-lock.json +437 -881
- data/package.json +19 -5
- data/spec/client/components/__snapshots__/query-builder.spec.jsx.snap +34 -1
- data/spec/client/components/__snapshots__/record-finder.spec.jsx.snap +1 -0
- data/spec/client/models/base.spec.js +7 -0
- data/spec/client/models/subscription.spec.js +8 -0
- data/spec/client/screens/__snapshots__/preferences.spec.jsx.snap +223 -0
- data/spec/client/screens/preferences.spec.jsx +10 -0
- data/spec/client/test-models.js +1 -1
- data/spec/client/workspace/__snapshots__/menu.spec.jsx.snap +12 -0
- data/spec/factories/subscription.rb +6 -0
- data/spec/factories/tenant.rb +1 -1
- data/spec/factories/user.rb +1 -1
- data/spec/server/api/tenant_change_spec.rb +11 -8
- data/spec/server/api/tenant_isolation_spec.rb +11 -8
- data/spec/server/api/user_sessions_spec.rb +10 -7
- data/spec/server/api/user_spec.rb +45 -0
- data/spec/server/models/subscription_spec.rb +10 -0
- data/spec/server/payment_helpers.rb +13 -0
- data/spec/server/print/form_spec.rb +1 -1
- data/spec/server/spec_helper.rb +3 -11
- data/templates/js/screen-definitions.js +4 -1
- data/templates/spec/factories/model.rb +1 -1
- data/views/hippo_root_view.erb +5 -5
- metadata +84 -25
- data/client/hippo/components/grid/config.json +0 -3
- data/client/hippo/components/grid/editors.scss +0 -78
- data/client/hippo/components/grid/index.js +0 -2
- data/client/hippo/components/grid/row-editor.scss +0 -74
- data/client/hippo/components/grid/styles.scss +0 -118
- data/client/hippo/components/text-editor/display-modes/Button.jsx +0 -20
- data/client/hippo/components/text-editor/display-modes/ToggleEdit.jsx +0 -23
- data/client/hippo/components/text-editor/display-modes/ToggleInsert.jsx +0 -22
- data/client/hippo/components/text-editor/display-modes/ToggleLayout.jsx +0 -22
- data/client/hippo/components/text-editor/display-modes/TogglePreview.jsx +0 -22
- data/client/hippo/components/text-editor/display-modes/ToggleResize.jsx +0 -22
- data/client/hippo/components/text-editor/display-modes/index.css +0 -0
- data/client/hippo/components/text-editor/display-modes/index.js +0 -30
- data/client/hippo/components/text-editor/image-plugin/Component/Display/index.js +0 -82
- data/client/hippo/components/text-editor/image-plugin/Component/Form/index.js +0 -42
- data/client/hippo/components/text-editor/image-plugin/Component/index.js +0 -16
- data/client/hippo/components/text-editor/image-plugin/Component/index.scss +0 -0
- data/client/hippo/components/text-editor/image-plugin/index.js +0 -32
- data/client/hippo/components/text-editor/image-plugin/index.scss +0 -25
- data/client/hippo/components/toolbar/changes-notification.scss +0 -63
- data/client/hippo/components/toolbar/index.js +0 -3
- data/client/hippo/components/toolbar/styles.scss +0 -74
@@ -0,0 +1,135 @@
|
|
1
|
+
/* eslint-disable */
|
2
|
+
import ActionCable from './cable';
|
3
|
+
|
4
|
+
// Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting
|
5
|
+
// revival reconnections if things go astray. Internal class, not intended for direct user manipulation.
|
6
|
+
ActionCable.ConnectionMonitor = (function() {
|
7
|
+
var clamp, now, secondsSince;
|
8
|
+
|
9
|
+
class ConnectionMonitor {
|
10
|
+
constructor(connection) {
|
11
|
+
this.visibilityDidChange = this.visibilityDidChange.bind(this);
|
12
|
+
this.connection = connection;
|
13
|
+
this.reconnectAttempts = 0;
|
14
|
+
}
|
15
|
+
|
16
|
+
start() {
|
17
|
+
if (!this.isRunning()) {
|
18
|
+
this.startedAt = now();
|
19
|
+
delete this.stoppedAt;
|
20
|
+
this.startPolling();
|
21
|
+
document.addEventListener("visibilitychange", this.visibilityDidChange);
|
22
|
+
return ActionCable.log(`ConnectionMonitor started. pollInterval = ${this.getPollInterval()} ms`);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
stop() {
|
27
|
+
if (this.isRunning()) {
|
28
|
+
this.stoppedAt = now();
|
29
|
+
this.stopPolling();
|
30
|
+
document.removeEventListener("visibilitychange", this.visibilityDidChange);
|
31
|
+
return ActionCable.log("ConnectionMonitor stopped");
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
isRunning() {
|
36
|
+
return (this.startedAt != null) && (this.stoppedAt == null);
|
37
|
+
}
|
38
|
+
|
39
|
+
recordPing() {
|
40
|
+
return this.pingedAt = now();
|
41
|
+
}
|
42
|
+
|
43
|
+
recordConnect() {
|
44
|
+
this.reconnectAttempts = 0;
|
45
|
+
this.recordPing();
|
46
|
+
delete this.disconnectedAt;
|
47
|
+
return ActionCable.log("ConnectionMonitor recorded connect");
|
48
|
+
}
|
49
|
+
|
50
|
+
recordDisconnect() {
|
51
|
+
this.disconnectedAt = now();
|
52
|
+
return ActionCable.log("ConnectionMonitor recorded disconnect");
|
53
|
+
}
|
54
|
+
|
55
|
+
// Private
|
56
|
+
startPolling() {
|
57
|
+
this.stopPolling();
|
58
|
+
return this.poll();
|
59
|
+
}
|
60
|
+
|
61
|
+
stopPolling() {
|
62
|
+
return clearTimeout(this.pollTimeout);
|
63
|
+
}
|
64
|
+
|
65
|
+
poll() {
|
66
|
+
return this.pollTimeout = setTimeout(() => {
|
67
|
+
this.reconnectIfStale();
|
68
|
+
return this.poll();
|
69
|
+
}, this.getPollInterval());
|
70
|
+
}
|
71
|
+
|
72
|
+
getPollInterval() {
|
73
|
+
var interval, max, min;
|
74
|
+
({min, max} = this.constructor.pollInterval);
|
75
|
+
interval = 5 * Math.log(this.reconnectAttempts + 1);
|
76
|
+
return Math.round(clamp(interval, min, max) * 1000);
|
77
|
+
}
|
78
|
+
|
79
|
+
reconnectIfStale() {
|
80
|
+
if (this.connectionIsStale()) {
|
81
|
+
ActionCable.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, pollInterval = ${this.getPollInterval()} ms, time disconnected = ${secondsSince(this.disconnectedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`);
|
82
|
+
this.reconnectAttempts++;
|
83
|
+
if (this.disconnectedRecently()) {
|
84
|
+
return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
|
85
|
+
} else {
|
86
|
+
ActionCable.log("ConnectionMonitor reopening");
|
87
|
+
return this.connection.reopen();
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
connectionIsStale() {
|
93
|
+
var ref;
|
94
|
+
return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
|
95
|
+
}
|
96
|
+
|
97
|
+
disconnectedRecently() {
|
98
|
+
return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
|
99
|
+
}
|
100
|
+
|
101
|
+
visibilityDidChange() {
|
102
|
+
if (document.visibilityState === "visible") {
|
103
|
+
return setTimeout(() => {
|
104
|
+
if (this.connectionIsStale() || !this.connection.isOpen()) {
|
105
|
+
ActionCable.log(`ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = ${document.visibilityState}`);
|
106
|
+
return this.connection.reopen();
|
107
|
+
}
|
108
|
+
}, 200);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
};
|
113
|
+
|
114
|
+
ConnectionMonitor.pollInterval = {
|
115
|
+
min: 3,
|
116
|
+
max: 30
|
117
|
+
};
|
118
|
+
|
119
|
+
ConnectionMonitor.staleThreshold = 6; // Server::Connections::BEAT_INTERVAL * 2 (missed two pings)
|
120
|
+
|
121
|
+
now = function() {
|
122
|
+
return new Date().getTime();
|
123
|
+
};
|
124
|
+
|
125
|
+
secondsSince = function(time) {
|
126
|
+
return (now() - time) / 1000;
|
127
|
+
};
|
128
|
+
|
129
|
+
clamp = function(number, min, max) {
|
130
|
+
return Math.max(min, Math.min(max, number));
|
131
|
+
};
|
132
|
+
|
133
|
+
return ConnectionMonitor;
|
134
|
+
|
135
|
+
})();
|
@@ -0,0 +1,56 @@
|
|
1
|
+
/* eslint-disable */
|
2
|
+
import ActionCable from './cable';
|
3
|
+
|
4
|
+
// The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established,
|
5
|
+
// the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates.
|
6
|
+
// The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription
|
7
|
+
// method.
|
8
|
+
|
9
|
+
// The following example shows how this can be setup:
|
10
|
+
|
11
|
+
// @App = {}
|
12
|
+
// App.cable = ActionCable.createConsumer "ws://example.com/accounts/1"
|
13
|
+
// App.appearance = App.cable.subscriptions.create "AppearanceChannel"
|
14
|
+
|
15
|
+
// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
|
16
|
+
|
17
|
+
// When a consumer is created, it automatically connects with the server.
|
18
|
+
|
19
|
+
// To disconnect from the server, call
|
20
|
+
|
21
|
+
// App.cable.disconnect()
|
22
|
+
|
23
|
+
// and to restart the connection:
|
24
|
+
|
25
|
+
// App.cable.connect()
|
26
|
+
|
27
|
+
// Any channel subscriptions which existed prior to disconnecting will
|
28
|
+
// automatically resubscribe.
|
29
|
+
ActionCable.Consumer = class Consumer {
|
30
|
+
constructor(url) {
|
31
|
+
this.url = url;
|
32
|
+
this.subscriptions = new ActionCable.Subscriptions(this);
|
33
|
+
this.connection = new ActionCable.Connection(this);
|
34
|
+
}
|
35
|
+
|
36
|
+
send(data) {
|
37
|
+
return this.connection.send(data);
|
38
|
+
}
|
39
|
+
|
40
|
+
connect() {
|
41
|
+
return this.connection.open();
|
42
|
+
}
|
43
|
+
|
44
|
+
disconnect() {
|
45
|
+
return this.connection.close({
|
46
|
+
allowReconnect: false
|
47
|
+
});
|
48
|
+
}
|
49
|
+
|
50
|
+
ensureActiveConnection() {
|
51
|
+
if (!this.connection.isActive()) {
|
52
|
+
return this.connection.open();
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
};
|
@@ -0,0 +1,98 @@
|
|
1
|
+
/* eslint-disable */
|
2
|
+
import ActionCable from './cable';
|
3
|
+
|
4
|
+
// A new subscription is created through the ActionCable.Subscriptions instance available on the consumer.
|
5
|
+
// It provides a number of callbacks and a method for calling remote procedure calls on the corresponding
|
6
|
+
// Channel instance on the server side.
|
7
|
+
|
8
|
+
// An example demonstrates the basic functionality:
|
9
|
+
|
10
|
+
// App.appearance = App.cable.subscriptions.create "AppearanceChannel",
|
11
|
+
// connected: ->
|
12
|
+
// # Called once the subscription has been successfully completed
|
13
|
+
|
14
|
+
// disconnected: ({ willAttemptReconnect: boolean }) ->
|
15
|
+
// # Called when the client has disconnected with the server.
|
16
|
+
// # The object will have an `willAttemptReconnect` property which
|
17
|
+
// # says whether the client has the intention of attempting
|
18
|
+
// # to reconnect.
|
19
|
+
|
20
|
+
// appear: ->
|
21
|
+
// @perform 'appear', appearing_on: @appearingOn()
|
22
|
+
|
23
|
+
// away: ->
|
24
|
+
// @perform 'away'
|
25
|
+
|
26
|
+
// appearingOn: ->
|
27
|
+
// $('main').data 'appearing-on'
|
28
|
+
|
29
|
+
// The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server
|
30
|
+
// by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away).
|
31
|
+
// The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter.
|
32
|
+
|
33
|
+
// This is how the server component would look:
|
34
|
+
|
35
|
+
// class AppearanceChannel < ApplicationActionCable::Channel
|
36
|
+
// def subscribed
|
37
|
+
// current_user.appear
|
38
|
+
// end
|
39
|
+
|
40
|
+
// def unsubscribed
|
41
|
+
// current_user.disappear
|
42
|
+
// end
|
43
|
+
|
44
|
+
// def appear(data)
|
45
|
+
// current_user.appear on: data['appearing_on']
|
46
|
+
// end
|
47
|
+
|
48
|
+
// def away
|
49
|
+
// current_user.away
|
50
|
+
// end
|
51
|
+
// end
|
52
|
+
|
53
|
+
// The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name.
|
54
|
+
// The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method.
|
55
|
+
ActionCable.Subscription = (function() {
|
56
|
+
var extend;
|
57
|
+
|
58
|
+
class Subscription {
|
59
|
+
constructor(consumer, params = {}, mixin) {
|
60
|
+
this.consumer = consumer;
|
61
|
+
this.identifier = JSON.stringify(params);
|
62
|
+
extend(this, mixin);
|
63
|
+
}
|
64
|
+
|
65
|
+
// Perform a channel action with the optional data passed as an attribute
|
66
|
+
perform(action, data = {}) {
|
67
|
+
data.action = action;
|
68
|
+
return this.send(data);
|
69
|
+
}
|
70
|
+
|
71
|
+
send(data) {
|
72
|
+
return this.consumer.send({
|
73
|
+
command: "message",
|
74
|
+
identifier: this.identifier,
|
75
|
+
data: JSON.stringify(data)
|
76
|
+
});
|
77
|
+
}
|
78
|
+
|
79
|
+
unsubscribe() {
|
80
|
+
return this.consumer.subscriptions.remove(this);
|
81
|
+
}
|
82
|
+
|
83
|
+
};
|
84
|
+
|
85
|
+
extend = function(object, properties) {
|
86
|
+
var key, value;
|
87
|
+
if (properties != null) {
|
88
|
+
for (key in properties) {
|
89
|
+
value = properties[key];
|
90
|
+
object[key] = value;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
return object;
|
94
|
+
};
|
95
|
+
|
96
|
+
return Subscription;
|
97
|
+
|
98
|
+
})();
|
@@ -0,0 +1,129 @@
|
|
1
|
+
/* eslint-disable */
|
2
|
+
import ActionCable from './cable';
|
3
|
+
|
4
|
+
// Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user
|
5
|
+
// us ActionCable.Subscriptions#create, and it should be called through the consumer like so:
|
6
|
+
|
7
|
+
// @App = {}
|
8
|
+
// App.cable = ActionCable.createConsumer "ws://example.com/accounts/1"
|
9
|
+
// App.appearance = App.cable.subscriptions.create "AppearanceChannel"
|
10
|
+
|
11
|
+
// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
|
12
|
+
ActionCable.Subscriptions = class Subscriptions {
|
13
|
+
constructor(consumer) {
|
14
|
+
this.consumer = consumer;
|
15
|
+
this.subscriptions = [];
|
16
|
+
}
|
17
|
+
|
18
|
+
create(channelName, mixin) {
|
19
|
+
var channel, params, subscription;
|
20
|
+
channel = channelName;
|
21
|
+
params = typeof channel === "object" ? channel : {channel};
|
22
|
+
subscription = new ActionCable.Subscription(this.consumer, params, mixin);
|
23
|
+
return this.add(subscription);
|
24
|
+
}
|
25
|
+
|
26
|
+
// Private
|
27
|
+
add(subscription) {
|
28
|
+
this.subscriptions.push(subscription);
|
29
|
+
this.consumer.ensureActiveConnection();
|
30
|
+
this.notify(subscription, "initialized");
|
31
|
+
this.sendCommand(subscription, "subscribe");
|
32
|
+
return subscription;
|
33
|
+
}
|
34
|
+
|
35
|
+
remove(subscription) {
|
36
|
+
this.forget(subscription);
|
37
|
+
if (!this.findAll(subscription.identifier).length) {
|
38
|
+
this.sendCommand(subscription, "unsubscribe");
|
39
|
+
}
|
40
|
+
return subscription;
|
41
|
+
}
|
42
|
+
|
43
|
+
reject(identifier) {
|
44
|
+
var i, len, ref, results, subscription;
|
45
|
+
ref = this.findAll(identifier);
|
46
|
+
results = [];
|
47
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
48
|
+
subscription = ref[i];
|
49
|
+
this.forget(subscription);
|
50
|
+
this.notify(subscription, "rejected");
|
51
|
+
results.push(subscription);
|
52
|
+
}
|
53
|
+
return results;
|
54
|
+
}
|
55
|
+
|
56
|
+
forget(subscription) {
|
57
|
+
var s;
|
58
|
+
this.subscriptions = (function() {
|
59
|
+
var i, len, ref, results;
|
60
|
+
ref = this.subscriptions;
|
61
|
+
results = [];
|
62
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
63
|
+
s = ref[i];
|
64
|
+
if (s !== subscription) {
|
65
|
+
results.push(s);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
return results;
|
69
|
+
}).call(this);
|
70
|
+
return subscription;
|
71
|
+
}
|
72
|
+
|
73
|
+
findAll(identifier) {
|
74
|
+
var i, len, ref, results, s;
|
75
|
+
ref = this.subscriptions;
|
76
|
+
results = [];
|
77
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
78
|
+
s = ref[i];
|
79
|
+
if (s.identifier === identifier) {
|
80
|
+
results.push(s);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
return results;
|
84
|
+
}
|
85
|
+
|
86
|
+
reload() {
|
87
|
+
var i, len, ref, results, subscription;
|
88
|
+
ref = this.subscriptions;
|
89
|
+
results = [];
|
90
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
91
|
+
subscription = ref[i];
|
92
|
+
results.push(this.sendCommand(subscription, "subscribe"));
|
93
|
+
}
|
94
|
+
return results;
|
95
|
+
}
|
96
|
+
|
97
|
+
notifyAll(callbackName, ...args) {
|
98
|
+
var i, len, ref, results, subscription;
|
99
|
+
ref = this.subscriptions;
|
100
|
+
results = [];
|
101
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
102
|
+
subscription = ref[i];
|
103
|
+
results.push(this.notify(subscription, callbackName, ...args));
|
104
|
+
}
|
105
|
+
return results;
|
106
|
+
}
|
107
|
+
|
108
|
+
notify(subscription, callbackName, ...args) {
|
109
|
+
var i, len, results, subscriptions;
|
110
|
+
if (typeof subscription === "string") {
|
111
|
+
subscriptions = this.findAll(subscription);
|
112
|
+
} else {
|
113
|
+
subscriptions = [subscription];
|
114
|
+
}
|
115
|
+
results = [];
|
116
|
+
for (i = 0, len = subscriptions.length; i < len; i++) {
|
117
|
+
subscription = subscriptions[i];
|
118
|
+
results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : void 0);
|
119
|
+
}
|
120
|
+
return results;
|
121
|
+
}
|
122
|
+
|
123
|
+
sendCommand(subscription, command) {
|
124
|
+
var identifier;
|
125
|
+
({identifier} = subscription);
|
126
|
+
return this.consumer.send({command, identifier});
|
127
|
+
}
|
128
|
+
|
129
|
+
};
|
@@ -1,10 +1,19 @@
|
|
1
|
-
import moment from 'moment';
|
2
|
-
import { observable, computed } from 'mobx';
|
3
|
-
import { isString, isEmpty, map,
|
1
|
+
import moment from 'moment-timezone';
|
2
|
+
import { observable, computed, isObservableArray, intercept } from 'mobx';
|
3
|
+
import { includes, isString, isEmpty, map, isArray, first, last } from 'lodash';
|
4
4
|
import {
|
5
5
|
identifiedBy,
|
6
6
|
} from 'mobx-decorated-models';
|
7
7
|
|
8
|
+
function coerceToMoment(change) {
|
9
|
+
if (!includes(['start', 'end'], change.name)) { return change; }
|
10
|
+
if (change.newValue && !moment.isMoment(change.newValue)) {
|
11
|
+
change.newValue = moment(change.newValue);
|
12
|
+
}
|
13
|
+
return change;
|
14
|
+
}
|
15
|
+
|
16
|
+
|
8
17
|
@identifiedBy('date-range')
|
9
18
|
export default class DateRange {
|
10
19
|
|
@@ -12,8 +21,12 @@ export default class DateRange {
|
|
12
21
|
@observable end;
|
13
22
|
|
14
23
|
constructor(range) {
|
24
|
+
intercept(this, coerceToMoment);
|
15
25
|
if (isString(range) && !isEmpty(range)) {
|
16
26
|
[this.start, this.end] = map(range.split('...'), d => new Date(d));
|
27
|
+
} else if (isArray(range) || isObservableArray(range)) {
|
28
|
+
this.start = first(range);
|
29
|
+
this.end = last(range);
|
17
30
|
} else if (range) {
|
18
31
|
this.start = range.start;
|
19
32
|
this.end = range.end;
|
@@ -21,8 +34,8 @@ export default class DateRange {
|
|
21
34
|
}
|
22
35
|
|
23
36
|
toJSON() {
|
24
|
-
if (this.start && this.end &&
|
25
|
-
// this strange format is what PG
|
37
|
+
if (this.start && this.end && this.start.isValid() && this.end.isValid()) {
|
38
|
+
// this strange format is what PG uses and is therefore what active record expects
|
26
39
|
return `[${this.start.toISOString()},${this.end.toISOString()})`;
|
27
40
|
}
|
28
41
|
return '';
|
@@ -36,4 +49,8 @@ export default class DateRange {
|
|
36
49
|
return moment(this.end).isAfter() && moment(this.start).isBefore();
|
37
50
|
}
|
38
51
|
|
52
|
+
@computed get asArray() {
|
53
|
+
return [this.start, this.end];
|
54
|
+
}
|
55
|
+
|
39
56
|
}
|