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') { %> + '> Preview + <% } %> '> 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),