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.
- checksums.yaml +4 -4
- data/client/hippo/components/asset.jsx +22 -2
- data/client/hippo/components/asset.scss +1 -1
- data/client/hippo/components/data-list.jsx +38 -27
- data/client/hippo/components/data-list/data-list.scss +2 -0
- data/client/hippo/components/data-table.jsx +4 -2
- data/client/hippo/components/data-table/formatters.js +7 -0
- data/client/hippo/components/data-table/table-styles.scss +10 -0
- data/client/hippo/components/date-time.jsx +93 -133
- data/client/hippo/components/date-time.scss +1 -36
- data/client/hippo/components/form/api.js +4 -3
- data/client/hippo/components/form/fields.jsx +7 -1
- data/client/hippo/components/form/fields/date-wrapper.jsx +4 -4
- data/client/hippo/components/icon.jsx +10 -1
- data/client/hippo/components/query-builder.jsx +6 -1
- data/client/hippo/components/record-finder/query-layer.jsx +1 -1
- data/client/hippo/components/save-button.jsx +55 -0
- data/client/hippo/components/text-editor.jsx +10 -9
- data/client/hippo/components/text-editor/text-editor.scss +0 -1
- data/client/hippo/components/time-zone-select.jsx +60 -0
- data/client/hippo/models/asset.js +10 -5
- data/client/hippo/models/base.js +1 -1
- data/client/hippo/models/pub_sub.js +22 -67
- data/client/hippo/models/pub_sub/channel.js +28 -8
- data/client/hippo/models/pub_sub/map.js +57 -0
- data/client/hippo/models/query/array-result.js +5 -4
- data/client/hippo/models/query/field.js +19 -3
- data/client/hippo/models/system-setting.js +16 -1
- data/client/hippo/models/tenant.js +8 -7
- data/client/hippo/screens/system-settings.jsx +10 -12
- data/client/hippo/screens/user-management/edit-form.jsx +10 -11
- data/client/hippo/workspace/index.jsx +6 -3
- data/client/hippo/workspace/menu-option.jsx +2 -5
- data/client/hippo/workspace/menu.jsx +13 -1
- data/client/hippo/workspace/styles.scss +11 -26
- data/command-reference-files/initial/Gemfile +1 -1
- data/command-reference-files/model/client/appy-app/models/test_test.js +1 -1
- data/command-reference-files/model/spec/server/models/test_test_spec.rb +10 -0
- data/lib/hippo/api/cable.rb +0 -2
- data/lib/hippo/command/generate_model.rb +2 -3
- data/lib/hippo/spec_helper.rb +4 -2
- data/lib/hippo/tenant.rb +7 -1
- data/lib/hippo/version.rb +1 -1
- data/package-lock.json +60 -46
- data/package.json +5 -2
- data/spec/client/components/__snapshots__/record-finder.spec.jsx.snap +1 -0
- data/spec/client/components/__snapshots__/time-zone-select.spec.jsx.snap +48 -0
- data/spec/client/components/form.spec.jsx +7 -0
- data/spec/client/components/time-zone-select.spec.jsx +11 -0
- data/spec/client/models/pub_sub.spec.js +1 -3
- data/spec/client/models/pub_sub/channel.spec.js +45 -0
- data/spec/client/models/system-setting.spec.js +14 -0
- data/spec/client/workspace/__snapshots__/menu.spec.jsx.snap +9 -9
- data/spec/server/api/tenant_change_spec.rb +1 -1
- data/templates/client/models/model.js +1 -1
- data/templates/spec/factories/model.rb +6 -0
- data/templates/spec/server/model_spec.rb +4 -4
- data/views/hippo_root_view.erb +4 -0
- metadata +11 -10
- data/client/hippo/components/date-time/calendar.jsx +0 -113
- data/client/hippo/components/date-time/date-time-drop.jsx +0 -75
- data/client/hippo/components/date-time/date-time.scss +0 -157
- data/client/hippo/components/date-time/time.jsx +0 -119
- data/client/hippo/workspace/foo.js +0 -0
- data/command-reference-files/model/spec/fixtures/appy-app/test_test.yml +0 -11
- data/command-reference-files/model/spec/server/test_test_spec.rb +0 -10
- data/spec/client/components/date-time.spec.jsx +0 -20
@@ -1,38 +1,3 @@
|
|
1
1
|
#hippo-root {
|
2
|
-
@import '~
|
3
|
-
.date-time.component {
|
4
|
-
|
5
|
-
.tab {
|
6
|
-
display: none;
|
7
|
-
&.active { display: block; }
|
8
|
-
}
|
9
|
-
|
10
|
-
.tabs {
|
11
|
-
> * { padding: 0.23rem; }
|
12
|
-
margin-bottom: 10px;
|
13
|
-
.grommetux-control-icon { margin-right: 0.5rem; }
|
14
|
-
}
|
15
|
-
|
16
|
-
.toolbar {
|
17
|
-
display: flex;
|
18
|
-
justify-content: space-between;
|
19
|
-
button {
|
20
|
-
min-width: 30px;
|
21
|
-
padding: 0;
|
22
|
-
&.next-month {
|
23
|
-
&:before {
|
24
|
-
content: ">";
|
25
|
-
display: block;
|
26
|
-
}
|
27
|
-
}
|
28
|
-
&.prev-month {
|
29
|
-
&:before {
|
30
|
-
content: "<";
|
31
|
-
display: block;
|
32
|
-
}
|
33
|
-
}
|
34
|
-
}
|
35
|
-
}
|
36
|
-
}
|
37
|
-
|
2
|
+
@import '~flatpickr/dist/themes/material_orange.css';
|
38
3
|
}
|
@@ -3,7 +3,7 @@
|
|
3
3
|
}] */
|
4
4
|
import { observable, computed, when, action } from 'mobx';
|
5
5
|
import {
|
6
|
-
pick, isFunction, mapValues, every, get, set, filter, isNil, each,
|
6
|
+
pick, isFunction, mapValues, every, get, set, filter, isNil, each, extend,
|
7
7
|
} from 'lodash';
|
8
8
|
|
9
9
|
export class FormField {
|
@@ -26,7 +26,7 @@ export class FormField {
|
|
26
26
|
|
27
27
|
update(attrs) {
|
28
28
|
each(pick(attrs, [
|
29
|
-
'name', 'default', 'help', 'validate',
|
29
|
+
'name', 'default', 'help', 'validate', 'value',
|
30
30
|
]), (v, k) => {
|
31
31
|
this[k] = isFunction(v) ? v.call(this) : v;
|
32
32
|
});
|
@@ -91,7 +91,7 @@ export class FormState {
|
|
91
91
|
if (field) {
|
92
92
|
field.update(attrs);
|
93
93
|
} else {
|
94
|
-
field = new FormField(name, attrs);
|
94
|
+
field = new FormField(name, extend({}, attrs, { value: get(this.values, name) }));
|
95
95
|
this.fields.set(name, field);
|
96
96
|
}
|
97
97
|
return field;
|
@@ -131,6 +131,7 @@ export class FormState {
|
|
131
131
|
|
132
132
|
@action
|
133
133
|
set(values) {
|
134
|
+
this.values = values;
|
134
135
|
this.fields.forEach((field, name) => {
|
135
136
|
const value = get(values, name);
|
136
137
|
field.value = isNil(value) ? '' : value;
|
@@ -13,6 +13,8 @@ import SelectWrapper from './fields/select-wrapper';
|
|
13
13
|
import TextWrapper from './fields/text-wrapper';
|
14
14
|
import CheckBoxWrapper from './fields/checkbox-wrapper';
|
15
15
|
import Label from './fields/label';
|
16
|
+
import TimeZoneSelect from '../../components/time-zone-select';
|
17
|
+
|
16
18
|
import './fields/form-field.scss';
|
17
19
|
|
18
20
|
const TypesMapping = {
|
@@ -21,6 +23,7 @@ const TypesMapping = {
|
|
21
23
|
label: Label,
|
22
24
|
select: SelectWrapper,
|
23
25
|
number: NumberInput,
|
26
|
+
timezone: TimeZoneSelect,
|
24
27
|
checkbox: CheckBoxWrapper,
|
25
28
|
};
|
26
29
|
|
@@ -32,12 +35,14 @@ export default class FormField extends React.PureComponent {
|
|
32
35
|
name: PropTypes.string.isRequired,
|
33
36
|
className: PropTypes.string,
|
34
37
|
type: PropTypes.string,
|
38
|
+
tabIndex: PropTypes.number,
|
35
39
|
}, Col.PropTypes)
|
36
40
|
|
37
41
|
static defaultProps = {
|
38
42
|
label: '',
|
39
43
|
className: '',
|
40
44
|
type: 'text',
|
45
|
+
tabIndex: 0,
|
41
46
|
}
|
42
47
|
|
43
48
|
focus() {
|
@@ -61,7 +66,7 @@ export default class FormField extends React.PureComponent {
|
|
61
66
|
|
62
67
|
render() {
|
63
68
|
const {
|
64
|
-
name, className, autoFocus, type, children, label,
|
69
|
+
name, className, autoFocus, type, children, label, tabIndex,
|
65
70
|
validate: _, formState: __, help: ___, ...otherProps
|
66
71
|
} = getColumnProps(this.props);
|
67
72
|
|
@@ -76,6 +81,7 @@ export default class FormField extends React.PureComponent {
|
|
76
81
|
>
|
77
82
|
<InputTag
|
78
83
|
name={name}
|
84
|
+
tabIndex={tabIndex}
|
79
85
|
autoFocus={autoFocus}
|
80
86
|
ref={this.setRef}
|
81
87
|
value={this.field.value || ''}
|
@@ -8,7 +8,7 @@ import DateTime from '../../date-time';
|
|
8
8
|
@observer
|
9
9
|
export default class DateWrapper extends React.PureComponent {
|
10
10
|
static defaultProps = {
|
11
|
-
format: 'M/
|
11
|
+
format: 'M/d/Y h:iK',
|
12
12
|
}
|
13
13
|
static childContextTypes = { onDropChange: PropTypes.func }
|
14
14
|
@observable isSelecting;
|
@@ -26,9 +26,9 @@ export default class DateWrapper extends React.PureComponent {
|
|
26
26
|
this.isSelecting = active;
|
27
27
|
}
|
28
28
|
|
29
|
-
@action.bound onDateChange(
|
30
|
-
this.dateValue =
|
31
|
-
this.props.onChange({ target: { value
|
29
|
+
@action.bound onDateChange({ target: { value } }) {
|
30
|
+
this.dateValue = value;
|
31
|
+
this.props.onChange({ target: { value } });
|
32
32
|
}
|
33
33
|
|
34
34
|
@action.bound onBlur(ev) {
|
@@ -1,3 +1,12 @@
|
|
1
|
-
import
|
1
|
+
import React from 'react'; // eslint-disable-line no-unused-vars
|
2
|
+
import FAIcon from 'react-fontawesome';
|
3
|
+
import cn from 'classnames';
|
4
|
+
|
5
|
+
const Icon = (props) => {
|
6
|
+
const { className, ...otherProps } = props;
|
7
|
+
return (
|
8
|
+
<FAIcon className={cn('icon', className)} {...otherProps} />
|
9
|
+
);
|
10
|
+
};
|
2
11
|
|
3
12
|
export default Icon;
|
@@ -74,12 +74,17 @@ class Clause extends React.PureComponent {
|
|
74
74
|
this.props.clause.value = ev.target.value;
|
75
75
|
}
|
76
76
|
|
77
|
+
@action.bound
|
78
|
+
setMenuRef(ref) {
|
79
|
+
this.menuRef = ref;
|
80
|
+
}
|
81
|
+
|
77
82
|
render() {
|
78
83
|
const { clause } = this.props;
|
79
84
|
return (
|
80
85
|
<Box direction='row' pad={{ between: 'small' }}>
|
81
86
|
<Menu
|
82
|
-
ref={
|
87
|
+
ref={this.setMenuRef}
|
83
88
|
size='large'
|
84
89
|
pad='small'
|
85
90
|
closeOnClick={false} icon={<ClauseFilter clause={clause} />}
|
@@ -53,7 +53,7 @@ export default class QueryLayer extends React.PureComponent {
|
|
53
53
|
flex='grow'
|
54
54
|
>
|
55
55
|
<Box flex="grow">
|
56
|
-
<Heading tag="h3" margin="none">
|
56
|
+
<Heading tag="h3" margin="none">{title}</Heading>
|
57
57
|
</Box>
|
58
58
|
<Button plain icon={<CloseIcon />} onClick={onClose} />
|
59
59
|
</Box>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { observer } from 'mobx-react';
|
4
|
+
import { computed } from 'mobx';
|
5
|
+
import { get } from 'lodash';
|
6
|
+
import cn from 'classnames';
|
7
|
+
|
8
|
+
import Button from 'grommet/components/Button';
|
9
|
+
import Spinning from 'grommet/components/icons/Spinning';
|
10
|
+
import SaveIcon from 'grommet/components/icons/base/Save';
|
11
|
+
import { BaseModel } from '../models/base';
|
12
|
+
|
13
|
+
|
14
|
+
@observer
|
15
|
+
export default class SaveButton extends React.PureComponent {
|
16
|
+
static propTypes = {
|
17
|
+
busy: PropTypes.bool,
|
18
|
+
model: PropTypes.instanceOf(BaseModel),
|
19
|
+
busyLabel: PropTypes.string,
|
20
|
+
label: PropTypes.string,
|
21
|
+
}
|
22
|
+
|
23
|
+
static defaultProps = {
|
24
|
+
label: 'Save',
|
25
|
+
busyLabel: 'Saving…',
|
26
|
+
}
|
27
|
+
|
28
|
+
@computed get isBusy() {
|
29
|
+
return this.props.busy || get(this.props, 'model.syncInProgress.isUpdate');
|
30
|
+
}
|
31
|
+
|
32
|
+
@computed get label() {
|
33
|
+
return this.isBusy ? this.props.busyLabel : this.props.label;
|
34
|
+
}
|
35
|
+
|
36
|
+
@computed get icon() {
|
37
|
+
return this.isBusy ? <Spinning /> : <SaveIcon />;
|
38
|
+
}
|
39
|
+
|
40
|
+
render() {
|
41
|
+
// eslint-disable-next-line no-unused-vars
|
42
|
+
const { label, icon, props: { busyLabel, busy, model, label: _, ...props } } = this;
|
43
|
+
|
44
|
+
return (
|
45
|
+
<Button
|
46
|
+
{...props}
|
47
|
+
primary
|
48
|
+
className={cn('save-button', this.props.className)}
|
49
|
+
icon={icon}
|
50
|
+
disabled={busy}
|
51
|
+
label={label}
|
52
|
+
/>
|
53
|
+
);
|
54
|
+
}
|
55
|
+
}
|
@@ -16,6 +16,11 @@ import { plugins, defaultPlugin } from './text-editor/plugins';
|
|
16
16
|
import DisplayModeToggle from './text-editor/display-modes';
|
17
17
|
import './text-editor/text-editor.scss';
|
18
18
|
|
19
|
+
const editorInstance = new Editor({
|
20
|
+
plugins,
|
21
|
+
editables: [createEmptyState()],
|
22
|
+
defaultPlugin,
|
23
|
+
});
|
19
24
|
|
20
25
|
@observer
|
21
26
|
export default class TextEditor extends React.PureComponent {
|
@@ -32,11 +37,7 @@ export default class TextEditor extends React.PureComponent {
|
|
32
37
|
componentWillMount() {
|
33
38
|
const content = toJS(this.props.defaultContent);
|
34
39
|
this.content = isEmpty(content) ? createEmptyState() : content;
|
35
|
-
this.
|
36
|
-
plugins,
|
37
|
-
editables: [this.content],
|
38
|
-
defaultPlugin,
|
39
|
-
});
|
40
|
+
editorInstance.trigger.editable.add(this.content);
|
40
41
|
}
|
41
42
|
|
42
43
|
@action.bound
|
@@ -56,21 +57,21 @@ export default class TextEditor extends React.PureComponent {
|
|
56
57
|
>
|
57
58
|
<div className="text-editor">
|
58
59
|
<DisplayModeToggle
|
59
|
-
editor={
|
60
|
+
editor={editorInstance}
|
60
61
|
onSave={this.onSave}
|
61
62
|
>
|
62
63
|
{this.props.children}
|
63
64
|
</DisplayModeToggle>
|
64
65
|
<div className="text-editor-content">
|
65
66
|
<Editable
|
66
|
-
editor={
|
67
|
+
editor={editorInstance}
|
67
68
|
id={this.content.id}
|
68
69
|
onAddImage={this.props.onAddImage}
|
69
70
|
onChange={this.onEditStateChange}
|
70
71
|
/>
|
71
72
|
</div>
|
72
|
-
<Trash editor={
|
73
|
-
<Toolbar editor={
|
73
|
+
<Trash editor={editorInstance}/>
|
74
|
+
<Toolbar editor={editorInstance} />
|
74
75
|
</div>
|
75
76
|
|
76
77
|
</Provider>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import Select from 'grommet/components/Select';
|
4
|
+
import { observer } from 'mobx-react';
|
5
|
+
import { action, computed, observable } from 'mobx';
|
6
|
+
import { filter } from 'lodash';
|
7
|
+
import moment from 'moment-timezone';
|
8
|
+
|
9
|
+
|
10
|
+
@observer
|
11
|
+
export default class TimeZoneSelect extends React.PureComponent {
|
12
|
+
static propTypes = {
|
13
|
+
value: PropTypes.string,
|
14
|
+
onChange: PropTypes.func.isRequired,
|
15
|
+
}
|
16
|
+
|
17
|
+
static defaultProps = {
|
18
|
+
value: '',
|
19
|
+
}
|
20
|
+
|
21
|
+
@observable currentSearch;
|
22
|
+
|
23
|
+
choices = moment.tz.names();
|
24
|
+
|
25
|
+
get value() {
|
26
|
+
return this.props.value || moment.tz.guess();
|
27
|
+
}
|
28
|
+
|
29
|
+
@action.bound filterFn(tz) {
|
30
|
+
return this.currentSearch.test(tz);
|
31
|
+
}
|
32
|
+
|
33
|
+
@computed get names() {
|
34
|
+
if (this.currentSearch) {
|
35
|
+
return filter(moment.tz.names(), this.filterFn);
|
36
|
+
}
|
37
|
+
return this.choices;
|
38
|
+
}
|
39
|
+
|
40
|
+
@action.bound onSearch({ target: { value } }) {
|
41
|
+
this.currentSearch = new RegExp(`^${value}|/${value}`, 'i');
|
42
|
+
}
|
43
|
+
|
44
|
+
@action.bound
|
45
|
+
onChange({ value }) {
|
46
|
+
this.props.onChange({ value, target: { value } });
|
47
|
+
}
|
48
|
+
|
49
|
+
render() {
|
50
|
+
return (
|
51
|
+
<Select
|
52
|
+
placeHolder='None'
|
53
|
+
options={this.names}
|
54
|
+
onSearch={this.onSearch}
|
55
|
+
value={this.value}
|
56
|
+
onChange={this.onChange}
|
57
|
+
/>
|
58
|
+
);
|
59
|
+
}
|
60
|
+
}
|
@@ -73,13 +73,12 @@ export default class Asset extends BaseModel {
|
|
73
73
|
});
|
74
74
|
}
|
75
75
|
|
76
|
-
|
77
76
|
@computed get isDirty() {
|
78
|
-
return Boolean(this.
|
77
|
+
return Boolean(this.file);
|
79
78
|
}
|
80
79
|
|
81
80
|
@computed get exists() {
|
82
|
-
return !!(this.file || this.
|
81
|
+
return !!(this.file || this.file_data);
|
83
82
|
}
|
84
83
|
|
85
84
|
@computed get isImage() {
|
@@ -117,9 +116,15 @@ export default class Asset extends BaseModel {
|
|
117
116
|
}
|
118
117
|
return fetch(`${Config.api_path}${Config.assets_path_prefix}`, options)
|
119
118
|
.then(resp => resp.json())
|
119
|
+
.then(json => this.set(json.data))
|
120
120
|
.then((json) => {
|
121
|
-
this.file
|
121
|
+
if (this.file) {
|
122
|
+
if (this.file.preview && window.URL.revokeObjectURL) {
|
123
|
+
window.URL.revokeObjectURL(this.file.preview);
|
124
|
+
}
|
125
|
+
this.file = undefined;
|
126
|
+
}
|
122
127
|
return json;
|
123
|
-
})
|
128
|
+
});
|
124
129
|
}
|
125
130
|
}
|
data/client/hippo/models/base.js
CHANGED
@@ -1,69 +1,12 @@
|
|
1
1
|
import { Atom, when, reaction } from 'mobx';
|
2
2
|
import ActionCable from 'actioncable';
|
3
|
-
import
|
4
|
-
import { isEmpty, mapValues } from 'lodash';
|
3
|
+
import { BaseModel } from './base';
|
5
4
|
import User from '../user';
|
6
5
|
import Config from '../config';
|
7
|
-
|
8
6
|
import PubSubCableChannel from './pub_sub/channel';
|
7
|
+
import PubSubMap from './pub_sub/map';
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
class PubSubMap {
|
13
|
-
constructor(modelKlass) {
|
14
|
-
this.channel_prefix = modelKlass.identifiedBy;
|
15
|
-
this.map = Object.create(null);
|
16
|
-
}
|
17
|
-
|
18
|
-
channelForId(id) {
|
19
|
-
return `${this.channel_prefix}/${id}`;
|
20
|
-
}
|
21
|
-
|
22
|
-
forModel(model) {
|
23
|
-
const id = model[model.constructor.identifierFieldName];
|
24
|
-
let models = this.map[id];
|
25
|
-
if (!models) {
|
26
|
-
models = [];
|
27
|
-
this.map[id] = models;
|
28
|
-
}
|
29
|
-
return { id, models };
|
30
|
-
}
|
31
|
-
|
32
|
-
onChange(id, data) {
|
33
|
-
const models = this.map[id];
|
34
|
-
if (!isEmpty(models)) {
|
35
|
-
const update = mapValues(data.update, '[1]');
|
36
|
-
for (let i = 0; i < models.length; i += 1) {
|
37
|
-
models[i].set(update);
|
38
|
-
}
|
39
|
-
}
|
40
|
-
}
|
41
|
-
|
42
|
-
observe(model) {
|
43
|
-
const { id, models } = this.forModel(model);
|
44
|
-
if (!models.includes(model)) {
|
45
|
-
models.push(model);
|
46
|
-
if (1 === models.length) {
|
47
|
-
const channel = this.channelForId(id);
|
48
|
-
PubSub.channel.subscribe(channel);
|
49
|
-
}
|
50
|
-
}
|
51
|
-
}
|
52
|
-
|
53
|
-
remove(model) {
|
54
|
-
const { id, models } = this.forModel(model);
|
55
|
-
const indx = models.indexOf(model);
|
56
|
-
invariant(-1 !== indx, 'Asked to remove model from pubsub but it was never observed');
|
57
|
-
if (1 === models.length) {
|
58
|
-
delete this.map[id];
|
59
|
-
PubSub.channel.unsubscribe(this.channelForId(id));
|
60
|
-
} else {
|
61
|
-
models.splice(indx, 1);
|
62
|
-
}
|
63
|
-
}
|
64
|
-
}
|
65
|
-
|
66
|
-
PubSub = {
|
9
|
+
const PubSub = {
|
67
10
|
|
68
11
|
types: new WeakMap(),
|
69
12
|
|
@@ -75,7 +18,9 @@ PubSub = {
|
|
75
18
|
this.mapForModel(model).remove(model);
|
76
19
|
},
|
77
20
|
|
78
|
-
onModelChange(
|
21
|
+
onModelChange(modelId, id, data) {
|
22
|
+
const model = BaseModel.findDerived(modelId);
|
23
|
+
if (!model) { return; }
|
79
24
|
const map = this.types.get(model);
|
80
25
|
if (map) {
|
81
26
|
map.onChange(id, data);
|
@@ -86,7 +31,7 @@ PubSub = {
|
|
86
31
|
const klass = model.constructor;
|
87
32
|
let map = this.types.get(klass);
|
88
33
|
if (!map) {
|
89
|
-
map = new PubSubMap(klass);
|
34
|
+
map = new PubSubMap(klass, this.channel);
|
90
35
|
this.types.set(klass, map);
|
91
36
|
}
|
92
37
|
return map;
|
@@ -95,6 +40,7 @@ PubSub = {
|
|
95
40
|
|
96
41
|
onLoginChange() {
|
97
42
|
if (User.isLoggedIn) {
|
43
|
+
if (PubSub.cable) { return; }
|
98
44
|
const host = Config.api_host.replace(/^http/, 'ws');
|
99
45
|
const url = `${host}${Config.api_path}/cable?token=${Config.access_token}`;
|
100
46
|
ActionCable.startDebugging();
|
@@ -109,14 +55,23 @@ PubSub = {
|
|
109
55
|
|
110
56
|
};
|
111
57
|
|
58
|
+
export default PubSub;
|
59
|
+
|
112
60
|
export function onBoot() {
|
113
|
-
|
114
|
-
() => User.isLoggedIn,
|
115
|
-
PubSub.onLoginChange,
|
116
|
-
{ fireImmediately: true },
|
117
|
-
);
|
61
|
+
PubSub.onLoginChange();
|
118
62
|
}
|
119
63
|
|
64
|
+
when(
|
65
|
+
() => Config.isIntialized,
|
66
|
+
() => {
|
67
|
+
reaction(
|
68
|
+
() => User.isLoggedIn,
|
69
|
+
PubSub.onLoginChange,
|
70
|
+
{ fireImmediately: true },
|
71
|
+
);
|
72
|
+
},
|
73
|
+
);
|
74
|
+
|
120
75
|
export function stop() {
|
121
76
|
PubSub.kill();
|
122
77
|
}
|