diff --git a/assets/css/style.css b/assets/css/style.css
index dad0988e1..fb8b499a1 100644
--- a/assets/css/style.css
+++ b/assets/css/style.css
@@ -836,6 +836,7 @@ li:hover .icon { display:block; }
.icon.tooltip { background-position:-580px -180px; }
.icon.star { background-position:-600px -180px; }
.icon.cloud { background-position:-620px -180px; }
+.icon.eye { background-position:-640px -180px; }
.reverse.edit { background-position:0px -200px; }
.reverse.inspect { background-position:-20px -200px; }
@@ -863,6 +864,7 @@ li:hover .icon { display:block; }
.reverse.tooltip { background-position:-580px -200px; }
.reverse.star { background-position:-600px -200px; }
.reverse.cloud { background-position:-620px -200px; }
+.reverse.eye { background-position:-640px -200px; }
/* Dock-icon specific reverse styles */
.dock .active > .icon.legend { background-position:-280px -200px; }
@@ -1356,6 +1358,24 @@ div.fonts { padding-top:30px; }
width:120px;
}
+/* Exports preview */
+#preview {
+ background-position:50% 50%;
+ background-repeat:no-repeat;
+ background-color:#eee;
+ border:1px solid #ccc;
+ position:absolute !important;
+ left:20px !important;
+ right:20px !important;
+ top:80px !important;
+ bottom:20px !important;
+ width:auto !important;
+ height:auto !important;
+ box-shadow:inset #ddd 0px 0px 5px;
+ -moz-box-shadow:inset #ddd 0px 0px 5px;
+ -webkit-box-shadow:inset #ddd 0px 0px 5px;
+ }
+
/* Error page */
.empty {
text-align:center;
diff --git a/assets/images/sprite.png b/assets/images/sprite.png
index 233a9cd3f..92db69fb4 100644
Binary files a/assets/images/sprite.png and b/assets/images/sprite.png differ
diff --git a/assets/images/sprite.svg b/assets/images/sprite.svg
index 060795d5f..f381692a6 100644
--- a/assets/images/sprite.svg
+++ b/assets/images/sprite.svg
@@ -6056,9 +6056,9 @@
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="7.4505806"
- inkscape:cx="150.66173"
- inkscape:cy="168.27361"
+ inkscape:zoom="1"
+ inkscape:cx="618.15013"
+ inkscape:cy="185.16902"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
@@ -6122,6 +6122,10 @@
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
+
+
diff --git a/index.js b/index.js
index c19074475..ed2644936 100755
--- a/index.js
+++ b/index.js
@@ -1,9 +1,8 @@
#!/usr/bin/env node
process.title = 'tilemill';
-
-var tilelive_mapnik = require('tilelive-mapnik');
-tilelive_mapnik.registerProtocols(require('tilelive'));
+require('tilelive-mapnik').registerProtocols(require('tilelive'));
+require('mbtiles').registerProtocols(require('tilelive'));
require('bones').load(__dirname);
!module.parent && require('bones').start();
diff --git a/models/Preview.bones b/models/Preview.bones
new file mode 100644
index 000000000..d9e6aa97a
--- /dev/null
+++ b/models/Preview.bones
@@ -0,0 +1,3 @@
+// Preview of an mbtiles export.
+model = Backbone.Model.extend({});
+model.prototype.url = function() { return '/api/Preview/' + this.id; };
diff --git a/models/Preview.server.bones b/models/Preview.server.bones
new file mode 100644
index 000000000..a8148eb61
--- /dev/null
+++ b/models/Preview.server.bones
@@ -0,0 +1,17 @@
+var path = require('path');
+var tilelive = require('tilelive');
+var settings = Bones.plugin.config;
+
+models.Preview.prototype.sync = function(method, model, success, error) {
+ if (method !== 'read') return error(new Error('Method not supported.'));
+ var filepath = path.join(settings.files, 'export', model.id);
+ tilelive.load('mbtiles://' + filepath, function(err, source) {
+ if (err) return error(err);
+ source.getInfo(function(err, info) {
+ if (err) return error(err);
+ info.tiles = ['/1.0.0/' + model.id + '/{z}/{x}/{y}.png'];
+ success(_(info).extend({id: model.id }));
+ });
+ });
+};
+
diff --git a/servers/Tile.bones b/servers/Tile.bones
index 98964b4ad..f56590136 100644
--- a/servers/Tile.bones
+++ b/servers/Tile.bones
@@ -5,7 +5,9 @@ var path = require('path'),
server = Bones.Server.extend({});
server.prototype.initialize = function() {
- _.bindAll(this, 'load', 'grid', 'getArtifact');
+ _.bindAll(this, 'load', 'grid', 'getArtifact', 'mbtiles');
+ this.get('/1.0.0/:id.mbtiles/:z/:x/:y.:format(png8|png|jpeg[\\d]+|jpeg)', this.mbtiles);
+ this.get('/1.0.0/:id.mbtiles/:z/:x/:y.:format(grid.json)', this.mbtiles);
this.get('/1.0.0/:id/:z/:x/:y.:format(png8|png|jpeg[\\d]+|jpeg)', this.load, this.getArtifact);
this.get('/1.0.0/:id/:z/:x/:y.:format(grid.json)', this.load, this.grid, this.getArtifact);
};
@@ -50,7 +52,7 @@ server.prototype.getArtifact = function(req, res, next) {
var fn = req.params.format === 'grid.json' ? 'getGrid' : 'getTile';
source[fn](z, x, y, function(err, tile, headers) {
if (err) return next(err);
- headers['max-age'] = 3600;
+ if (headers) headers['max-age'] = 3600;
res.send(tile, headers);
});
});
@@ -70,3 +72,22 @@ server.prototype.grid = function(req, res, next) {
next();
};
+server.prototype.mbtiles = function(req, res, next) {
+ var uri = 'mbtiles://' + path.join(settings.files, 'export', req.param('id') + '.mbtiles');
+ tilelive.load(uri, function(err, source) {
+ if (err) return next(err);
+
+ var z = req.params.z, x = +req.params.x, y = +req.params.y;
+
+ // The interface is still TMS.
+ y = (1 << z) - 1 - y;
+
+ var fn = req.params.format === 'grid.json' ? 'getGrid' : 'getTile';
+ source[fn](z, x, y, function(err, tile, headers) {
+ if (err) return next(err);
+ if (headers) headers['max-age'] = 3600;
+ res.send(tile, headers);
+ });
+ });
+};
+
diff --git a/templates/Exports._ b/templates/Exports._
index cbe95d30c..89a91aa97 100644
--- a/templates/Exports._
+++ b/templates/Exports._
@@ -22,7 +22,12 @@
<%= obj.time(m.get('remaining')) %> remaining
<% } %>
<% if (m.get('status') === 'complete') { %>
+
+ <% if (m.get('format') === 'mbtiles') { %>
+
+ <% } %>
'> Download
+
<% } %>
<% if (m.get('status') === 'error') { %>
<%=m.get('error')%>
diff --git a/templates/Preview._ b/templates/Preview._
new file mode 100644
index 000000000..c577ff92d
--- /dev/null
+++ b/templates/Preview._
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/views/Exports.bones b/views/Exports.bones
index 652513568..29c880d39 100644
--- a/views/Exports.bones
+++ b/views/Exports.bones
@@ -1,11 +1,12 @@
view = Backbone.View.extend();
view.prototype.events = {
- 'click a.delete': 'exportDelete'
+ 'click a.delete': 'exportDelete',
+ 'click a.preview': 'exportPreview'
};
view.prototype.initialize = function(options) {
- _(this).bindAll('render', 'exportDelete', 'poll');
+ _(this).bindAll('render', 'exportDelete', 'exportPreview', 'poll');
this.collection.bind('all', this.render);
this.collection.bind('all', this.poll);
this.render(true).poll();
@@ -47,6 +48,19 @@ view.prototype.exportDelete = function(ev) {
return false;
};
+view.prototype.exportPreview = function(ev) {
+ var id = $(ev.currentTarget).attr('href').split('#').pop();
+ (new models.Preview({id:id})).fetch({
+ success: function(model, resp) {
+ new views.Preview({
+ el: $('#popup'),
+ model:model
+ });
+ },
+ error: function(m, e) { new views.Modal(e) }
+ });
+};
+
// Poll controller.
// - Starts polling if exports are active and drawer shows this view.
// - Stops polling under all other conditions.
diff --git a/views/Preview.bones b/views/Preview.bones
new file mode 100644
index 000000000..2f8aedf33
--- /dev/null
+++ b/views/Preview.bones
@@ -0,0 +1,27 @@
+view = Backbone.View.extend();
+
+view.prototype.initialize = function(options) {
+ _(this).bindAll('render');
+ this.render();
+};
+
+view.prototype.render = function() {
+ this.$('.content').html(templates.Preview(this.model));
+
+ if (!com.modestmaps) throw new Error('ModestMaps not found.');
+ this.map = new com.modestmaps.Map('preview',
+ new wax.mm.connector(this.model.attributes));
+ wax.mm.interaction(this.map, this.model.attributes);
+ wax.mm.legend(this.map, this.model.attributes);
+ wax.mm.zoombox(this.map);
+ wax.mm.zoomer(this.map).appendTo(this.map.parent);
+
+ var center = this.model.get('center');
+ this.map.setCenterZoom(new com.modestmaps.Location(
+ center[1],
+ center[0]),
+ center[2]);
+
+ return this;
+};
+
diff --git a/views/Project.bones b/views/Project.bones
index a92a42342..1067744bf 100644
--- a/views/Project.bones
+++ b/views/Project.bones
@@ -80,12 +80,8 @@ view.prototype.render = function(init) {
new wax.mm.connector(this.model.attributes));
// Add references to all controls onto the map object.
- // Allows controls to be removed later on. @TODO need
- // wax 3.x and updates to controls to return references
- // to themselves.
+ // Allows controls to be removed later on.
this.map.controls = {
- // @TODO wax 3.x.
- // interaction, legend require TileJSON attributes from the model.
interaction: wax.mm.interaction(this.map, this.model.attributes),
legend: wax.mm.legend(this.map, this.model.attributes),
zoombox: wax.mm.zoombox(this.map),