Skip to content

Commit

Permalink
[Feature] Notification systems with new UI panel and helpers to gener…
Browse files Browse the repository at this point in the history
…ate messages (#333)

    * Added notification utils/reducers with tests

    * Created first version of notification container

    * implemented logic to show/hide notifications

    * Able to visualize different types of notifications

    * Added first design for global notifications

    * Able to collpase and expand notifications

    * Showing only global notifications

    * Added markdown support

    * Added tests for notification panel and item

    * Removed uber npm repo references
  • Loading branch information
macrigiuseppe committed Jan 19, 2019
1 parent 27f19a8 commit bc09c03
Show file tree
Hide file tree
Showing 32 changed files with 1,457 additions and 416 deletions.
27 changes: 26 additions & 1 deletion examples/demo-app/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import sampleTripData from './data/sample-trip-data';
import sampleGeojson from './data/sample-geojson.json';
import sampleH3Data from './data/sample-hex-id-csv';
import sampleIconCsv, {config as savedMapConfig} from './data/sample-icon-csv';
import {updateVisData, addDataToMap} from 'kepler.gl/actions';
import {updateVisData, addDataToMap, addNotification} from 'kepler.gl/actions';
import Processors from 'kepler.gl/processors';
/* eslint-enable no-unused-vars */

Expand Down Expand Up @@ -121,6 +121,9 @@ class App extends Component {

// load sample data
// this._loadSampleData();

// Notifications
this._loadMockNotifications();
}

componentWillUnmount() {
Expand All @@ -147,6 +150,28 @@ class App extends Component {
window.localStorage.setItem(BannerKey, 'true');
};

_loadMockNotifications = () => {
const notifications = [
[{message: 'Welcome to Kepler.gl'}, 3000],
[{message: 'Something is wrong', type: 'error'}, 1000],
[{message: 'I am getting better', type: 'warning'}, 1000],
[{message: 'Everything is fine', type: 'success'}, 1000]
];

this._addNotifications(notifications);
};

_addNotifications(notifications) {
if (notifications && notifications.length) {
const [notification, timeout] = notifications[0];

window.setTimeout(() => {
this.props.dispatch(addNotification(notification));
this._addNotifications(notifications.slice(1));
}, timeout);
}
}

_loadSampleData() {
this.props.dispatch(
updateVisData(
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"react-file-drop": "^0.1.8",
"react-json-pretty": "^1.7.9",
"react-map-gl": "^3.3.0",
"react-markdown": "^4.0.6",
"react-modal": "^3.1.10",
"react-onclickoutside": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.7.1.tgz",
"react-palm": "^1.1.2",
Expand Down
14 changes: 11 additions & 3 deletions src/actions/ui-state-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ const {
SHOW_EXPORT_DROPDOWN,
HIDE_EXPORT_DROPDOWN,
TOGGLE_SIDE_PANEL,
TOGGLE_MAP_CONTROL
TOGGLE_MAP_CONTROL,
ADD_NOTIFICATION,
REMOVE_NOTIFICATION
} = ActionTypes;

// second argument of createAction is expected to be payloadCreator or undefined
Expand All @@ -49,6 +51,8 @@ const [
hideExportDropdown,
toggleMapControl,
openDeleteModal,
addNotification,
removeNotification,
// export image
setRatio,
setResolution,
Expand All @@ -69,6 +73,8 @@ const [
HIDE_EXPORT_DROPDOWN,
TOGGLE_MAP_CONTROL,
OPEN_DELETE_MODAL,
ADD_NOTIFICATION,
REMOVE_NOTIFICATION,
SET_RATIO,
SET_RESOLUTION,
TOGGLE_LEGEND,
Expand All @@ -83,7 +89,9 @@ const [
].map(a => createAction(a));

export {
toggleSidePanel, toggleModal, showExportDropdown, hideExportDropdown, toggleMapControl, openDeleteModal, setExportConfig, setExportData,
setRatio, setResolution, toggleLegend, startExportingImage, setExportImageDataUri, cleanupExportImage,
toggleSidePanel, toggleModal, showExportDropdown, hideExportDropdown,
toggleMapControl, openDeleteModal, addNotification, removeNotification,
setExportConfig, setExportData, setRatio, setResolution, toggleLegend,
startExportingImage, setExportImageDataUri, cleanupExportImage,
setExportSelectedDataset, setExportDataType, setExportFiltered
};
45 changes: 45 additions & 0 deletions src/components/common/icons/checkmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Base from './base';

export default class Checkmark extends Component {
static propTypes = {
/** Set the height of the icon, ex. '16px' */
height: PropTypes.string
};

static defaultProps = {
height: '16px',
predefinedClassName: 'data-ex-icons-checkmark',
stroke: '#FFF'
};

render() {
return (
<Base viewBox="0 0 42 42" {...this.props}>
<path d="M16,0C7.163,0,0,7.163,0,16c0,8.837,7.163,16,16,16c8.836,0,16-7.164,16-16C32,7.163,24.836,0,16,0z M16,30 C8.268,30,2,23.732,2,16C2,8.268,8.268,2,16,2s14,6.268,14,14C30,23.732,23.732,30,16,30z"/>
<path d="M23.3,10.393L13.012,20.589l-4.281-4.196c-0.394-0.391-1.034-0.391-1.428,0 c-0.395,0.391-0.395,1.024,0,1.414l4.999,4.899c0.41,0.361,1.023,0.401,1.428,0l10.999-10.899c0.394-0.39,0.394-1.024,0-1.414 C24.334,10.003,23.695,10.003,23.3,10.393z"/>
</Base>
);
}
}
2 changes: 1 addition & 1 deletion src/components/common/icons/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Base from './base';

export default class Cross extends Component {
export default class Delete extends Component {
static propTypes = {
height: PropTypes.string
};
Expand Down
4 changes: 4 additions & 0 deletions src/components/common/icons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export {default as ArrowDown} from './arrow-down';
export {default as ArrowLeft} from './arrow-left';
export {default as ArrowRight} from './arrow-right';
export {default as Base} from './base';
export {default as Checkmark} from './checkmark';
export {default as Clock} from './clock';
export {default as Close} from './close';
export {default as Crosshairs} from './crosshairs';
Expand All @@ -40,6 +41,7 @@ export {default as Files} from './files';
export {default as FileType} from './file-type';
export {default as FilterFunnel} from './filter-funnel';
export {default as Histogram} from './histogram';
export {default as Info} from './info';
export {default as Layers} from './layers';
export {default as LeftArrow} from './left-arrow';
export {default as Legend} from './legend';
Expand All @@ -64,3 +66,5 @@ export {default as Upload} from './upload';
export {default as VertDots} from './vert-dots';
export {default as IconWrapper} from './base';
export {default as CodeAlt} from './code-alt';
export {default as Warning} from './warning';

45 changes: 45 additions & 0 deletions src/components/common/icons/info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Base from './base';

export default class Info extends Component {
static propTypes = {
/** Set the height of the icon, ex. '16px' */
height: PropTypes.string
};

static defaultProps = {
height: '16px',
predefinedClassName: 'data-ex-icons-info',
stroke: '#FFF'
};

render() {
return (
<Base viewBox="0 0 64 64" {...this.props}>
<circle cx="25" cy="25" fill="none" r="24" stroke={this.props.stroke} strokeLinecap="round" strokeMiterlimit="10" strokeWidth="2"/>
<path d="M23.779,16.241c-0.216,0-0.357-0.144-0.357-0.359v-2.618c0-0.215,0.142-0.359,0.357-0.359h2.439 c0.215,0,0.359,0.144,0.359,0.359v2.618c0,0.215-0.145,0.359-0.359,0.359H23.779z M23.852,37.293c-0.215,0-0.358-0.143-0.358-0.358 V20.473c0-0.215,0.144-0.359,0.358-0.359h2.295c0.216,0,0.359,0.144,0.359,0.359v16.462c0,0.216-0.144,0.358-0.359,0.358H23.852z"/>
</Base>
);
}
}
46 changes: 46 additions & 0 deletions src/components/common/icons/warning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Base from './base';

export default class Warning extends Component {
static propTypes = {
/** Set the height of the icon, ex. '16px' */
height: PropTypes.string
};

static defaultProps = {
height: '16px',
predefinedClassName: 'data-ex-icons-warning',
stroke: '#FFF'
};

render() {
return (
<Base viewBox="0 0 64 64" {...this.props}>
<path d="M0.349,49h49.302L25,1.842L0.349,49z M3.651,47L25,6.159L46.349,47H3.651z"/>
<rect height="18" width="2" x="24" y="18"/>
<rect height="3" width="2" x="24" y="39"/>
</Base>
);
}
}
2 changes: 1 addition & 1 deletion src/components/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ function flattenDeps(allDeps, factory) {

const allDependencies = flattenDeps([], ContainerFactory);

// provide all dependencites to appInjector
// provide all dependencies to appInjector
export const appInjector = allDependencies
.reduce((inj, factory) => inj.provide(factory, factory), injector());

Expand Down
13 changes: 11 additions & 2 deletions src/components/kepler-gl.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import MapContainerFactory from './map-container';
import BottomWidgetFactory from './bottom-widget';
import ModalContainerFactory from './modal-container';
import PlotContainerFactory from './plot-container';
import NotificationPanelFactory from './notification-panel';

import {generateHashId} from 'utils/utils';

Expand Down Expand Up @@ -80,15 +81,17 @@ KeplerGlFactory.deps = [
MapContainerFactory,
ModalContainerFactory,
SidePanelFactory,
PlotContainerFactory
PlotContainerFactory,
NotificationPanelFactory
];

function KeplerGlFactory(
BottomWidget,
MapContainer,
ModalWrapper,
SidePanel,
PlotContainer
PlotContainer,
NotificationPanel
) {
class KeplerGL extends Component {
static defaultProps = {
Expand Down Expand Up @@ -204,6 +207,11 @@ function KeplerGlFactory(
clicked
} = visState;

const notificationPanelFields = {
removeNotification: uiStateActions.removeNotification,
notifications: uiState.notifications
};

const sideFields = {
appName,
version,
Expand Down Expand Up @@ -279,6 +287,7 @@ function KeplerGlFactory(
this.root = node;
}}
>
<NotificationPanel {...notificationPanelFields} />
{!uiState.readOnly && <SidePanel {...sideFields} />}
<div className="maps" style={{display: 'flex'}}>
{mapContainers}
Expand Down
74 changes: 74 additions & 0 deletions src/components/notification-panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

import NotificationItemFactory from './notification-panel/notification-item';
import {DEFAULT_NOTIFICATION_TOPICS} from 'constants/default-settings';

const NotificationPanelContent = styled.div`
background: transparent;
display: flex;
flex-direction: column;
align-items: flex-end;
padding: 4px;
overflow-y: scroll;
overflow-x: hidden;
position: absolute;
top: 1em;
right: 1em;
z-index: 10000;
box-sizing: border-box;
`;

NotificationPanelFactory.deps = [
NotificationItemFactory
];

export default function NotificationPanelFactory (
NotificationItem
) {
return class NotificationPanel extends Component {
static propTypes = {
removeNotification: PropTypes.func.isRequired,
notifications: PropTypes.arrayOf(PropTypes.object).isRequired
};

render() {
return (
<NotificationPanelContent className="notification-panel">
{this.props.notifications
.filter(n => n.topic === DEFAULT_NOTIFICATION_TOPICS.global)
.map(n => (
<NotificationItem
key={n.id}
notification={n}
removeNotification={this.props.removeNotification}
/>
))
}
</NotificationPanelContent>
);
}
}
}

Loading

0 comments on commit bc09c03

Please sign in to comment.