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,12 +1,12 @@
|
|
1
1
|
import { omit } from 'lodash';
|
2
|
-
import { action } from 'mobx';
|
2
|
+
import { action, observable } from 'mobx';
|
3
3
|
import { logger } from '../../lib/util';
|
4
|
-
import { BaseModel } from '../base';
|
5
4
|
|
6
5
|
const CHANNEL_SPLITTER = new RegExp('^(.*):(.*)/([^/]+)$');
|
7
6
|
|
8
7
|
export default class PubSubCableChannel {
|
9
8
|
constructor(pub_sub) {
|
9
|
+
this.callbacks = observable.map();
|
10
10
|
this.channel = pub_sub.cable.subscriptions.create(
|
11
11
|
'Hippo::API::PubSub', this,
|
12
12
|
);
|
@@ -14,14 +14,31 @@ export default class PubSubCableChannel {
|
|
14
14
|
this.pub_sub = pub_sub;
|
15
15
|
}
|
16
16
|
|
17
|
-
subscribe(channel) {
|
17
|
+
subscribe(channel, cb) {
|
18
18
|
logger.info(`[pubsub] subscribe to: ${channel}`);
|
19
|
+
if (cb) {
|
20
|
+
const callbacks = this.callbacks.get(channel) || observable([]);
|
21
|
+
callbacks.push(cb);
|
22
|
+
this.callbacks.set(channel, callbacks);
|
23
|
+
}
|
19
24
|
this.channel.perform('on', { channel });
|
20
25
|
}
|
21
26
|
|
22
|
-
unsubscribe(channel) {
|
27
|
+
unsubscribe(channel, cb) {
|
23
28
|
logger.info(`[pubsub] unsubscribe from: ${channel}`);
|
24
|
-
|
29
|
+
if (cb) {
|
30
|
+
const callbacks = this.callbacks.get(channel);
|
31
|
+
if (callbacks) {
|
32
|
+
callbacks.remove(cb);
|
33
|
+
if (callbacks.length) {
|
34
|
+
this.callbacks.set(channel, callbacks);
|
35
|
+
} else {
|
36
|
+
this.callbacks.delete(channel);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
} else {
|
40
|
+
this.channel.perform('off', { channel });
|
41
|
+
}
|
25
42
|
}
|
26
43
|
|
27
44
|
@action.bound
|
@@ -29,8 +46,11 @@ export default class PubSubCableChannel {
|
|
29
46
|
const [_, __, modelId, id] = Array.from(
|
30
47
|
data.channel.match(CHANNEL_SPLITTER),
|
31
48
|
);
|
32
|
-
|
33
|
-
|
34
|
-
|
49
|
+
const callbacks = this.callbacks.get(`${modelId}/${id}`);
|
50
|
+
if (callbacks) {
|
51
|
+
callbacks.forEach(c => c(data));
|
52
|
+
} else {
|
53
|
+
this.pub_sub.onModelChange(modelId, id, omit(data, 'channel'));
|
54
|
+
}
|
35
55
|
}
|
36
56
|
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import invariant from 'invariant';
|
2
|
+
import { isEmpty, mapValues } from 'lodash';
|
3
|
+
|
4
|
+
export default class PubSubMap {
|
5
|
+
constructor(modelKlass, channel) {
|
6
|
+
this.channel = channel;
|
7
|
+
this.channel_prefix = modelKlass.identifiedBy;
|
8
|
+
this.map = Object.create(null);
|
9
|
+
}
|
10
|
+
|
11
|
+
channelForId(id) {
|
12
|
+
return `${this.channel_prefix}/${id}`;
|
13
|
+
}
|
14
|
+
|
15
|
+
forModel(model) {
|
16
|
+
const id = model[model.constructor.identifierFieldName];
|
17
|
+
let models = this.map[id];
|
18
|
+
if (!models) {
|
19
|
+
models = [];
|
20
|
+
this.map[id] = models;
|
21
|
+
}
|
22
|
+
return { id, models };
|
23
|
+
}
|
24
|
+
|
25
|
+
onChange(id, data) {
|
26
|
+
const models = this.map[id];
|
27
|
+
if (!isEmpty(models)) {
|
28
|
+
const update = mapValues(data.update, '[1]');
|
29
|
+
for (let i = 0; i < models.length; i += 1) {
|
30
|
+
models[i].set(update);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
observe(model) {
|
36
|
+
const { id, models } = this.forModel(model);
|
37
|
+
if (!models.includes(model)) {
|
38
|
+
models.push(model);
|
39
|
+
if (1 === models.length) {
|
40
|
+
const channel = this.channelForId(id);
|
41
|
+
this.channel.subscribe(channel);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
remove(model) {
|
47
|
+
const { id, models } = this.forModel(model);
|
48
|
+
const indx = models.indexOf(model);
|
49
|
+
invariant(-1 !== indx, 'Asked to remove model from pubsub but it was never observed');
|
50
|
+
if (1 === models.length) {
|
51
|
+
delete this.map[id];
|
52
|
+
this.channel.unsubscribe(this.channelForId(id));
|
53
|
+
} else {
|
54
|
+
models.splice(indx, 1);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import {
|
2
|
-
isEmpty, isNil, extend, map, bindAll, inRange, find, range,
|
2
|
+
isEmpty, isNil, extend, map, bindAll, inRange, find, range, isUndefined,
|
3
3
|
} from 'lodash';
|
4
4
|
import { reaction, observe, toJS } from 'mobx';
|
5
5
|
|
@@ -32,13 +32,14 @@ export default class ArrayResult extends Result {
|
|
32
32
|
);
|
33
33
|
}
|
34
34
|
|
35
|
-
@computed get
|
35
|
+
@computed get fingerprint() {
|
36
36
|
return [
|
37
|
+
this.query.fingerprint,
|
37
38
|
this.rows.length,
|
38
39
|
(this.sortField ? this.sortField.id : 'none'),
|
39
40
|
this.sortAscending,
|
40
41
|
this.rowUpdateCount,
|
41
|
-
].join('
|
42
|
+
].join(';');
|
42
43
|
}
|
43
44
|
|
44
45
|
insertRow() {
|
@@ -73,7 +74,7 @@ export default class ArrayResult extends Result {
|
|
73
74
|
newValue.whenComplete(() => {
|
74
75
|
const row = this.rows[index];
|
75
76
|
this.query.info.loadableFields.forEach((f) => {
|
76
|
-
if (model[f.id]) { row[f.dataIndex] = model[f.id]; }
|
77
|
+
if (!isUndefined(model[f.id])) { row[f.dataIndex] = model[f.id]; }
|
77
78
|
});
|
78
79
|
this.rowUpdateCount += 1;
|
79
80
|
}, 99);
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { get, includes } from 'lodash';
|
2
|
+
import classname from 'classnames';
|
2
3
|
import { titleize } from '../../lib/util';
|
3
4
|
import {
|
4
5
|
BaseModel, identifiedBy, session, belongsTo, identifier, computed, observable,
|
@@ -17,15 +18,14 @@ export default class Field extends BaseModel {
|
|
17
18
|
@session loadable = true;
|
18
19
|
@session queryable = true;
|
19
20
|
@session sortable = true;
|
20
|
-
@session
|
21
|
+
@session _className = '';
|
22
|
+
@session align = 'left';
|
21
23
|
@session dataType = 'string'
|
22
24
|
@session cellRenderer;
|
23
25
|
@session defaultValue;
|
24
26
|
@session fetchIndex;
|
25
27
|
@session sortBy;
|
26
28
|
|
27
|
-
@observable format;
|
28
|
-
@observable component;
|
29
29
|
@observable onColumnClick;
|
30
30
|
|
31
31
|
@belongsTo query;
|
@@ -46,6 +46,22 @@ export default class Field extends BaseModel {
|
|
46
46
|
return includes(['number'], this.queryType);
|
47
47
|
}
|
48
48
|
|
49
|
+
set className(c) {
|
50
|
+
this._className = c;
|
51
|
+
}
|
52
|
+
|
53
|
+
@computed get headerClassName() {
|
54
|
+
return classname('header', this.className);
|
55
|
+
}
|
56
|
+
|
57
|
+
@computed get className() {
|
58
|
+
return classname(this._className, {
|
59
|
+
r: 'right' === this.align,
|
60
|
+
l: 'left' === this.align,
|
61
|
+
c: 'center' === this.align,
|
62
|
+
});
|
63
|
+
}
|
64
|
+
|
49
65
|
@computed get dataIndex() {
|
50
66
|
if (!this.loadable) { return null; }
|
51
67
|
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import { merge } from 'lodash';
|
2
|
+
import { when } from 'mobx';
|
2
3
|
import {
|
3
4
|
BaseModel, identifiedBy, identifier, belongsTo, field, computed,
|
4
5
|
} from './base';
|
5
6
|
import Sync from './sync';
|
6
|
-
|
7
|
+
import Config from '../config';
|
7
8
|
import Asset from './asset';
|
8
9
|
|
9
10
|
@identifiedBy('hippo/system-settings')
|
@@ -21,4 +22,18 @@ export default class SystemSettings extends BaseModel {
|
|
21
22
|
@computed get syncUrl() {
|
22
23
|
return this.constructor.syncUrl;
|
23
24
|
}
|
25
|
+
|
26
|
+
get syncData() {
|
27
|
+
return this.serialize();
|
28
|
+
}
|
29
|
+
|
30
|
+
set syncData(data) {
|
31
|
+
super.syncData = data;
|
32
|
+
if (this.logo && this.logo.isDirty) {
|
33
|
+
when(
|
34
|
+
() => !this.logo.isDirty,
|
35
|
+
() => { Config.logo = this.logo.file_data; },
|
36
|
+
);
|
37
|
+
}
|
38
|
+
}
|
24
39
|
}
|
@@ -4,18 +4,19 @@ import {
|
|
4
4
|
} from './base';
|
5
5
|
import Config from '../config';
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
});
|
7
|
+
|
8
|
+
const CACHED = observable.box();
|
10
9
|
|
11
10
|
@identifiedBy('hippo/tenant')
|
12
11
|
export default class Tenant extends BaseModel {
|
13
12
|
@computed static get current() {
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
let tenant = CACHED.get();
|
14
|
+
if (!tenant) {
|
15
|
+
tenant = new Tenant();
|
16
|
+
CACHED.set(tenant);
|
17
|
+
tenant.fetch({ query: 'current' });
|
17
18
|
}
|
18
|
-
return
|
19
|
+
return tenant;
|
19
20
|
}
|
20
21
|
|
21
22
|
@identifier id;
|
@@ -3,17 +3,16 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import { observable, action } from 'mobx';
|
4
4
|
import { observer } from 'mobx-react';
|
5
5
|
import { map, compact, invoke } from 'lodash';
|
6
|
+
import { Row, Col } from 'react-flexbox-grid';
|
6
7
|
import Heading from 'grommet/components/Heading';
|
7
8
|
import Header from 'grommet/components/Header';
|
8
|
-
import Button from 'grommet/components/Button';
|
9
|
-
import SaveIcon from 'grommet/components/icons/base/Save';
|
10
|
-
|
11
|
-
import Screen from 'hippo/components/screen';
|
12
|
-
import Asset from 'hippo/components/asset';
|
13
|
-
import Settings from 'hippo/models/system-setting';
|
14
9
|
|
15
|
-
import
|
16
|
-
import
|
10
|
+
import Screen from '../components/screen';
|
11
|
+
import Asset from '../components/asset';
|
12
|
+
import Settings from '../models/system-setting';
|
13
|
+
import Warning from '../components/warning-notification';
|
14
|
+
import SaveButton from '../components/save-button';
|
15
|
+
import ScreenInstance from '../screens/instance';
|
17
16
|
import Extensions from '../extensions';
|
18
17
|
import MailerConfig from './system-settings/mailer-config';
|
19
18
|
import TenantSettings from './system-settings/tenant';
|
@@ -68,14 +67,13 @@ export default class SystemSettings extends React.PureComponent {
|
|
68
67
|
return (
|
69
68
|
<Screen {...this.props}>
|
70
69
|
<Header fixed>
|
71
|
-
<
|
72
|
-
|
73
|
-
icon={<SaveIcon />}
|
74
|
-
label='Save'
|
70
|
+
<SaveButton
|
71
|
+
model={this.settings}
|
75
72
|
onClick={this.onSave}
|
76
73
|
/>
|
77
74
|
</Header>
|
78
75
|
<Heading>{this.props.screen.definition.title}</Heading>
|
76
|
+
<Warning message={this.settings.errorMessage} />
|
79
77
|
<TenantSettings ref={this.setTenantRef} />
|
80
78
|
<Heading tag="h3">Images</Heading>
|
81
79
|
<Row>
|
@@ -3,15 +3,15 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import { Row, Col } from 'react-flexbox-grid';
|
4
4
|
|
5
5
|
import { observer } from 'mobx-react';
|
6
|
-
import { action } from 'mobx';
|
6
|
+
import { observable, action } from 'mobx';
|
7
7
|
|
8
8
|
import Box from 'grommet/components/Box';
|
9
9
|
import Button from 'grommet/components/Button';
|
10
|
-
import Warning from '
|
10
|
+
import Warning from '../../components/warning-notification';
|
11
|
+
import SaveButton from '../../components/save-button';
|
12
|
+
import Query from '../../models/query';
|
11
13
|
|
12
|
-
import
|
13
|
-
|
14
|
-
import { Form, Field, FormState, nonBlank, validEmail, booleanValue } from 'hippo/components/form';
|
14
|
+
import { Form, Field, FormState, nonBlank, validEmail, booleanValue } from '../../components/form';
|
15
15
|
|
16
16
|
@observer
|
17
17
|
export default class EditForm extends React.PureComponent {
|
@@ -22,7 +22,7 @@ export default class EditForm extends React.PureComponent {
|
|
22
22
|
}
|
23
23
|
|
24
24
|
static desiredHeight = 300
|
25
|
-
|
25
|
+
@observable errorMessage;
|
26
26
|
formState = new FormState();
|
27
27
|
|
28
28
|
constructor(props) {
|
@@ -43,7 +43,7 @@ export default class EditForm extends React.PureComponent {
|
|
43
43
|
@action.bound
|
44
44
|
onSaved(user) {
|
45
45
|
if (user.errors) {
|
46
|
-
this.errorMessage = user.
|
46
|
+
this.errorMessage = user.errorMessage;
|
47
47
|
} else {
|
48
48
|
this.props.onComplete();
|
49
49
|
}
|
@@ -70,12 +70,11 @@ export default class EditForm extends React.PureComponent {
|
|
70
70
|
<Field md={4} xs={6} type="password" name="password" />
|
71
71
|
<Field md={4} xs={6} type="checkbox" name="is_admin" validate={booleanValue} />
|
72
72
|
<Col md={4} xs={6}>
|
73
|
-
<Box direction="row">
|
73
|
+
<Box direction="row" justify="between">
|
74
74
|
<Button label="Cancel" onClick={this.onCancel} accent />
|
75
|
-
<
|
76
|
-
label="Save"
|
75
|
+
<SaveButton
|
77
76
|
onClick={this.formState.isValid ? this.onSave : null}
|
78
|
-
|
77
|
+
model={this.user}
|
79
78
|
/>
|
80
79
|
</Box>
|
81
80
|
</Col>
|
@@ -70,19 +70,22 @@ class Workspace extends React.Component {
|
|
70
70
|
<LoginDialog />
|
71
71
|
<Sidebar
|
72
72
|
styles={{ sidebar: { zIndex: 5 } }}
|
73
|
-
sidebar={<Menu
|
73
|
+
sidebar={<Menu
|
74
|
+
isOpen={this.sidebarOpen}
|
75
|
+
isDocked={this.sidebarDocked}
|
76
|
+
onCloseMenu={this.toggleSidebarDocked}
|
77
|
+
onDockToggle={this.toggleSidebarDocked}
|
78
|
+
/>}
|
74
79
|
open={this.sidebarOpen}
|
75
80
|
docked={this.sidebarDocked}
|
76
81
|
onSetOpen={this.onSetSidebarOpen}
|
77
82
|
>
|
78
|
-
|
79
83
|
<Button
|
80
84
|
primary
|
81
85
|
icon={<CirclePlayIcon />}
|
82
86
|
onClick={this.toggleSidebarDocked}
|
83
87
|
className={cn('sidebar-toggle', { 'is-open': this.sidebarOpen })}
|
84
88
|
/>
|
85
|
-
|
86
89
|
<Switch>
|
87
90
|
<Route name='screen' path="/:screenId/:identifier?" component={Screen} />
|
88
91
|
<Route component={NoMatch} />
|
@@ -1,12 +1,9 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
|
3
2
|
import PropTypes from 'prop-types';
|
4
|
-
|
5
3
|
import { action } from 'mobx';
|
6
4
|
import { observer } from 'mobx-react';
|
7
|
-
import Icon from 'hippo/components/icon';
|
8
|
-
|
9
5
|
import Anchor from 'grommet/components/Anchor';
|
6
|
+
import Icon from '../components/icon';
|
10
7
|
|
11
8
|
@observer
|
12
9
|
export default class MenuOption extends React.Component {
|
@@ -31,8 +28,8 @@ export default class MenuOption extends React.Component {
|
|
31
28
|
const { screen } = this.props;
|
32
29
|
return (
|
33
30
|
<Anchor path={`/${screen.id}/`} onClick={this.activateScreen}>
|
34
|
-
{screen.title}
|
35
31
|
<Icon name={screen.icon} />
|
32
|
+
{screen.title}
|
36
33
|
</Anchor>
|
37
34
|
);
|
38
35
|
}
|
@@ -5,9 +5,12 @@ import { isEmpty, get } from 'lodash';
|
|
5
5
|
import PropTypes from 'prop-types';
|
6
6
|
import Box from 'grommet/components/Box';
|
7
7
|
import Sidebar from 'grommet/components/Sidebar';
|
8
|
+
import Button from 'grommet/components/Button';
|
9
|
+
import CloseIcon from 'grommet/components/icons/base/Close';
|
8
10
|
import Header from 'grommet/components/Header';
|
9
11
|
import Anchor from 'grommet/components/Anchor';
|
10
12
|
import Menu from 'grommet/components/Menu';
|
13
|
+
import Icon from '../components/icon';
|
11
14
|
import Group from './menu-group';
|
12
15
|
import Screens from '../screens';
|
13
16
|
import MenuOption from './menu-option';
|
@@ -34,6 +37,7 @@ class Logout extends React.PureComponent {
|
|
34
37
|
return (
|
35
38
|
<Menu direction="column" align="start" justify="between" primary>
|
36
39
|
<Anchor label="Log Out" onClick={this.onLogoutClick}>
|
40
|
+
<Icon name="sign-out" />
|
37
41
|
Log Out
|
38
42
|
</Anchor>
|
39
43
|
</Menu>
|
@@ -41,7 +45,6 @@ class Logout extends React.PureComponent {
|
|
41
45
|
}
|
42
46
|
}
|
43
47
|
|
44
|
-
|
45
48
|
const Logo = observer(() => {
|
46
49
|
if (!get(Config, 'logo.thumbnail')) {
|
47
50
|
if (!isEmpty(Config.product_name)) {
|
@@ -65,14 +68,23 @@ export default class WorkspaceMenu extends React.PureComponent {
|
|
65
68
|
);
|
66
69
|
}
|
67
70
|
|
71
|
+
renderClose() {
|
72
|
+
if (this.props.isOpen && !this.props.isDocked) {
|
73
|
+
return <Button icon={<CloseIcon />} onClick={this.props.onCloseMenu} plain />;
|
74
|
+
}
|
75
|
+
return null;
|
76
|
+
}
|
77
|
+
|
68
78
|
render() {
|
69
79
|
return (
|
70
80
|
<Sidebar
|
71
81
|
full size="small" separator="right"
|
72
82
|
colorIndex="brand"
|
83
|
+
className="screen-selection-menu"
|
73
84
|
>
|
74
85
|
<Header justify="between" size="large" pad={{ horizontal: 'medium' }}>
|
75
86
|
<Logo />
|
87
|
+
{this.renderClose()}
|
76
88
|
</Header>
|
77
89
|
{Screens.activeGroups.map(g => <Group key={g.id} group={g} />)}
|
78
90
|
{this.renderUnGrouped()}
|