Skip to content

Commit

Permalink
Merge pull request #177 from nmco/globalspinner
Browse files Browse the repository at this point in the history
Full Screen Map Loading Spinner #108.
  • Loading branch information
mbarto committed Sep 30, 2015
2 parents e47598d + 4f9af6e commit ac3a336
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 15 deletions.
46 changes: 45 additions & 1 deletion web/client/actions/__tests__/map-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@ var {
CHANGE_MAP_VIEW,
CLICK_ON_MAP,
CHANGE_MOUSE_POINTER,
LAYER_LOADING,
LAYER_LOAD,
SHOW_SPINNER,
HIDE_SPINNER,
changeMapView,
clickOnMap,
changeMousePointer
changeMousePointer,
layerLoading,
layerLoad,
showSpinner,
hideSpinner
} = require('../map');

describe('Test correctness of the map actions', () => {
Expand Down Expand Up @@ -50,4 +58,40 @@ describe('Test correctness of the map actions', () => {
expect(retval.type).toBe(CHANGE_MOUSE_POINTER);
expect(retval.pointer).toBe(testVal);
});

it('a layer is loading', () => {
const testVal = 'layer1';
const retval = layerLoading(testVal);

expect(retval).toExist();
expect(retval.type).toBe(LAYER_LOADING);
expect(retval.layerId).toBe(testVal);
});

it('a layer is load', () => {
const testVal = 'layer1';
const retval = layerLoad(testVal);

expect(retval).toExist();
expect(retval.type).toBe(LAYER_LOAD);
expect(retval.layerId).toBe(testVal);
});

it('show some spinner', () => {
const testVal = 'spinner1';
const retval = showSpinner(testVal);

expect(retval).toExist();
expect(retval.type).toBe(SHOW_SPINNER);
expect(retval.spinnerId).toBe(testVal);
});

it('hide some spinner', () => {
const testVal = 'spinner1';
const retval = hideSpinner(testVal);

expect(retval).toExist();
expect(retval.type).toBe(HIDE_SPINNER);
expect(retval.spinnerId).toBe(testVal);
});
});
42 changes: 41 additions & 1 deletion web/client/actions/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
const CHANGE_MAP_VIEW = 'CHANGE_MAP_VIEW';
const CLICK_ON_MAP = 'CLICK_ON_MAP';
const CHANGE_MOUSE_POINTER = 'CHANGE_MOUSE_POINTER';
const LAYER_LOADING = 'LAYER_LOADING';
const LAYER_LOAD = 'LAYER_LOAD';
const SHOW_SPINNER = 'SHOW_SPINNER';
const HIDE_SPINNER = 'HIDE_SPINNER';

