From 44923eb660c5d8bd7aa360f4246ffa6bc56c0738 Mon Sep 17 00:00:00 2001 From: Connor Turland Date: Sun, 9 Nov 2014 22:10:13 -0500 Subject: [PATCH] added new map screenshot capture method --- app/assets/images/screenshot_sprite.png | Bin 0 -> 1556 bytes app/assets/javascripts/src/Metamaps.JIT.js | 43 +++--- app/assets/javascripts/src/Metamaps.Router.js | 4 +- app/assets/javascripts/src/Metamaps.js | 126 +++++++++++++++++- app/assets/stylesheets/clean.css | 16 +++ app/controllers/maps_controller.rb | 26 +++- app/models/map.rb | 6 +- app/views/layouts/_lowermapelements.html.erb | 1 + config/routes.rb | 1 + 9 files changed, 197 insertions(+), 26 deletions(-) create mode 100644 app/assets/images/screenshot_sprite.png diff --git a/app/assets/images/screenshot_sprite.png b/app/assets/images/screenshot_sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..d3c87c5a88580bcc443a2acef7c904b4baf93d03 GIT binary patch literal 1556 zcmV+v2J88WP){xF|lUp1*i3@kj9#t1Acr6|ft9|8+DKLiawcn5v) z(;5{8209`QQkjAJLCA#_nu^XK{2=&VrAQIm{0A1Y`~1ds+1%Wpw|TkTyC1x~d(U~# zo^#IcJkRgh7>h+LViAj2{C^`(>cCT{PAPCxxF79r-n<$6Cnp{){5T1q+LV+O$J*Lj zu|}ie2?+^oetw=!PEOiwHk-w6w;M1SaSIo%O-oC2;orG)hbJZ`vbniAHZd__|M>Bv zWnp0FMd}qM{;}nVHFyN~Qed$&<>TKY!|+PG{WI z)RZ;Ki`S{u>Q=p8&*S3a`0((sDI+68efsoiwtxSAR#;dlKXc}c^2d)KI$UAnhqVD0 zFJ5fAcJ11|va&KcW&H5r!)$PHaPP>-$V+U1irOgR@39Mjj=M|@Ob>>ts{{EZtoG?Z!YsHv%O;Xi!%&=0?-rzdRu zZJ{_{yf@%PsOg!ax3`xK3=HTo&UoO!0S-EtkqNt0&YwGX zt_LT;uW!$uJ#1!XMg`s*a3a+7O!4~lYi6}t^%!T&%F5!PLs0zRLd<~!#QNmPlYpH| zOG_>Ve*XNq_5S_)e06n|b#--FcI?T)L-n{-OFRIaxZKdt5U|tXaJcZF zK7DF^{P;1^j=g&I%Cc+MuBxk7ue#oO_wHR#{7@U9fV#fjr0)UL*6z-f*xA|1Q333W zi;Hh>-n?n_=t6F8Zh1#Xhtv&FeERe$Ed18iR*nilQ+TV>>5Lv-fH;=7wY61CIevo4 zWO9n4=yQGk{Mm`@_5~73;+$5io$`MF#fukCv@50HKYRAfiLCluD0$8c7cTS#5=z3q zd-v{?_xqqkQ2bybs3f)D7{qZLeZL*W&ezf%dZE~;6$n8>_LbS$*?x`0aU6X=d}QB+ z55e}qhi3?iFNaM?JS(zkXduJf>2+d-v|#>gsBNp5X*t_(+nGtZ<-MUS1~62#O!744P2aHCVU+ z(_@DEH+c*Xi495=WkN)|BG6!zCb}7Zf%YcUbqy9SfJ$c6_9nNeF}OnothfO26YJ{g z0^&C|HipCx>IBI_$z-y%+1c4|m;?vM6y*0Qz(dL~$0TY|^ULA-e8v)GumJ||V;X4? z{`&g*+Lte1zNMz7a!^b`exD*aIhmo%@edz9)WZ9hZxmmm49aLod|4P=d;or#j~+eB zJ?6#}Hk&E)wMa@>_>%q*K_$=}Ja|y>h#$(M2%4Di@pt0wR{tS_N}xG% 1) { var ctx = canvas.getCtx(); var x = (pos.x + posChild.x) / 2; @@ -400,7 +410,7 @@ Metamaps.JIT = { ctx = canvas.getCtx(); // if the topic is selected draw a circle around it - if (node.selected) { + if (!canvas.denySelected && node.selected) { ctx.beginPath(); ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false); ctx.strokeStyle = Metamaps.Settings.colors.topics.selected; @@ -1595,12 +1605,10 @@ Metamaps.JIT = { ctx.lineTo(v2.x, v2.y); ctx.stroke(); }, // renderMidArrow - renderEdgeArrows: function (edgeHelper, adj, synapse) { + renderEdgeArrows: function (edgeHelper, adj, synapse, canvas) { var self = Metamaps.JIT; - var canvas = Metamaps.Visualize.mGraph.canvas; - var directionCat = synapse.get('category'); var direction = synapse.getDirection(); @@ -1657,8 +1665,7 @@ Metamaps.JIT = { Metamaps.Visualize.mGraph.canvas.scale(0.8,0.8); $(document).trigger(Metamaps.JIT.events.zoom, [event]); }, - centerMap: function () { - var canvas = Metamaps.Visualize.mGraph.canvas; + centerMap: function (canvas) { var offsetScale = canvas.scaleOffsetX; canvas.scale(1/offsetScale,1/offsetScale); @@ -1674,7 +1681,8 @@ Metamaps.JIT = { eX = Metamaps.Mouse.boxEndCoordinates.x, eY = Metamaps.Mouse.boxEndCoordinates.y; - Metamaps.JIT.centerMap(); + var canvas = Metamaps.Visualize.mGraph.canvas; + Metamaps.JIT.centerMap(canvas); var height = $(document).height(), width = $(document).width(); @@ -1686,8 +1694,6 @@ Metamaps.JIT = { var newRatio = Math.min(ratioX,ratioY); - var canvas = Metamaps.Visualize.mGraph.canvas; - if(canvas.scaleOffsetX *newRatio<= 5 && canvas.scaleOffsetX*newRatio >= 0.2){ canvas.scale(newRatio,newRatio); } @@ -1711,15 +1717,14 @@ Metamaps.JIT = { Metamaps.Visualize.mGraph.plot(); }, - zoomExtents: function (event) { - Metamaps.JIT.centerMap(); - var height = $(document).height(), - width = $(document).width(), + zoomExtents: function (event, canvas, denySelected) { + Metamaps.JIT.centerMap(canvas); + var height = canvas.getSize().height, + width = canvas.getSize().width, maxX, minX, maxY, minY, counter = 0; - var canvas = Metamaps.Visualize.mGraph.canvas; - if (Metamaps.Selected.Nodes.length > 0) { + if (!denySelected && Metamaps.Selected.Nodes.length > 0) { var nodes = Metamaps.Selected.Nodes; } else { diff --git a/app/assets/javascripts/src/Metamaps.Router.js b/app/assets/javascripts/src/Metamaps.Router.js index aa4fa338..466326d2 100644 --- a/app/assets/javascripts/src/Metamaps.Router.js +++ b/app/assets/javascripts/src/Metamaps.Router.js @@ -125,7 +125,7 @@ if (Metamaps.Visualize.mGraph) { Metamaps.Visualize.mGraph.graph.empty(); Metamaps.Visualize.mGraph.plot(); - Metamaps.JIT.centerMap(); + Metamaps.JIT.centerMap(Metamaps.Visualize.mGraph.canvas); } Metamaps.Famous.viz.show(); Metamaps.Topic.end(); @@ -156,7 +156,7 @@ if (Metamaps.Visualize.mGraph) { Metamaps.Visualize.mGraph.graph.empty(); Metamaps.Visualize.mGraph.plot(); - Metamaps.JIT.centerMap(); + Metamaps.JIT.centerMap(Metamaps.Visualize.mGraph.canvas); } Metamaps.Famous.viz.show(); Metamaps.Map.end(); diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index 2543f0df..7e03ea4e 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -3384,7 +3384,7 @@ Metamaps.Listeners = { case 69: //if e or E is pressed if (e.ctrlKey){ e.preventDefault(); - Metamaps.JIT.zoomExtents(); + Metamaps.JIT.zoomExtents(null, Metamaps.Visualize.mGraph.canvas); } break; case 77: //if m or M is pressed @@ -4203,6 +4203,130 @@ Metamaps.Map = { Metamaps.Map.sideLength = 1; Metamaps.Map.timeToTurn = 0; Metamaps.Map.turnCount = 0; + }, + exportImage: function() { + + var canvas = {}; + + canvas.canvas = document.createElement("canvas"); + canvas.canvas.width = 1880; // 960; + canvas.canvas.height = 1260; // 630 + + canvas.scaleOffsetX = 1; + canvas.scaleOffsetY = 1; + canvas.translateOffsetY = 0; + canvas.translateOffsetX = 0; + canvas.denySelected = true; + + canvas.getSize = function() { + if(this.size) return this.size; + var canvas = this.canvas; + return this.size = { + width: canvas.width, + height: canvas.height + }; + }; + canvas.scale = function(x, y) { + var px = this.scaleOffsetX * x, + py = this.scaleOffsetY * y; + var dx = this.translateOffsetX * (x -1) / px, + dy = this.translateOffsetY * (y -1) / py; + this.scaleOffsetX = px; + this.scaleOffsetY = py; + this.getCtx().scale(x, y); + this.translate(dx, dy); + }; + canvas.translate = function(x, y) { + var sx = this.scaleOffsetX, + sy = this.scaleOffsetY; + this.translateOffsetX += x*sx; + this.translateOffsetY += y*sy; + this.getCtx().translate(x, y); + }; + canvas.getCtx = function() { + return this.canvas.getContext("2d"); + }; + // center it + canvas.getCtx().translate(1880/2, 1260/2); + + var mGraph = Metamaps.Visualize.mGraph; + + var id = mGraph.root; + var root = mGraph.graph.getNode(id); + var T = !!root.visited; + + // pass true to avoid basing it on a selection + Metamaps.JIT.zoomExtents(null, canvas, true); + + var c = canvas.canvas, + ctx = canvas.getCtx(), + scale = canvas.scaleOffsetX; + + // draw a grey background + ctx.fillStyle = '#d8d9da'; + var xPoint = (-(c.width/scale)/2) - (canvas.translateOffsetX/scale), + yPoint = (-(c.height/scale)/2) - (canvas.translateOffsetY/scale); + ctx.fillRect(xPoint,yPoint,c.width/scale,c.height/scale); + + // draw the graph + mGraph.graph.eachNode(function(node) { + var nodeAlpha = node.getData('alpha'); + node.eachAdjacency(function(adj) { + var nodeTo = adj.nodeTo; + if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) { + mGraph.fx.plotLine(adj, canvas); + } + }); + if(node.drawn) { + mGraph.fx.plotNode(node, canvas); + } + if(!mGraph.labelsHidden) { + if(node.drawn && nodeAlpha >= 0.95) { + mGraph.labels.plotLabel(canvas, node); + } else { + mGraph.labels.hideLabel(node, false); + } + } + node.visited = !T; + }); + + var imageData = { + encoded_image: canvas.canvas.toDataURL() + }; + + console.log(imageData.encoded_image); + var map = Metamaps.Active.Map; + + var today = new Date(); + var dd = today.getDate(); + var mm = today.getMonth()+1; //January is 0! + var yyyy = today.getFullYear(); + if(dd<10) { + dd='0'+dd + } + if(mm<10) { + mm='0'+mm + } + today = mm+'/'+dd+'/'+yyyy; + + var downloadMessage = ""; + downloadMessage += "Captured map screenshot! "; + downloadMessage += "DOWNLOAD"; + Metamaps.GlobalUI.notifyUser(downloadMessage); + + $.ajax({ + type: "POST", + dataType: 'json', + url: "/maps/" + Metamaps.Active.Map.id + "/upload_screenshot", + data: imageData, + success: function (data) { + console.log('successfully uploaded map screenshot'); + }, + error: function () { + console.log('failed to save map screenshot'); + } + }); } }; diff --git a/app/assets/stylesheets/clean.css b/app/assets/stylesheets/clean.css index 5ebcb763..847ec0ed 100644 --- a/app/assets/stylesheets/clean.css +++ b/app/assets/stylesheets/clean.css @@ -763,6 +763,18 @@ background-position: 0 0; cursor:pointer; } +.takeScreenshot { + margin-bottom: 5px; + border-radius: 2px; + background-image: url(screenshot_sprite.png); + display: none; +} +.takeScreenshot:hover { + background-position: -32px 0; +} +.canEditMap .takeScreenshot { + display: block; +} .zoomExtents { margin-bottom:5px; border-radius: 2px; @@ -883,6 +895,10 @@ line-height:14px; } +.toast a { + color: #4fc059; +} + /* end toast */ /* feedback */ diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index 0fbad24b..fbf34abc 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -1,6 +1,6 @@ class MapsController < ApplicationController - before_filter :require_user, only: [:create, :update, :destroy] + before_filter :require_user, only: [:create, :update, :screenshot, :destroy] respond_to :html, :json @@ -183,6 +183,30 @@ class MapsController < ApplicationController end end + # POST maps/:id/upload_screenshot + def screenshot + @current = current_user + @map = Map.find(params[:id]).authorize_to_edit(@current) + + if @map + png = Base64.decode64(params[:encoded_image]['data:image/png;base64,'.length .. -1]) + StringIO.open(png) do |data| + data.class.class_eval { attr_accessor :original_filename, :content_type } + data.original_filename = "map-" + @map.id.to_s + "-screenshot.png" + data.content_type = "image/png" + @map.screenshot = data + end + + if @map.save + render :json => {:message => "Successfully uploaded the map screenshot."} + else + render :json => {:message => "Failed to upload image."} + end + else + render :json => {:message => "Unauthorized to set map screenshot."} + end + end + # DELETE maps/:id def destroy @current = current_user diff --git a/app/models/map.rb b/app/models/map.rb index fcfefc81..91bc0a11 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -8,12 +8,12 @@ class Map < ActiveRecord::Base has_many :topics, :through => :topicmappings has_many :synapses, :through => :synapsemappings - after_touch :save_screenshot + #after_touch :save_screenshot # This method associates the attribute ":image" with a file attachment has_attached_file :screenshot, :styles => { - :thumb => ['188x126#', :png], - :full => ['940x630#', :png] + :thumb => ['188x126#', :png] + #:full => ['940x630#', :png] }, :default_url => "/assets/missing-map.png" diff --git a/app/views/layouts/_lowermapelements.html.erb b/app/views/layouts/_lowermapelements.html.erb index c69d475f..42b1820a 100644 --- a/app/views/layouts/_lowermapelements.html.erb +++ b/app/views/layouts/_lowermapelements.html.erb @@ -1,4 +1,5 @@
+
diff --git a/config/routes.rb b/config/routes.rb index 5031b431..edc19576 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -30,6 +30,7 @@ Metamaps::Application.routes.draw do match 'maps/topics/:id', to: 'maps#index', via: :get, as: :topicmaps resources :maps, except: [:new, :edit] match 'maps/:id/contains', to: 'maps#contains', via: :get, as: :contains + match 'maps/:id/upload_screenshot', to: 'maps#screenshot', via: :post, as: :screenshot devise_for :users, controllers: { registrations: 'users/registrations', passwords: 'users/passwords', sessions: 'devise/sessions' }, :skip => [:sessions]