hippo-fw 0.9.6 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
}
|