function changeMapView(center, zoom, bbox, size) {
return {
Expand All @@ -34,11 +38,47 @@ function changeMousePointer(pointerType) {
};
}

function layerLoading(layerId) {
return {
type: LAYER_LOADING,
layerId: layerId
};
}

function layerLoad(layerId) {
return {
type: LAYER_LOAD,
layerId: layerId
};
}

function showSpinner(spinnerId) {
return {
type: SHOW_SPINNER,
spinnerId: spinnerId
};
}

function hideSpinner(spinnerId) {
return {
type: HIDE_SPINNER,
spinnerId: spinnerId
};
}

module.exports = {
CHANGE_MAP_VIEW,
CLICK_ON_MAP,
CHANGE_MOUSE_POINTER,
LAYER_LOADING,
LAYER_LOAD,
SHOW_SPINNER,
HIDE_SPINNER,
changeMapView,
clickOnMap,
changeMousePointer
changeMousePointer,
layerLoading,
layerLoad,
showSpinner,
hideSpinner
};
51 changes: 51 additions & 0 deletions web/client/components/globalspinner/GlobalSpinner.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright 2015, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
var React = require('react');

var GlobalSpinner = React.createClass({
propTypes: {
id: React.PropTypes.string,
loadingLayers: React.PropTypes.object,
showSpinner: React.PropTypes.func,
hideSpinner: React.PropTypes.func,
spinnersInfo: React.PropTypes.object,
delayMs: React.PropTypes.number
},
getDefaultProps() {
return {
id: "mapstore-globalspinner",
loadingLayers: {},
showSpinner() {},
hideSpinner() {},
delayMs: 500
};
},
componentWillReceiveProps(newProps) {
var id = newProps.id;
var delayMs = newProps.delayMs;
var func = this.isSomeLayerLoading(newProps.loadingLayers) ? newProps.showSpinner : newProps.hideSpinner;
setTimeout(() => func(id), delayMs);
},
render() {
if (this.isSomeLayerLoading(this.props.loadingLayers) && this.show()) {
return (
<div id={this.props.id}></div>
);
}
return null;
},
isSomeLayerLoading(loadingLayers) {
return Object.keys(loadingLayers).map(
(key) => { return loadingLayers[key]; }).some(element => element === true);
},
show() {
return (this.props.spinnersInfo || {})[this.props.id] || false;
}
});

module.exports = GlobalSpinner;
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright 2015, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
var React = require('react/addons');
var GlobalSpinner = require('../GlobalSpinner');
var expect = require('expect');

describe('test the globalspinner component', () => {
afterEach((done) => {
React.unmountComponentAtNode(document.body);
document.body.innerHTML = '';
setTimeout(done);
});

it('creates the component with defaults', () => {
const globalspinner = React.render(<GlobalSpinner/>, document.body);
expect(globalspinner).toExist();
const globalspinnerDiv = React.findDOMNode(globalspinner);
expect(globalspinnerDiv).toNotExist();
});

it('creates the component with layers loading and spinner to show', () => {
const globalspinner = React.render(<GlobalSpinner id="globalspinner" loadingLayers={{layer1: true}}
spinnersInfo={{globalspinner: true}}/>, document.body);
expect(globalspinner).toExist();
const globalspinnerDiv = React.findDOMNode(globalspinner);
expect(globalspinnerDiv).toExist();
});

it('creates the component with layers loading but no spinner to show', () => {
const globalspinner = React.render(<GlobalSpinner id="globalspinner" loadingLayers={{layer1: true}}
spinnersInfo={{globalspinner: false}}/>, document.body);
expect(globalspinner).toExist();
const globalspinnerDiv = React.findDOMNode(globalspinner);
expect(globalspinnerDiv).toNotExist();
});

it('creates the component with layers load', () => {
const globalspinner = React.render(<GlobalSpinner loadingLayers={{layer1: false}}/>, document.body);
expect(globalspinner).toExist();
const globalspinnerDiv = React.findDOMNode(globalspinner);
expect(globalspinnerDiv).toNotExist();
});
});
14 changes: 12 additions & 2 deletions web/client/components/map/leaflet/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ var LeafletMap = React.createClass({
onClick: React.PropTypes.func,
mapOptions: React.PropTypes.object,
mousePointer: React.PropTypes.string,
onMouseMove: React.PropTypes.func
onMouseMove: React.PropTypes.func,
onLayerLoading: React.PropTypes.func,
onLayerLoad: React.PropTypes.func
},
getDefaultProps() {
return {
Expand All @@ -31,7 +33,9 @@ var LeafletMap = React.createClass({
zoomAnimation: false,
attributionControl: false
},
projection: "EPSG:3857"
projection: "EPSG:3857",
onLayerLoading: () => {},
onLayerLoad: () => {}
};
},
getInitialState() {
Expand All @@ -56,6 +60,12 @@ var LeafletMap = React.createClass({
this.setMousePointer(this.props.mousePointer);
// NOTE: this re-call render function after div creation to have the map initialized.
this.forceUpdate();

this.map.on('layeradd', (event) => {
this.props.onLayerLoading(event.layer._leaflet_id);
event.layer.on('loading', (loadingEvent) => { this.props.onLayerLoading(loadingEvent.target._leaflet_id); });
event.layer.on('load', (loadEvent) => { this.props.onLayerLoad(loadEvent.target._leaflet_id); });
});
},
componentWillReceiveProps(newProps) {
if (newProps.mousePointer !== this.props.mousePointer) {
Expand Down
16 changes: 14 additions & 2 deletions web/client/components/map/openlayers/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ var OpenlayersMap = React.createClass({
onClick: React.PropTypes.func,
mapOptions: React.PropTypes.object,
mousePointer: React.PropTypes.string,
onMouseMove: React.PropTypes.func
onMouseMove: React.PropTypes.func,
onLayerLoading: React.PropTypes.func,
onLayerLoad: React.PropTypes.func
},
getDefaultProps() {
return {
Expand All @@ -29,7 +31,9 @@ var OpenlayersMap = React.createClass({
onClick: null,
onMouseMove: () => {},
mapOptions: {},
projection: 'EPSG:3857'
projection: 'EPSG:3857',
onLayerLoading: () => {},
onLayerLoad: () => {}
};
},
getInitialState() {
Expand Down Expand Up @@ -99,6 +103,14 @@ var OpenlayersMap = React.createClass({
}
});

map.on('precompose', () => {
map.getLayers().forEach((element, index) => { this.props.onLayerLoading(index); });
});

map.on('postcompose', () => {
map.getLayers().forEach((element, index) => { this.props.onLayerLoad(index); });
});

this.map = map;
this.setMousePointer(this.props.mousePointer);
// NOTE: this re-call render function after div creation to have the map initialized.
Expand Down
8 changes: 6 additions & 2 deletions web/client/examples/viewer/components/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ var VMap = React.createClass({
config: ConfigUtils.PropTypes.config,
onMapViewChanges: React.PropTypes.func,
onClick: React.PropTypes.func,
onMouseMove: React.PropTypes.func
onMouseMove: React.PropTypes.func,
onLayerLoading: React.PropTypes.func,
onLayerLoad: React.PropTypes.func
},
renderLayers(layers) {
if (layers) {
Expand All @@ -43,7 +45,9 @@ var VMap = React.createClass({
onMapViewChanges={this.props.onMapViewChanges}
onClick={this.props.onClick}
mousePointer={this.props.config.mousePointer}
onMouseMove={this.props.onMouseMove}>
onMouseMove={this.props.onMouseMove}
onLayerLoading={this.props.onLayerLoading}
onLayerLoad={this.props.onLayerLoad}>
{this.renderLayers(this.props.config.layers)}
</LMap>
);
Expand Down
18 changes: 14 additions & 4 deletions web/client/examples/viewer/containers/Viewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ var ConfigUtils = require('../../../utils/ConfigUtils');

var {loadLocale} = require('../../../actions/locale');

var {changeMapView, clickOnMap, changeMousePointer} = require('../../../actions/map');
var {changeMapView, clickOnMap, changeMousePointer,
layerLoading, layerLoad, showSpinner, hideSpinner} = require('../../../actions/map');

var VMap = require('../components/Map');
var Localized = require('../../../components/I18N/Localized');
Expand All @@ -41,7 +42,11 @@ var Viewer = React.createClass({
changeMousePointer: React.PropTypes.func,
activatePanel: React.PropTypes.func,
floatingPanel: React.PropTypes.object,
plugins: React.PropTypes.array
plugins: React.PropTypes.array,
layerLoading: React.PropTypes.func,
layerLoad: React.PropTypes.func,
showSpinner: React.PropTypes.func,
hideSpinner: React.PropTypes.func
},
getFirstWmsVisibleLayer() {
for (let i = 0; i < this.props.mapConfig.layers.length; i++) {
Expand Down Expand Up @@ -81,7 +86,8 @@ var Viewer = React.createClass({
}
return (
<div key="viewer" className="fill">
<VMap key="map" config={config} onMapViewChanges={this.manageNewMapView} onClick={this.props.clickOnMap} onMouseMove={this.manageMousePosition}/>
<VMap key="map" config={config} onMapViewChanges={this.manageNewMapView} onClick={this.props.clickOnMap} onMouseMove={this.manageMousePosition}
onLayerLoading={this.props.layerLoading} onLayerLoad={this.props.layerLoad}/>
{plugins}
</div>
);
Expand Down Expand Up @@ -119,7 +125,11 @@ module.exports = (actions) => {
loadLocale: loadLocale.bind(null, '../../translations'),
changeMapView,
clickOnMap,
changeMousePointer
changeMousePointer,
layerLoading,
layerLoad,
showSpinner,
hideSpinner
}, actions), dispatch);
})(Viewer);
};
Binary file added web/client/examples/viewer/img/spinner.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions web/client/examples/viewer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@
position: absolute;
margin: 8px;
}
#mapstore-globalspinner {
width: 28px;
height: 28px;
box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.2);
background: url("img/spinner.gif") no-repeat scroll center center #FFF;
background-size: 80px 80px;
background-repeat: no-repeat;
border-radius: 4px;
border: 1px solid #999;
z-index: 10;
top: 70px;
left: 2px;
position: absolute;
margin: 8px;
}

</style>
</head>
Expand Down
Loading

0 comments on commit ac3a336

Please sign in to comment.