diff --git a/css/app.css b/css/app.css
index 9ba2e9e67..d1056ba8c 100644
--- a/css/app.css
+++ b/css/app.css
@@ -24,6 +24,25 @@ body {
max-width: 1200px;
}
+.spinner {
+ opacity: .5;
+}
+
+.spinner img {
+ position: fixed;
+ padding: 5px;
+ height: 40px;
+ width: 40px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ top: 0;
+ margin: auto;
+ border-radius: 5px;
+ background: black;
+ pointer-events:none;
+}
+
div, textarea, input, form, span, ul, li, ol, a, button {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
diff --git a/img/loader-black.gif b/img/loader-black.gif
new file mode 100644
index 000000000..08f0d7e34
Binary files /dev/null and b/img/loader-black.gif differ
diff --git a/index.html b/index.html
index 1b35bb7da..536c0de00 100644
--- a/index.html
+++ b/index.html
@@ -77,6 +77,7 @@
+
diff --git a/js/id/connection.js b/js/id/connection.js
index 2f8b3abb2..8b3328b78 100644
--- a/js/id/connection.js
+++ b/js/id/connection.js
@@ -1,6 +1,6 @@
iD.Connection = function(context) {
- var event = d3.dispatch('auth', 'load'),
+ var event = d3.dispatch('auth', 'loading', 'load', 'loaded'),
url = 'http://www.openstreetmap.org',
connection = {},
user = {},
@@ -19,19 +19,6 @@ iD.Connection = function(context) {
return url + '/browse/changeset/' + changesetId;
};
- function bboxUrl(b) {
- return url + '/api/0.6/map?bbox=' + [b[0][0],b[1][1],b[1][0],b[0][1]];
- }
-
- function bboxFromAPI(box, tile, callback) {
- function done(err, parsed) {
- loadedTiles[tile.toString()] = true;
- delete inflight[tile.toString()];
- callback(err, parsed);
- }
- inflight[tile.toString()] = connection.loadFromURL(bboxUrl(box), done);
- }
-
connection.loadFromURL = function(url, callback) {
function done(dom) {
return callback(null, parse(dom));
@@ -237,17 +224,8 @@ iD.Connection = function(context) {
oauth.xhr({ method: 'GET', path: '/api/0.6/user/details' }, done);
};
- function tileAlreadyLoaded(c) { return !loadedTiles[c.toString()] && !inflight[c.toString()]; }
-
function abortRequest(i) { i.abort(); }
- function loadTile(e) {
- function done(err, g) {
- event.load(err, g);
- }
- bboxFromAPI(e.box, e.tile, done);
- }
-
connection.loadTiles = function(projection, dimensions) {
var scaleExtent = [16, 16],
s = projection.scale(),
@@ -263,15 +241,14 @@ iD.Connection = function(context) {
s / 2 - projection.translate()[0],
s / 2 - projection.translate()[1]];
- function apiExtentBox(c) {
- var x = (c[0] * ts) - tile_origin[0];
- var y = (c[1] * ts) - tile_origin[1];
- return {
- box: [
- projection.invert([x, y]),
- projection.invert([x + ts, y + ts])],
- tile: c
- };
+ function bboxUrl(tile) {
+ var x = (tile[0] * ts) - tile_origin[0];
+ var y = (tile[1] * ts) - tile_origin[1];
+ var b = [
+ projection.invert([x, y]),
+ projection.invert([x + ts, y + ts])];
+
+ return url + '/api/0.6/map?bbox=' + [b[0][0], b[1][1], b[1][0], b[0][1]];
}
_.filter(inflight, function(v, i) {
@@ -282,10 +259,26 @@ iD.Connection = function(context) {
return !wanted;
}).map(abortRequest);
- tiles
- .filter(tileAlreadyLoaded)
- .map(apiExtentBox)
- .forEach(loadTile);
+ tiles.forEach(function(tile) {
+ var id = tile.toString();
+
+ if (loadedTiles[id] || inflight[id]) return;
+
+ if (_.isEmpty(inflight)) {
+ event.loading();
+ }
+
+ inflight[id] = connection.loadFromURL(bboxUrl(tile), function(err, parsed) {
+ loadedTiles[id] = true;
+ delete inflight[id];
+
+ event.load(err, parsed);
+
+ if (_.isEmpty(inflight)) {
+ event.loaded();
+ }
+ });
+ });
};
connection.userUrl = function(username) {
diff --git a/js/id/ui.js b/js/id/ui.js
index d4e0fa10a..6e28d13f4 100644
--- a/js/id/ui.js
+++ b/js/id/ui.js
@@ -48,6 +48,10 @@ iD.ui = function(context) {
.attr('class', 'button-wrap col1')
.call(iD.ui.Save(context));
+ bar.append('div')
+ .attr('class', 'spinner')
+ .call(iD.ui.Spinner(context));
+
container.append('div')
.attr('class', 'map-control zoombuttons')
.call(iD.ui.Zoom(context));
diff --git a/js/id/ui/spinner.js b/js/id/ui/spinner.js
new file mode 100644
index 000000000..c567dd257
--- /dev/null
+++ b/js/id/ui/spinner.js
@@ -0,0 +1,19 @@
+iD.ui.Spinner = function(context) {
+ var connection = context.connection();
+
+ return function(selection) {
+ var img = selection.append('img')
+ .attr('src', 'img/loader-black.gif')
+ .style('opacity', 0);
+
+ connection.on('loading.spinner', function() {
+ img.transition()
+ .style('opacity', 1);
+ });
+
+ connection.on('loaded.spinner', function() {
+ img.transition()
+ .style('opacity', 0);
+ });
+ }
+};