Skip to content

Commit 14380dd

Browse files
feat(Dashboards): Switches to grid view (LLC-15) (#1522)
1 parent 37978ef commit 14380dd

File tree

25 files changed

+854
-212
lines changed

25 files changed

+854
-212
lines changed

Diff for: lib/constants/dashboard.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
export const JWT_SECURED = 'JWT_SECURED';
22
export const ANY = 'ANY';
33
export const OFF = 'OFF';
4+
5+
export const BLANK_DASHBOARD = 'blankDashboard';
6+
export const STREAM_STARTER = 'streamStarter';
7+
export const GETTING_STARTED = 'gettingStarted';

Diff for: lib/models/dashboard.js

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export const schema = new mongoose.Schema({
7171
owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true },
7272
isPublic: { type: Boolean, default: false },
7373
hasBeenMigrated: { type: Boolean, default: false },
74+
type: { type: String },
7475
});
7576

7677
schema.readScopes = keys(scopes.USER_SCOPES);

Diff for: ui/src/components/DropDownMenu/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ class DropDownMenu extends Component {
5353
{this.props.button}
5454
</span>
5555
{this.state.isOpen &&
56-
<ul className="dropdown-menu">
57-
{React.Children.map(this.props.children, child => (
58-
<li>{child}</li>
56+
<ul className={`dropdown-menu ${this.props.customClass}`}>
57+
{React.Children.map(this.props.children, (child, index) => (
58+
<li key={index}>{child}</li>
5959
))}
6060
</ul>
6161
}

Diff for: ui/src/components/IconButton/CopyIconButton.js

+25-15
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import ConfirmModal from 'ui/components/Modal/ConfirmModal';
77
* @param {boolean} _.white - white button? default is btn-inverse
88
* @param {() => void} _.onClickConfirm
99
*/
10-
const CopyIconButton = ({ message, white, onClickConfirm }) => {
10+
const CopyIconButton = ({ message, white, onClickConfirm, textFormat }) => {
1111
const [isModalOpen, setModalOpen] = useState(false);
1212

1313
const closeModal = useCallback(() => setModalOpen(false));
@@ -19,26 +19,36 @@ const CopyIconButton = ({ message, white, onClickConfirm }) => {
1919
onClickConfirm();
2020
setModalOpen(false);
2121
});
22+
const renderConfirmModal = (
23+
<ConfirmModal
24+
isOpen={isModalOpen}
25+
title="Confirm copy"
26+
message={<span>{message}</span>}
27+
onConfirm={onConfirm}
28+
onCancel={closeModal} />
29+
);
2230

2331
const className = white === true ?
2432
'btn btn-default btn-sm flat-btn flat-white' :
2533
'btn btn-sm btn-inverse';
2634

2735
return (
28-
<button
29-
className={className}
30-
title="Copy"
31-
onClick={openModal}
32-
style={{ width: '33px' }}>
33-
<i className="icon ion-ios-copy" />
34-
35-
<ConfirmModal
36-
isOpen={isModalOpen}
37-
title="Confirm copy"
38-
message={<span>{message}</span>}
39-
onConfirm={onConfirm}
40-
onCancel={closeModal} />
41-
</button>
36+
textFormat ? (
37+
<span
38+
onClick={openModal}>
39+
Duplicate
40+
{renderConfirmModal}
41+
</span>
42+
) : (
43+
<button
44+
className={className}
45+
title="Copy"
46+
onClick={openModal}
47+
style={{ width: '33px' }}>
48+
<i className="icon ion-ios-copy" />
49+
{renderConfirmModal}
50+
</button>
51+
)
4252
);
4353
};
4454

Diff for: ui/src/containers/Dashboard/index.js

+96-106
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,29 @@
11
import React, { Component } from 'react';
2+
import { isLoadingSelector } from 'ui/redux/modules/pagination';
23
import PropTypes from 'prop-types';
34
import * as _ from 'lodash';
45
import Scroll from 'react-scroll';
56
import { connect } from 'react-redux';
6-
import { actions as routerActions } from 'redux-router5';
7+
import { actions, routeNodeSelector } from 'redux-router5';
78
import { withProps, compose, lifecycle, withHandlers, mapProps } from 'recompose';
89
import { Map, List, is } from 'immutable';
910
import Input from 'ui/components/Material/Input';
1011
import Spinner from 'ui/components/Spinner';
11-
import CopyIconButton from 'ui/components/IconButton/CopyIconButton';
1212
import DashboardGrid from 'ui/containers/DashboardGrid';
1313
import DashboardSharing from 'ui/containers/DashboardSharing';
14-
import DeleteButton from 'ui/containers/DeleteButton';
15-
import Owner from 'ui/containers/Owner';
16-
import PrivacyToggleButton from 'ui/containers/PrivacyToggleButton';
1714
import WidgetVisualiseCreator from 'ui/containers/WidgetVisualiseCreator';
18-
import { withModel } from 'ui/utils/hocs';
19-
import { COPY_DASHBOARD } from 'ui/redux/modules/dashboard/copyDashboard';
20-
import { getVisualisationsFromDashboard } from 'ui/redux/modules/dashboard/selectors';
15+
import { withModels, withModel } from 'ui/utils/hocs';
2116
import { loggedInUserId } from 'ui/redux/selectors';
2217
import { activeOrgIdSelector } from 'ui/redux/modules/router';
23-
import { EditInputWrapper, Editor, EditWrapper } from 'ui/containers/Dashboard/styled';
24-
25-
const schema = 'dashboard';
18+
import { EditWrapper } from 'ui/containers/Dashboard/styled';
2619

2720
class Dashboard extends Component {
2821
static propTypes = {
2922
model: PropTypes.instanceOf(Map),
30-
navigateTo: PropTypes.func,
3123
updateModel: PropTypes.func,
3224
saveModel: PropTypes.func,
3325
setMetadata: PropTypes.func,
3426
getMetadata: PropTypes.func,
35-
doCopyDashboard: PropTypes.func,
3627
};
3728

3829
static defaultProps = {
@@ -128,135 +119,134 @@ class Dashboard extends Component {
128119
};
129120

130121
render() {
131-
const { model, organisationId, navigateTo, doCopyDashboard } = this.props;
122+
const { model, backToDashboard } = this.props;
132123

133124
if (!model.get('_id')) {
134125
return <Spinner />;
135126
}
136127

137128
return (
138-
<div className="row">
139-
<Editor>
140-
<EditWrapper>
141-
<EditInputWrapper>
129+
<div>
130+
<header id="topbar">
131+
<div className="heading heading-light">
132+
<span className="pull-right open_panel_btn" >
133+
<a
134+
onClick={this.onClickAddWidget}
135+
className="btn btn-primary btn-sm">
136+
<i className="ion ion-stats-bars" /> Add widget
137+
</a>
138+
139+
<button
140+
className="btn btn-default btn-sm flat-btn flat-white"
141+
title="Share"
142+
onClick={this.toggleSharing}
143+
style={{
144+
backgroundColor: this.props.getMetadata('isSharing') ? '#F5AB35' : null,
145+
color: this.props.getMetadata('isSharing') ? 'white' : null,
146+
marginRight: 0,
147+
}}>
148+
<i className="icon ion-android-share-alt" />
149+
</button>
150+
</span>
151+
<EditWrapper>
152+
<a
153+
onClick={backToDashboard} >
154+
<i className="icon ion-chevron-left" />
155+
</a>
142156
<Input
143157
type="text"
144158
name="Title"
145-
label="Enter title"
146159
value={model.get('title', ' ')}
147160
onChange={this.onTitleChange}
148-
style={{ fontSize: '13px' }} />
149-
</EditInputWrapper>
150-
151-
&nbsp;&nbsp;
152-
153-
<a
154-
onClick={this.onClickAddWidget}
155-
className="btn btn-default btn-sm flat-btn flat-white">
156-
<i className="ion ion-stats-bars" /> Add widget
157-
</a>
158-
159-
<PrivacyToggleButton
160-
white
161-
id={model.get('_id')}
162-
schema={schema} />
163-
164-
<CopyIconButton
165-
message="This will copy the dashboard and visualisations. Are you sure?"
166-
white
167-
onClickConfirm={doCopyDashboard} />
168-
169-
<DeleteButton
170-
white
171-
id={model.get('_id')}
172-
schema={schema}
173-
onDeletedModel={() =>
174-
navigateTo('organisation.data.dashboards', { organisationId })
175-
} />
176-
177-
<button
178-
className="btn btn-default btn-sm flat-btn flat-white"
179-
title="Share"
180-
onClick={this.toggleSharing}
181-
style={{
182-
backgroundColor: this.props.getMetadata('isSharing') ? '#F5AB35' : null,
183-
color: this.props.getMetadata('isSharing') ? 'white' : null
184-
}}>
185-
<i className="icon ion-android-share-alt" />
186-
</button>
187-
188-
<span style={{ marginLeft: 'auto' }}>
189-
<Owner model={model} />
190-
</span>
191-
</EditWrapper>
161+
style={{ fontSize: '18px', color: '#929292', padding: 0 }} />
162+
</EditWrapper>
163+
</div>
164+
</header>
165+
<div className="row">
192166
{this.props.getMetadata('isSharing') &&
193-
<div>
167+
<div className="col-md-12">
194168
<DashboardSharing
195169
shareable={model.get('shareable', new List())}
196170
id={model.get('_id')} />
197171
</div>
198172
}
199-
</Editor>
200173

201-
<div className="clearfix" />
174+
<div className="clearfix" />
202175

203-
<WidgetVisualiseCreator
204-
isOpened={this.state.widgetModalOpen}
205-
model={model}
206-
onClickClose={() => this.toggleWidgetModal()}
207-
onChangeVisualisation={this.createPopulatedWidget} />
176+
<WidgetVisualiseCreator
177+
isOpened={this.state.widgetModalOpen}
178+
model={model}
179+
onClickClose={() => this.toggleWidgetModal()}
180+
onChangeVisualisation={this.createPopulatedWidget} />
208181

209-
<DashboardGrid
210-
widgets={model.get('widgets')}
211-
onChange={this.onChangeWidgets}
212-
onChangeTitle={this.onChangeWidgetTitle}
213-
onChangeVisualisation={this.onChangeWidgetVisualisation} />
182+
<DashboardGrid
183+
widgets={model.get('widgets')}
184+
onChange={this.onChangeWidgets}
185+
onChangeTitle={this.onChangeWidgetTitle}
186+
onChangeVisualisation={this.onChangeWidgetVisualisation} />
187+
</div>
214188
</div>
215189
);
216190
}
217191
}
218192

219193
export default compose(
220-
withProps(({ params, id }) =>
221-
({
222-
schema,
223-
id: id || params.dashboardId
194+
connect(
195+
state => ({
196+
isLoading: isLoadingSelector('dashboard', new Map())(state),
197+
userId: loggedInUserId(state),
198+
route: routeNodeSelector('organisation.dashboards')(state).route,
199+
organisation: activeOrgIdSelector(state)
200+
}),
201+
{ navigateTo: actions.navigateTo }
202+
),
203+
withProps({
204+
schema: 'dashboard',
205+
filter: new Map(),
206+
first: 300,
207+
}),
208+
withModels,
209+
withProps(
210+
({ route }) => ({
211+
id: route.name === 'organisation.data.dashboards.add' ? undefined : route.params.dashboardId
224212
})
225213
),
226214
withModel,
215+
withProps(
216+
({
217+
id,
218+
models,
219+
model
220+
}) => {
221+
if (model.size === 0 && id) {
222+
return ({
223+
modelsWithModel: models
224+
});
225+
}
226+
227+
return ({
228+
modelsWithModel: !id || models.has(id) ? models : models.reverse().set(id, model).reverse()
229+
});
230+
}
231+
),
227232
lifecycle({
228233
componentDidUpdate(previousProps) {
229-
if (this.props.model.get('widgets').size > previousProps.model.get('widgets').size && window) {
234+
if (
235+
this.props.model.get('widgets') && this.props.model.get('widgets').size && previousProps.size &&
236+
this.props.model.get('widgets').size > previousProps.model.get('widgets').size && window
237+
) {
230238
const scroll = Scroll.animateScroll;
231239
scroll.scrollToBottom({ smooth: true });
232240
}
233241
}
234242
}),
235-
connect(
236-
state => ({
237-
organisationId: activeOrgIdSelector(state),
238-
userId: loggedInUserId(state),
239-
state,
240-
}),
241-
dispatch => ({
242-
navigateTo: () => dispatch(routerActions.navigateTo),
243-
copyDashboard: ({ dashboard, visualisations, organisationId, userId }) => dispatch({
244-
type: COPY_DASHBOARD,
245-
dispatch,
246-
dashboard,
247-
visualisations,
248-
organisationId,
249-
userId,
250-
}),
251-
}),
252-
),
253243
withHandlers({
254-
doCopyDashboard: ({ model, state, organisationId, copyDashboard, userId }) => () => copyDashboard({
255-
dashboard: model,
256-
visualisations: getVisualisationsFromDashboard(model.get('_id'))(state),
257-
organisationId,
258-
userId,
259-
})
244+
backToDashboard: ({ route, navigateTo }) => () => {
245+
const organisationId = route.params.organisationId;
246+
navigateTo('organisation.data.dashboards', {
247+
organisationId
248+
});
249+
}
260250
}),
261-
mapProps(original => _.pick(original, ['model', 'navigateTo', 'updateModel', 'saveModel', 'setMetadata', 'getMetadata', 'doCopyDashboard'])),
251+
mapProps(original => _.pick(original, ['model', 'updateModel', 'saveModel', 'setMetadata', 'getMetadata', 'backToDashboard'])),
262252
)(Dashboard);

Diff for: ui/src/containers/Dashboard/styled/index.js

+12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ export const EditWrapper = styled.div`
1212
.btn {
1313
margin-bottom: auto;
1414
}
15+
16+
&:global(.btn) {
17+
margin-bottom: auto;
18+
}
19+
20+
div {
21+
padding: 0 0 0 15px;
22+
font-weight: 300;
23+
color: #929292;
24+
25+
font-family: Arial, sans-serif;
26+
}
1527
`;
1628

1729
export const EditInputWrapper = styled.div`
2.68 KB
Loading
4.12 KB
Loading

Diff for: ui/src/containers/DashboardCard/assets/private.png

360 Bytes
Loading
4.19 KB
Loading

0 commit comments

Comments
 (0)