From 70fe60a45d0509cc75de0f6a56b6f58c0ed204b2 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Sat, 30 Jul 2016 11:06:46 -0500 Subject: [PATCH] Added 'deepforge.image(, )' support. Fixes #519 (#575) WIP #519 Added saving image file on worker WIP #519 Added wrapper for the intermediate data results WIP #519 filename->name and silenced blobClient logs WIP #519 Removed old debug log WIP #519 Added Image metadata creation WIP #519 Added ImageViewer WIP #519 Added zoom fn-ality WIP #519 Added 'Image' node WIP #519 Updated pipeline libraries WIP #519 Added 'image' to deepforge autocomplete WIP #519 Added no-image image WIP #519 Fixed image updating WIP #519 Added better origin url detection WIP #519 Fixed code climate issues WIP #519 fixed code climate issues --- src/common/Constants.js | 3 + src/plugins/ExecuteJob/ExecuteJob.js | 73 +++++++--- .../ExecuteJob/templates/deepforge.ejs | 18 +++ src/plugins/ExecuteJob/templates/index.js | 5 +- src/plugins/ExecuteJob/templates/start.ejs | 81 +++++++++++ src/seeds/cifar10/cifar10.webgmex | Bin 1374275 -> 1376738 bytes src/seeds/pipeline/pipeline.webgmex | Bin 88654 -> 90691 bytes src/seeds/project/project.webgmex | Bin 1276379 -> 1278839 bytes src/seeds/xor/xor.webgmex | Bin 1319207 -> 1321670 bytes src/visualizers/Visualizers.json | 6 + .../panels/ImageViewer/ImageViewerControl.js | 136 ++++++++++++++++++ .../panels/ImageViewer/ImageViewerPanel.js | 99 +++++++++++++ .../widgets/ImageViewer/ImageViewerWidget.js | 107 ++++++++++++++ .../widgets/ImageViewer/no-image.gif | Bin 0 -> 3765 bytes .../ImageViewer/styles/ImageViewerWidget.css | 5 + .../widgets/TextEditor/deepforge.json | 3 +- webgme-setup.json | 7 + 17 files changed, 524 insertions(+), 19 deletions(-) create mode 100644 src/plugins/ExecuteJob/templates/start.ejs create mode 100644 src/visualizers/panels/ImageViewer/ImageViewerControl.js create mode 100644 src/visualizers/panels/ImageViewer/ImageViewerPanel.js create mode 100644 src/visualizers/widgets/ImageViewer/ImageViewerWidget.js create mode 100644 src/visualizers/widgets/ImageViewer/no-image.gif create mode 100644 src/visualizers/widgets/ImageViewer/styles/ImageViewerWidget.css diff --git a/src/common/Constants.js b/src/common/Constants.js index 43b35694c..cd857e397 100644 --- a/src/common/Constants.js +++ b/src/common/Constants.js @@ -4,6 +4,9 @@ define({ // DeepForge metadata creation in dist execution START_CMD: 'deepforge-cmd', + + IMAGE: 'IMG', + GRAPH_CREATE: 'GRAPH', GRAPH_PLOT: 'PLOT', GRAPH_CREATE_LINE: 'LINE' diff --git a/src/plugins/ExecuteJob/ExecuteJob.js b/src/plugins/ExecuteJob/ExecuteJob.js index 07d91bd02..6a3868444 100644 --- a/src/plugins/ExecuteJob/ExecuteJob.js +++ b/src/plugins/ExecuteJob/ExecuteJob.js @@ -129,6 +129,8 @@ define([ name, id, idsToDelete = [], + type, + base, child; this.lastAppliedCmd[nodeId] = 0; @@ -142,9 +144,16 @@ define([ if (this.isMetaTypeOf(child, this.META.Metadata)) { id = this.core.getPath(child); name = this.core.getAttribute(child, 'name'); + base = this.core.getBase(child); + type = this.core.getAttribute(base, 'name'); this._markForDeletion[nodeId][id] = child; - this._oldMetadataByName[nodeId][name] = id; + // namespace by metadata type + if (!this._oldMetadataByName[nodeId][type]) { + this._oldMetadataByName[nodeId][type] = {}; + } + + this._oldMetadataByName[nodeId][type][name] = id; // children of metadata nodes get deleted idsToDelete = idsToDelete @@ -314,13 +323,13 @@ define([ ); config = { - cmd: 'bash', - args: ['run.sh'], + cmd: 'node', + args: ['start.js'], outputInterval: OUTPUT_INTERVAL, resultArtifacts: outputs }; files['executor_config.json'] = JSON.stringify(config, null, 4); - files['run.sh'] = Templates.BASH; + files['start.js'] = _.template(Templates.START)(CONSTANTS); // Save the artifact // Remove empty hashes @@ -879,23 +888,14 @@ define([ ExecuteJob.prototype[CONSTANTS.GRAPH_CREATE] = function (job, id) { var graph, name = Array.prototype.slice.call(arguments, 2).join(' '), - jobId = this.core.getPath(job), - oldMetadata = this._oldMetadataByName[jobId], - oldId; + jobId = this.core.getPath(job); id = jobId + '/' + id; this.logger.info(`Creating graph ${id} named ${name}`); // Check if the graph already exists - if (oldMetadata && oldMetadata[name]) { - oldId = oldMetadata[name]; - graph = this._markForDeletion[jobId][oldId]; - - // Reset points - this.core.setAttribute(graph, 'points', ''); - - delete this._markForDeletion[jobId][oldId]; - } else { // create new graph + graph = this._getExistingMetadata(jobId, 'Graph', name); + if (!graph) { graph = this.core.createNode({ base: this.META.Graph, parent: job @@ -948,5 +948,46 @@ define([ this._metadata[jobId + '/' + id] = line; }; + ExecuteJob.prototype[CONSTANTS.IMAGE] = function (job, hash) { + var jobId = this.core.getPath(job), + name = Array.prototype.slice.call(arguments, 2).join(' '), + id = jobId + '/IMAGE/' + name, + imageNode = this._metadata[id]; // Look for the metadata imageNode + + id = jobId + '/' + id; + this.logger.info(`Creating graph ${id} named ${name}`); + + if (!imageNode) { + + // Check if the imageNode already exists + imageNode = this._getExistingMetadata(jobId, 'Image', name); + if (!imageNode) { + imageNode = this.core.createNode({ + base: this.META.Image, + parent: job + }); + this.core.setAttribute(imageNode, 'name', name); + } + this._metadata[id] = imageNode; + } + + this.core.setAttribute(imageNode, 'data', hash); + }; + + ExecuteJob.prototype._getExistingMetadata = function (jobId, type, name) { + var oldMetadata = this._oldMetadataByName[jobId] && + this._oldMetadataByName[jobId][type], + node, + id; + + if (oldMetadata && oldMetadata[name]) { + id = oldMetadata[name]; + node = this._markForDeletion[jobId][id]; + delete this._markForDeletion[jobId][id]; + } + + return node || null; + }; + return ExecuteJob; }); diff --git a/src/plugins/ExecuteJob/templates/deepforge.ejs b/src/plugins/ExecuteJob/templates/deepforge.ejs index d5d647abe..29f2e84b6 100644 --- a/src/plugins/ExecuteJob/templates/deepforge.ejs +++ b/src/plugins/ExecuteJob/templates/deepforge.ejs @@ -19,6 +19,7 @@ function deepforge._cmd(...) print(cmd) end +-- Graph support Graph = torch.class('deepforge.Graph') function Graph:__init(name) @@ -43,4 +44,21 @@ function Graph:line(name, opts) return deepforge._Line(self.id, name, opts) end +-- Image support +function deepforge.image(name, tensor) + require 'image' + require 'paths' + + -- save it in the tmp directory + local filename = name .. '.png' + local path = paths.concat('metadata', filename) + + if paths.dir('metadata') == nil then + paths.mkdir('metadata') + end + + image.save(path, tensor) + deepforge._cmd("<%= IMAGE %>", name) +end + return deepforge diff --git a/src/plugins/ExecuteJob/templates/index.js b/src/plugins/ExecuteJob/templates/index.js index fdbfb5e8a..2da9b3cff 100644 --- a/src/plugins/ExecuteJob/templates/index.js +++ b/src/plugins/ExecuteJob/templates/index.js @@ -1,11 +1,13 @@ /*globals define*/ define([ + 'text!./start.ejs', 'text!./entry.ejs', 'text!./main.ejs', 'text!./deepforge.ejs', 'text!./serialize.ejs', 'text!./deserialize.ejs' ], function( + START, ENTRY, MAIN, DEEPFORGE, @@ -13,9 +15,8 @@ define([ DESERIALIZE ) { - var BASH = 'th init.lua 2>&1'; return { - BASH, + START, ENTRY, MAIN, SERIALIZE, diff --git a/src/plugins/ExecuteJob/templates/start.ejs b/src/plugins/ExecuteJob/templates/start.ejs new file mode 100644 index 000000000..21757fe07 --- /dev/null +++ b/src/plugins/ExecuteJob/templates/start.ejs @@ -0,0 +1,81 @@ +// A wrapper for the torch script which: +// - merges stdout, stderr +// - receives some commands and uploads intermediate data +var spawn = require('child_process').spawn, + fs = require('fs'), + log = console.error, + logger = {}; + +// Create the stderr only logger +['error', 'warn', 'info', 'log', 'debug'].forEach(method => logger[method] = log); + +// Get the BlobClient... +var COMMAND_PREFIX = '<%= START_CMD %>', + IMAGE = '<%= IMAGE %>', + requirejs = require('webgme').requirejs; + +requirejs([ + 'blob/BlobClient' +], function( + BlobClient +) { + var url = process.env.ORIGIN_URL || 'http://127.0.0.1:8888', + protocol = url.split('://').shift(), + address, + port = (url.split(':') || ['80']).pop(); + + address = url.replace(protocol + '://', '') + .replace(':' + port, ''); + + var blobClient = new BlobClient({ + server: address, + httpsecure: protocol === 'https', + serverPort: port, + logger: logger + }); + + var uploadImage = function(line) { + var args = line.split(/\s+/), + name = args.slice(2).join(' ').replace(/\s+$/, ''), + filename = 'metadata/' + name + '.png'; + + // Upload the image from metadata/ + fs.readFile(filename, (err, content) => { + if (err) { + console.error(`Could not read ${filename}: ${err}`); + return; + } + + // Add hash to the image command + blobClient.putFile(filename, content) + .then(hash => { + args.splice(2, 0, hash); + console.log(args.join(' ')); + }) + .fail(err => console.error(`${filename} upload failed: ${err}`)); + }); + }; + + var onStdout = function(data) { + var lines = data.toString().split('\n'), + result = [], + cmdStart; + + // Check for commands... + for (var i = 0; i < lines.length; i++) { + cmdStart = lines[i].indexOf(COMMAND_PREFIX); + if (cmdStart !== -1 && lines[i].indexOf(IMAGE) !== -1) { + uploadImage(lines[i]); + } else { + result.push(lines[i]); + } + } + + process.stdout.write(result.join('\n')); + }; + + // Run 'th init.lua' and merge the stdout, stderr + var job = spawn('th', ['init.lua']); + job.stdout.on('data', onStdout); + job.stderr.on('data', data => process.stdout.write(data)); +}); diff --git a/src/seeds/cifar10/cifar10.webgmex b/src/seeds/cifar10/cifar10.webgmex index c947f63a31e7d73d891dbdeb4ab5ae1dd2af08eb..2e86ade17f08d742fff90217c2be52823b0ebd38 100644 GIT binary patch delta 1535 zcma)+U1%It7=|;mlg;L**_qu*X__CKn3!#9oAWbs&WsYft)Qt1WP_>2D$@BiX)U%| z+6yghO=yq?O&sk<&|(pkP`rq&I3OZ~fY6JIy(mJdx~Tz0EVST-7E#Zp!9uQf;9(ff ze24SC-+8~K+fUz|7;P>LCX~9W@)JGr&wz3J?&z4*R3(^g?#?-ONVsb=hOiUZc8HM8 z4NAPwastYc=ZC&!Gbfl0_x2!WFz#b&JB)aiZ+UhgDv29XLNT^6GacqJI(w5>ylu2g z`YOj=pKLD;p-z@-j#km-jW^^o|m zeV0?)AZB1vF65XI-kdG2D2HjzG+i1H$_$JUH!1UkRzD;N8@_LFixNyd!VrzLY!;5s z>8$vsbxW)GSfCAQn<`ceg%g=2*wQQ27B3ABBqScqe$x!4fn+i|@T~%qAJ=Hn%#Z@7 ze@Z(O)5G!fuoOQWZTvh}VOiBY&t@DyV79<3U zpjwa=)Cf|7T0xzlUeF-eAZUzR=hMfxRqopVK17!WFUP~z(ibR{{!+F=zFO^0FvEUt zLF$#C?JFyMw6+gZQ(Qz7L&w?rd-B)Ta^-hfJS?;(`e7lVK2#&BvCR-%l@*#=D@34u zT9aM%c4kRKAWU)KV6D2)!?1@ei@ClT8lj0f^N39`b37yT4a;C|5KR9I^TRI;U3GqZ?5cY}1nQL=qW5?qLat%+!!3e||TpvC=RoztX0xKJ@d~s6hkl;h6 zcE{}v>ei%8@cORIXVl(I^1j#$GC^=>znqf^1N^lV#@}_RXYx>Tc?dxrj8Q0#5O8a&A;D*|=8lQN2T+7IC@toFFal%*6H???36J!M0c=3>4+}=~+ zgb?fo92;6(5WEKg?l ll{eW}?olJ;pUiHHkIm`lUP!MS>CpJ6-j{qw%=fCQ{{RI#*meK_ delta 990 zcma*mUq}=|7y$6wz4g>n@Ahs_(NO!Ro?7D0?(EDgz9) zLMWoneVC^p2?PnFIB+Q-$5!Xl9fd)Ycc=;7v^tC~c^sL39o=KqVIX=PE z)0yq*B7p5ss=ELI8zF2F8Z5VD3Rup`S%!hlZ0760^C^W62Cl^@F&yX+gJI%Q;sDbL zK)qJ@qMS1eH%N23wYVrI;nO2(ZP5vYx39>9QZPQyUYP40lV!V;=SQQBLB7A5-#a5I z{LO;W#r54%bMW*?d}6ULT*W^1Y(hXa${FZe+Y+Bf}Ool0Tuy2^I4}tD@h!I9?3yaynfIohj$!wIMgm)#> z@~mb0K=cgrs0#sfOePjC@dz|M*CGJvkm?4+!Sqsw-&xQQSBb2IPZH`-sRZ~qOaD`p zP!wf zqf&S+r_Ly?MK+Sl^SM-0uzDyKULH~Zd@TN4e?uCDZ;z#ndj4aVl-lfwV7NESPY$_hb2-RNWc-F1~Lh@)t8ANpt`J diff --git a/src/seeds/pipeline/pipeline.webgmex b/src/seeds/pipeline/pipeline.webgmex index e0474e7bd7791e3e1a3488f5e4c1c001fbcf23c3..6dfaeb32cc9f01a672d25b289c25468c7f953136 100644 GIT binary patch delta 961 zcma)5O-~b16z%JWr9eO_AR^SZhEj0B`<^#QA}A5m4T-U)Y-DCSgBm5k23H27OBdE_ z91~*#F>2hI42f|iL@=Pig?~VzE({w0np57fD{i^zEX%(xjDmm9pb;o+ueVf6_IFf!v zFD&L9ul>=faG6kski0QgRx@3X6ih#hdXJyXMS82vBV7wCi8xHGAM~83Gm)xubZ=#l z@c2vC0l{M(zLynax?y{@8$PVv@ndMN7(3V>^ZMU61>N2As1Z-4`;+OT)#hJO=S{4C zxm)$jl$9MZG|RCyC3V9r&7-1NQOlo4Z0cNdI1IHk!>iA+@R{qztQAw+{D3J<7Yu4ANhpxPU%}8}@Fc+cu8y LzF+=^1_FNpirXRd delta 525 zcmX?ng!SAOR*3*_W)?065V+F(&Ewo=!@Mhzj0{&ICrYSkm>Q=Vnwh4hr6eVr7@8y+ zrvW+17AY1c#wp3B7KUa9CP@ZHCgz3{)50{8Q_PJ`%~MQ`)6A316U|eNjFVGM3=I-f z%+f5bmJ<}Hj1nK$>bE@mX!{LKa{au_EYh^TLV!!h4@dO#E--{kiT`KJqT zGOBJqZTE_u$|m5A2NasJb3m*&*ZQtn%iSz85gnep~moZ-Xul?wtv9bW?%pS-Da|1 diff --git a/src/seeds/project/project.webgmex b/src/seeds/project/project.webgmex index 908e01ebfd64d04b106560f0bba85f8cfdcbea58..705de3aacb7019478861829f39057e36760c3ca6 100644 GIT binary patch delta 1478 zcma)+U1%Le6vw-pz4zuOwY&FjB9SB|A5HFUB4oaHX1Ary6;V@)nm*W?2TgW&c58)} z=!;aV&?pvBr0GHrzJx|vMf;L+wOgdpq$1QlNJHN&+DKjmDNU;weW*A|3O0SZ1HXqE z{&Se~pL5QQKb~EF|GAdo-E~I8hL!c^)Bg?)PCWDFy$O56y@|<|-m)(l6%i)X<2d$7 z#5wiDh`2uYV=N@aScZ}~bTWRS>^nSmFh&CVE_ErF4iAVMsja{sqcUJ5Mjmq~7kSNb zlP9!`Wf4d&gijd>IFF={BN7lvg>*bWMzM=g;Kr_`)gHa5mwki-*NFn^Qy)1p4rz$g z#*=}d9wScVP{BEJs-s4Isq8objR@roBP1NkLxg=96NH(ROt_R_8WN6Zb#MQBbv3^P z2CrK!H75y6KUx6Chpghsr}W>GYkQ39Pw0rQiDdfE$6)wYF`IOsZ9ahvIQ3WE_GA_h zRR5v7Zzt!6nqd4By*VS4=?>iaLTiQb+1$pY<68qh4Xtvrbishm&#l&EobSl7RhgU2 z@9cw)&}xU7kBYrextZIZUhdX@$!a8>?$f@@WY>f%@seW_X$oCYr!W*5MOKkh)GP9e z21TP{qv8?8rc@&Hz)0` zh+LNk(utX>X~9F{QOy02#ge&<2T@FM93-75Tj1q|Ttjc{sdm1EcBU6|=9Hy`R{* zl0Ky&*rQuzjlj7d%>8NO4fAM4b71bBLMx1YpU;CO>a$RI+}aG+D*2%Y44cw1e?T`* z>oXevUzu)hvz|28g?d;5GhgM0S6xcSI<51Wt+h5-7fjoQ{`A$7b?a=+T?wz9F7Wj0 z>lSHCw$ZkgGPYIaC)UnV^3uh2s4Q6@Ckwm@u7;|tt*Lza&5~8rVd|i54jk&**3s2n zYfG=a@7VHPOHoLd?-qZ1t5oA<7(ZPQn?)E$!sVVHtV|l0AeV${(xAX&B4#juxY%y& zI_U3*xg*6#A>Cq6=#0Wq&+3J-9@~NEh28>#S4^{UMHTHm5WNw-4uw|=`(RhEy$@yw aZ7;of$iC39Y5i!1Gly-IExl{2q5U75h|QV+ delta 1073 zcmb7^O=uHA6vw-pH8HVvcatKK`cVV1v5B}l`!TavKPs(?T8Ss|qR#H@K=J6MqM#@$ ztsfW`c^4^q(Sw&lz^$ODh@N_=M=5AiFGbW+gsKSQL=WOc;=qq#9?bi{dGqIexpZ*m z^H9FJKcQsWnrn3M``*dJ1E1-sTpK-A%Wo_r%SAS(*fm`faT0LLb1=1R(?G}wu;uui zhlJolB?C{NtE+40y;8`0Y%<0SjuxCy5)gtWdle=MhMqdX3;u)dM%i_=BvO;R%8&IzG^Zc6Qs z_f#ad?D7DT1hOC@Pz23bQjiiX7o-Il!3sgUV5OiV9zg2wk#g&e%fLSy5f5Ke$1RvW zudn@6G+cPDPLu*B?1q_RaqJ66hR6y+;X}$?PB;;^6gv{196h_6haGdNbdO_&rt5Id z{J=JCYH@K02qU(|0*9EcVf%(~H|V!golQv>x+{_rKb}i%kYHg^Eynnzx+y7{F`ZJ! zvTO| p3Fby}<;(NmG=Uz^f6LCFj!%!|CNmv>PXXw^kTa53#6)Oo`w3-9SSA1f diff --git a/src/seeds/xor/xor.webgmex b/src/seeds/xor/xor.webgmex index eff55988db3f261f0d596fe8f393b25cd07878b2..41c1b13e7c69437c6ed3f29daa55781af708929c 100644 GIT binary patch delta 1504 zcma)+U1%It6vs1@+1>2gW@dLLlC-gA(|l~U#?GC4=gu9|>ZXasHnD7~sC_7AKBa&d}Kj+-@*2m>fzb-eIhw7zFUG;lo|BVNZxX)c5E^2kd#i{15f@>4(*-q&AKC^Y^ zFzPYiGpLJ+Z;c-WJq^e$b7t*7l*`xqa&A@fr}mN=%ycf z*wgus?YkDXiD3pNwkXxj#h0b$dkS0x4h&2Q(sj$k)YFmUhlY-b?-Pq*17R*#$BQ?5 zpKPc(5_qjqYpz-3;uWe%f)xpsmYK)Jgt*&=$rGXm^A8D9H2;osdb}?l?-RxwB7LBc zBA6g#TCkpxx?lQGg0Idg1t{r)1c#@UK~Ro~O{tA51o|5!^`;w`ctOoW`P(ds?;RAj zr34Bi7vvqVJ5*ZWue8`0&kYHs?!<=csbwKLvwBkCh@5(k#7S_HoD`>lljdYNw{f;` zwsIQd)syn8{WWKbYo8Wk{DC}+VQe*7gt5!=&ZI$!jcgdXo^FlG$F~SaVC9_B6O|d4 zW-^k~$(8-y5zZX}#-DiJbEqymwSgO9!5Eo`8HZ*94C(sHf`*M$zPG zCzMyRolsd37DS%vop;{pBoS<< z4q^%hU6jqHLq88u9RzE^mmp%dpi2^Y2ygxP2hv-IE{%Gs)4uTGWnP$hzC7?tngk)YhnRHOft8&&2n?7!FbM z6NXgF?^30M{Iyg&Q|rDlQ`w~*_k!$fBrjy2#S0A%h?>4-4MtZkWk0=cc+2<$i$u3u zEp$wYCSV=>?AmHUjTWqeI$g9D_oGF)*hAnJHU3--JIj6+Wx(d PYPm0GC${z7jK%%}*P$vX diff --git a/src/visualizers/Visualizers.json b/src/visualizers/Visualizers.json index d3e7d62a6..019889510 100644 --- a/src/visualizers/Visualizers.json +++ b/src/visualizers/Visualizers.json @@ -88,5 +88,11 @@ "title": "LineGraph", "panel": "panels/LineGraph/LineGraphPanel", "DEBUG_ONLY": false + }, + { + "id": "ImageViewer", + "title": "ImageViewer", + "panel": "panels/ImageViewer/ImageViewerPanel", + "DEBUG_ONLY": false } ] \ No newline at end of file diff --git a/src/visualizers/panels/ImageViewer/ImageViewerControl.js b/src/visualizers/panels/ImageViewer/ImageViewerControl.js new file mode 100644 index 000000000..2db80655f --- /dev/null +++ b/src/visualizers/panels/ImageViewer/ImageViewerControl.js @@ -0,0 +1,136 @@ +/*globals define, WebGMEGlobal*/ +/*jshint browser: true*/ + +define([ + 'blob/BlobClient', + 'js/Constants', + 'js/Utils/GMEConcepts', + 'js/NodePropertyNames' +], function ( + BlobClient, + CONSTANTS, + GMEConcepts, + nodePropertyNames +) { + + 'use strict'; + + var ImageViewerControl; + + ImageViewerControl = function (options) { + + this._logger = options.logger.fork('Control'); + + this._client = options.client; + + // Initialize core collections and variables + this._widget = options.widget; + this.blobClient = new BlobClient({ + logger: this._logger.fork('BlobClient') + }); + + this._currentNodeId = null; + + this._logger.debug('ctor finished'); + }; + + /* * * * * * * * Visualizer content update callbacks * * * * * * * */ + // One major concept here is with managing the territory. The territory + // defines the parts of the project that the visualizer is interested in + // (this allows the browser to then only load those relevant parts). + ImageViewerControl.prototype.selectedObjectChanged = function (nodeId) { + this._logger.debug('activeObject nodeId \'' + nodeId + '\''); + + // Remove current territory patterns + if (this._currentNodeId) { + this._client.removeUI(this._territoryId); + } + + this._currentNodeId = nodeId; + + if (typeof this._currentNodeId === 'string') { + // Put new node's info into territory rules + this._selfPatterns = {}; + this._selfPatterns[nodeId] = {children: 0}; // Territory "rule" + this._territoryId = this._client.addUI(this, this._eventCallback.bind(this)); + this._client.updateTerritory(this._territoryId, this._selfPatterns); + } + }; + + // This next function retrieves the relevant node information for the widget + ImageViewerControl.prototype._getObjectDescriptor = function (nodeId) { + var nodeObj = this._client.getNode(nodeId), + objDescriptor, + hash; + + if (nodeObj) { + objDescriptor = { + id: undefined, + name: undefined + }; + + objDescriptor.id = nodeObj.getId(); + objDescriptor.name = nodeObj.getAttribute(nodePropertyNames.Attributes.name); + // Get the blob url + hash = nodeObj.getAttribute('data'); + if (hash) { + objDescriptor.src = this.blobClient.getDownloadURL(hash); + } + } + + return objDescriptor; + }; + + /* * * * * * * * Node Event Handling * * * * * * * */ + ImageViewerControl.prototype._eventCallback = function (events) { + var i = events ? events.length : 0, + event; + + this._logger.debug('_eventCallback \'' + i + '\' items'); + + while (i--) { + event = events[i]; + switch (event.etype) { + + case CONSTANTS.TERRITORY_EVENT_LOAD: + this._onLoad(event.eid); + break; + case CONSTANTS.TERRITORY_EVENT_UPDATE: + this._onUpdate(event.eid); + break; + case CONSTANTS.TERRITORY_EVENT_UNLOAD: + this._onUnload(event.eid); + break; + default: + break; + } + } + + this._logger.debug('_eventCallback \'' + events.length + '\' items - DONE'); + }; + + ImageViewerControl.prototype._onUpdate = + ImageViewerControl.prototype._onLoad = function (gmeId) { + var description = this._getObjectDescriptor(gmeId); + this._widget.updateImage(description.src); + }; + + ImageViewerControl.prototype._onUnload = function (gmeId) { + this._widget.removeImage(gmeId); + }; + + /* * * * * * * * Visualizer life cycle callbacks * * * * * * * */ + ImageViewerControl.prototype.onActivate = function () { + if (typeof this._currentNodeId === 'string') { + WebGMEGlobal.State.registerSuppressVisualizerFromNode(true); + WebGMEGlobal.State.registerActiveObject(this._currentNodeId); + WebGMEGlobal.State.registerSuppressVisualizerFromNode(false); + } + }; + + ImageViewerControl.prototype.destroy = + ImageViewerControl.prototype.onDeactivate = function () { + }; + + return ImageViewerControl; +}); diff --git a/src/visualizers/panels/ImageViewer/ImageViewerPanel.js b/src/visualizers/panels/ImageViewer/ImageViewerPanel.js new file mode 100644 index 000000000..68e5e9cca --- /dev/null +++ b/src/visualizers/panels/ImageViewer/ImageViewerPanel.js @@ -0,0 +1,99 @@ +/*globals define, _, WebGMEGlobal*/ +/*jshint browser: true*/ +/** + * Generated by VisualizerGenerator 1.7.0 from webgme on Sat Jul 30 2016 07:12:17 GMT-0500 (CDT). + */ + +define(['js/PanelBase/PanelBaseWithHeader', + 'js/PanelManager/IActivePanel', + 'widgets/ImageViewer/ImageViewerWidget', + './ImageViewerControl' +], function (PanelBaseWithHeader, + IActivePanel, + ImageViewerWidget, + ImageViewerControl) { + 'use strict'; + + var ImageViewerPanel; + + ImageViewerPanel = function (layoutManager, params) { + var options = {}; + //set properties from options + options[PanelBaseWithHeader.OPTIONS.LOGGER_INSTANCE_NAME] = 'ImageViewerPanel'; + options[PanelBaseWithHeader.OPTIONS.FLOATING_TITLE] = true; + + //call parent's constructor + PanelBaseWithHeader.apply(this, [options, layoutManager]); + + this._client = params.client; + + //initialize UI + this._initialize(); + + this.logger.debug('ctor finished'); + }; + + //inherit from PanelBaseWithHeader + _.extend(ImageViewerPanel.prototype, PanelBaseWithHeader.prototype); + _.extend(ImageViewerPanel.prototype, IActivePanel.prototype); + + ImageViewerPanel.prototype._initialize = function () { + var self = this; + + //set Widget title + this.setTitle(''); + + this.widget = new ImageViewerWidget(this.logger, this.$el); + + this.widget.setTitle = function (title) { + self.setTitle(title); + }; + + this.control = new ImageViewerControl({ + logger: this.logger, + client: this._client, + widget: this.widget + }); + + this.onActivate(); + }; + + /* OVERRIDE FROM WIDGET-WITH-HEADER */ + /* METHOD CALLED WHEN THE WIDGET'S READ-ONLY PROPERTY CHANGES */ + ImageViewerPanel.prototype.onReadOnlyChanged = function (isReadOnly) { + //apply parent's onReadOnlyChanged + PanelBaseWithHeader.prototype.onReadOnlyChanged.call(this, isReadOnly); + + }; + + ImageViewerPanel.prototype.onResize = function (width, height) { + this.logger.debug('onResize --> width: ' + width + ', height: ' + height); + this.widget.onWidgetContainerResize(width, height); + }; + + /* * * * * * * * Visualizer life cycle callbacks * * * * * * * */ + ImageViewerPanel.prototype.destroy = function () { + this.control.destroy(); + this.widget.destroy(); + + PanelBaseWithHeader.prototype.destroy.call(this); + WebGMEGlobal.KeyboardManager.setListener(undefined); + WebGMEGlobal.Toolbar.refresh(); + }; + + ImageViewerPanel.prototype.onActivate = function () { + this.widget.onActivate(); + this.control.onActivate(); + WebGMEGlobal.KeyboardManager.setListener(this.widget); + WebGMEGlobal.Toolbar.refresh(); + }; + + ImageViewerPanel.prototype.onDeactivate = function () { + this.widget.onDeactivate(); + this.control.onDeactivate(); + WebGMEGlobal.KeyboardManager.setListener(undefined); + WebGMEGlobal.Toolbar.refresh(); + }; + + return ImageViewerPanel; +}); diff --git a/src/visualizers/widgets/ImageViewer/ImageViewerWidget.js b/src/visualizers/widgets/ImageViewer/ImageViewerWidget.js new file mode 100644 index 000000000..4d8ba1a63 --- /dev/null +++ b/src/visualizers/widgets/ImageViewer/ImageViewerWidget.js @@ -0,0 +1,107 @@ +/*globals define, $*/ +/*jshint browser: true*/ + +define([ + 'css!./styles/ImageViewerWidget.css' +], function ( +) { + 'use strict'; + + var ImageViewerWidget, + NO_IMAGE_URL = 'extlib/src/visualizers/widgets/ImageViewer/no-image.gif', + WIDGET_CLASS = 'image-viewer'; + + ImageViewerWidget = function (logger, container) { + this._logger = logger.fork('Widget'); + this.$el = container; + this._initialize(); + this._logger.debug('ctor finished'); + }; + + ImageViewerWidget.prototype._initialize = function () { + // set widget class + this.$el.addClass(WIDGET_CLASS); + this.zoom = 1; + this.left = 0; + this.top = 0; + this.width = 0; + this.height = 0; + this.img = { + width: 0, + height: 0 + }; + + this.$image = $(''); + this.$el.append(this.$image); + + this.$image.on('load', () => { + this.img.width = this.$image.width(); + this.img.height = this.$image.height(); + this.centerImage(); + }); + + this.updateImage(NO_IMAGE_URL); + + // Zoom functionality + this.$el[0].onwheel = event => { + if (event.ctrlKey || event.metaKey || event.altKey) { + var dz = -event.deltaY/20; + this.zoom += dz; + this.centerImage(); + event.stopPropagation(); + event.preventDefault(); + } + }; + }; + + ImageViewerWidget.prototype.centerImage = function () { + var left, + top; + + this.left = this.width/2 - (this.img.width*this.zoom/2); + this.top = this.height/2 - (this.img.height*this.zoom/2); + + left = this.left/this.zoom; + top = this.top/this.zoom; + this.$image.css({ + left: left, + top: top, + zoom: this.zoom + }); + }; + + ImageViewerWidget.prototype.onWidgetContainerResize = function (width, height) { + this.$el.css({ + width: width, + height: height + }); + this.width = width; + this.height = height; + this.centerImage(); + }; + + ImageViewerWidget.prototype.updateImage = function (url) { + url = url || NO_IMAGE_URL; + this.zoom = 1; + this.$image.attr('src', url); + }; + + ImageViewerWidget.prototype.removeImage = function () { + // Change to 'no picture' image + this.updateImage(NO_IMAGE_URL); + }; + + /* * * * * * * * Visualizer life cycle callbacks * * * * * * * */ + ImageViewerWidget.prototype.destroy = function () { + }; + + ImageViewerWidget.prototype.onActivate = function () { + this._logger.debug('ImageViewerWidget has been activated'); + }; + + ImageViewerWidget.prototype.onDeactivate = function () { + this._logger.debug('ImageViewerWidget has been deactivated'); + }; + + return ImageViewerWidget; +}); diff --git a/src/visualizers/widgets/ImageViewer/no-image.gif b/src/visualizers/widgets/ImageViewer/no-image.gif new file mode 100644 index 0000000000000000000000000000000000000000..dd462ccde3d47d5f2bacc322f61c4ad4dc735e8a GIT binary patch literal 3765 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 fj`^678JUtfnUq