Skip to content

Commit

Permalink
feat(Dashboards): Switches to grid view (LLC-15) (#1522)
Browse files Browse the repository at this point in the history
  • Loading branch information
vladislav-999 authored Apr 23, 2020
1 parent 37978ef commit 14380dd
Show file tree
Hide file tree
Showing 25 changed files with 854 additions and 212 deletions.
4 changes: 4 additions & 0 deletions lib/constants/dashboard.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export const JWT_SECURED = 'JWT_SECURED';
export const ANY = 'ANY';
export const OFF = 'OFF';

export const BLANK_DASHBOARD = 'blankDashboard';
export const STREAM_STARTER = 'streamStarter';
export const GETTING_STARTED = 'gettingStarted';
1 change: 1 addition & 0 deletions lib/models/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const schema = new mongoose.Schema({
owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true },
isPublic: { type: Boolean, default: false },
hasBeenMigrated: { type: Boolean, default: false },
type: { type: String },
});

schema.readScopes = keys(scopes.USER_SCOPES);
Expand Down
6 changes: 3 additions & 3 deletions ui/src/components/DropDownMenu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ class DropDownMenu extends Component {
{this.props.button}
</span>
{this.state.isOpen &&
<ul className="dropdown-menu">
{React.Children.map(this.props.children, child => (
<li>{child}</li>
<ul className={`dropdown-menu ${this.props.customClass}`}>
{React.Children.map(this.props.children, (child, index) => (
<li key={index}>{child}</li>
))}
</ul>
}
Expand Down
40 changes: 25 additions & 15 deletions ui/src/components/IconButton/CopyIconButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ConfirmModal from 'ui/components/Modal/ConfirmModal';
* @param {boolean} _.white - white button? default is btn-inverse
* @param {() => void} _.onClickConfirm
*/
const CopyIconButton = ({ message, white, onClickConfirm }) => {
const CopyIconButton = ({ message, white, onClickConfirm, textFormat }) => {
const [isModalOpen, setModalOpen] = useState(false);

const closeModal = useCallback(() => setModalOpen(false));
Expand All @@ -19,26 +19,36 @@ const CopyIconButton = ({ message, white, onClickConfirm }) => {
onClickConfirm();
setModalOpen(false);
});
const renderConfirmModal = (
<ConfirmModal
isOpen={isModalOpen}
title="Confirm copy"
message={<span>{message}</span>}
onConfirm={onConfirm}
onCancel={closeModal} />
);

const className = white === true ?
'btn btn-default btn-sm flat-btn flat-white' :
'btn btn-sm btn-inverse';

return (
<button
className={className}
title="Copy"
onClick={openModal}
style={{ width: '33px' }}>
<i className="icon ion-ios-copy" />

<ConfirmModal
isOpen={isModalOpen}
title="Confirm copy"
message={<span>{message}</span>}
onConfirm={onConfirm}
onCancel={closeModal} />
</button>
textFormat ? (
<span
onClick={openModal}>
Duplicate
{renderConfirmModal}
</span>
) : (
<button
className={className}
title="Copy"
onClick={openModal}
style={{ width: '33px' }}>
<i className="icon ion-ios-copy" />
{renderConfirmModal}
</button>
)
);
};

Expand Down
202 changes: 96 additions & 106 deletions ui/src/containers/Dashboard/index.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,29 @@
import React, { Component } from 'react';
import { isLoadingSelector } from 'ui/redux/modules/pagination';
import PropTypes from 'prop-types';
import * as _ from 'lodash';
import Scroll from 'react-scroll';
import { connect } from 'react-redux';
import { actions as routerActions } from 'redux-router5';
import { actions, routeNodeSelector } from 'redux-router5';
import { withProps, compose, lifecycle, withHandlers, mapProps } from 'recompose';
import { Map, List, is } from 'immutable';
import Input from 'ui/components/Material/Input';
import Spinner from 'ui/components/Spinner';
import CopyIconButton from 'ui/components/IconButton/CopyIconButton';
import DashboardGrid from 'ui/containers/DashboardGrid';
import DashboardSharing from 'ui/containers/DashboardSharing';
import DeleteButton from 'ui/containers/DeleteButton';
import Owner from 'ui/containers/Owner';
import PrivacyToggleButton from 'ui/containers/PrivacyToggleButton';
import WidgetVisualiseCreator from 'ui/containers/WidgetVisualiseCreator';
import { withModel } from 'ui/utils/hocs';
import { COPY_DASHBOARD } from 'ui/redux/modules/dashboard/copyDashboard';
import { getVisualisationsFromDashboard } from 'ui/redux/modules/dashboard/selectors';
import { withModels, withModel } from 'ui/utils/hocs';
import { loggedInUserId } from 'ui/redux/selectors';
import { activeOrgIdSelector } from 'ui/redux/modules/router';
import { EditInputWrapper, Editor, EditWrapper } from 'ui/containers/Dashboard/styled';

const schema = 'dashboard';
import { EditWrapper } from 'ui/containers/Dashboard/styled';

class Dashboard extends Component {
static propTypes = {
model: PropTypes.instanceOf(Map),
navigateTo: PropTypes.func,
updateModel: PropTypes.func,
saveModel: PropTypes.func,
setMetadata: PropTypes.func,
getMetadata: PropTypes.func,
doCopyDashboard: PropTypes.func,
};

static defaultProps = {
Expand Down Expand Up @@ -128,135 +119,134 @@ class Dashboard extends Component {
};

render() {
const { model, organisationId, navigateTo, doCopyDashboard } = this.props;
const { model, backToDashboard } = this.props;

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

return (
<div className="row">
<Editor>
<EditWrapper>
<EditInputWrapper>
<div>
<header id="topbar">
<div className="heading heading-light">
<span className="pull-right open_panel_btn" >
<a
onClick={this.onClickAddWidget}
className="btn btn-primary btn-sm">
<i className="ion ion-stats-bars" /> Add widget
</a>

<button
className="btn btn-default btn-sm flat-btn flat-white"
title="Share"
onClick={this.toggleSharing}
style={{
backgroundColor: this.props.getMetadata('isSharing') ? '#F5AB35' : null,
color: this.props.getMetadata('isSharing') ? 'white' : null,
marginRight: 0,
}}>
<i className="icon ion-android-share-alt" />
</button>
</span>
<EditWrapper>
<a
onClick={backToDashboard} >
<i className="icon ion-chevron-left" />
</a>
<Input
type="text"
name="Title"
label="Enter title"
value={model.get('title', ' ')}
onChange={this.onTitleChange}
style={{ fontSize: '13px' }} />
</EditInputWrapper>

&nbsp;&nbsp;

<a
onClick={this.onClickAddWidget}
className="btn btn-default btn-sm flat-btn flat-white">
<i className="ion ion-stats-bars" /> Add widget
</a>

<PrivacyToggleButton
white
id={model.get('_id')}
schema={schema} />

<CopyIconButton
message="This will copy the dashboard and visualisations. Are you sure?"
white
onClickConfirm={doCopyDashboard} />

<DeleteButton
white
id={model.get('_id')}
schema={schema}
onDeletedModel={() =>
navigateTo('organisation.data.dashboards', { organisationId })
} />

<button
className="btn btn-default btn-sm flat-btn flat-white"
title="Share"
onClick={this.toggleSharing}
style={{
backgroundColor: this.props.getMetadata('isSharing') ? '#F5AB35' : null,
color: this.props.getMetadata('isSharing') ? 'white' : null
}}>
<i className="icon ion-android-share-alt" />
</button>

<span style={{ marginLeft: 'auto' }}>
<Owner model={model} />
</span>
</EditWrapper>
style={{ fontSize: '18px', color: '#929292', padding: 0 }} />
</EditWrapper>
</div>
</header>
<div className="row">
{this.props.getMetadata('isSharing') &&
<div>
<div className="col-md-12">
<DashboardSharing
shareable={model.get('shareable', new List())}
id={model.get('_id')} />
</div>
}
</Editor>

<div className="clearfix" />
<div className="clearfix" />

<WidgetVisualiseCreator
isOpened={this.state.widgetModalOpen}
model={model}
onClickClose={() => this.toggleWidgetModal()}
onChangeVisualisation={this.createPopulatedWidget} />
<WidgetVisualiseCreator
isOpened={this.state.widgetModalOpen}
model={model}
onClickClose={() => this.toggleWidgetModal()}
onChangeVisualisation={this.createPopulatedWidget} />

<DashboardGrid
widgets={model.get('widgets')}
onChange={this.onChangeWidgets}
onChangeTitle={this.onChangeWidgetTitle}
onChangeVisualisation={this.onChangeWidgetVisualisation} />
<DashboardGrid
widgets={model.get('widgets')}
onChange={this.onChangeWidgets}
onChangeTitle={this.onChangeWidgetTitle}
onChangeVisualisation={this.onChangeWidgetVisualisation} />
</div>
</div>
);
}
}

export default compose(
withProps(({ params, id }) =>
({
schema,
id: id || params.dashboardId
connect(
state => ({
isLoading: isLoadingSelector('dashboard', new Map())(state),
userId: loggedInUserId(state),
route: routeNodeSelector('organisation.dashboards')(state).route,
organisation: activeOrgIdSelector(state)
}),
{ navigateTo: actions.navigateTo }
),
withProps({
schema: 'dashboard',
filter: new Map(),
first: 300,
}),
withModels,
withProps(
({ route }) => ({
id: route.name === 'organisation.data.dashboards.add' ? undefined : route.params.dashboardId
})
),
withModel,
withProps(
({
id,
models,
model
}) => {
if (model.size === 0 && id) {
return ({
modelsWithModel: models
});
}

return ({
modelsWithModel: !id || models.has(id) ? models : models.reverse().set(id, model).reverse()
});
}
),
lifecycle({
componentDidUpdate(previousProps) {
if (this.props.model.get('widgets').size > previousProps.model.get('widgets').size && window) {
if (
this.props.model.get('widgets') && this.props.model.get('widgets').size && previousProps.size &&
this.props.model.get('widgets').size > previousProps.model.get('widgets').size && window
) {
const scroll = Scroll.animateScroll;
scroll.scrollToBottom({ smooth: true });
}
}
}),
connect(
state => ({
organisationId: activeOrgIdSelector(state),
userId: loggedInUserId(state),
state,
}),
dispatch => ({
navigateTo: () => dispatch(routerActions.navigateTo),
copyDashboard: ({ dashboard, visualisations, organisationId, userId }) => dispatch({
type: COPY_DASHBOARD,
dispatch,
dashboard,
visualisations,
organisationId,
userId,
}),
}),
),
withHandlers({
doCopyDashboard: ({ model, state, organisationId, copyDashboard, userId }) => () => copyDashboard({
dashboard: model,
visualisations: getVisualisationsFromDashboard(model.get('_id'))(state),
organisationId,
userId,
})
backToDashboard: ({ route, navigateTo }) => () => {
const organisationId = route.params.organisationId;
navigateTo('organisation.data.dashboards', {
organisationId
});
}
}),
mapProps(original => _.pick(original, ['model', 'navigateTo', 'updateModel', 'saveModel', 'setMetadata', 'getMetadata', 'doCopyDashboard'])),
mapProps(original => _.pick(original, ['model', 'updateModel', 'saveModel', 'setMetadata', 'getMetadata', 'backToDashboard'])),
)(Dashboard);
12 changes: 12 additions & 0 deletions ui/src/containers/Dashboard/styled/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ export const EditWrapper = styled.div`
.btn {
margin-bottom: auto;
}
&:global(.btn) {
margin-bottom: auto;
}
div {
padding: 0 0 0 15px;
font-weight: 300;
color: #929292;
font-family: Arial, sans-serif;
}
`;

export const EditInputWrapper = styled.div`
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ui/src/containers/DashboardCard/assets/private.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 14380dd

Please sign in to comment.