--- a/client/css/renkan.css Fri May 16 17:36:21 2014 +0200
+++ b/client/css/renkan.css Sun May 18 10:31:12 2014 +0200
@@ -178,6 +178,22 @@
background-position: -172px 0;
}
+.Rk-Export-Button {
+ width: 30px; background-position: -274px 0;
+}
+
+.Rk-Export-Button.disabled {
+ opacity: .5; cursor: default;
+}
+
+.Rk-Export-Button:hover {
+ background-position: -274px -35px;
+}
+
+.Rk-Export-Button.disabled:hover {
+ opacity: 1; background-position: -274px 0;
+}
+
.Rk-Bookmarklet-Button {
width: 30px; background-position: -138px 0;
}
--- a/client/gruntfile.js Fri May 16 17:36:21 2014 +0200
+++ b/client/gruntfile.js Sun May 18 10:31:12 2014 +0200
@@ -34,7 +34,8 @@
paths: {
requtils: "require-utils",
jquery: "empty:",
- underscore: "empty:"
+ underscore: "empty:",
+ filesaver: "empty:"
}
}
}
--- a/client/js/defaults.js Fri May 16 17:36:21 2014 +0200
+++ b/client/js/defaults.js Sun May 18 10:31:12 2014 +0200
@@ -1,5 +1,5 @@
Rkns.defaults = {
-
+
language: (navigator.language || navigator.userLanguage || "en"),
/* GUI Language */
container: "renkan",
@@ -38,13 +38,14 @@
autoscale_padding: 50,
default_view: false,
/* Allows to load default view (zoom+offset) at start on read_only mode, instead of autoScale. default_view has to be an integer 0,1,2... */
-
+
/* TOP BAR BUTTONS */
show_search_field: true,
show_user_list: true,
user_name_editable: true,
user_color_editable: true,
show_save_button: true,
+ show_export_button: true,
show_open_button: false,
show_addnode_button: true,
show_addedge_button: true,
@@ -52,9 +53,9 @@
show_fullscreen_button: true,
home_button_url: false,
home_button_title: "Home",
-
+
/* MINI-MAP OPTIONS */
-
+
show_minimap: true,
/* Show a small map at the bottom right */
minimap_width: 160,
@@ -64,15 +65,15 @@
minimap_border_color: "#cccccc",
minimap_highlight_color: "#ffff00",
minimap_highlight_weight: 5,
-
+
/* EDGE/NODE COMMON OPTIONS */
-
+
buttons_background: "#202020",
buttons_label_color: "#c000c0",
buttons_label_font_size: 9,
-
+
/* NODE DISPLAY OPTIONS */
-
+
show_node_circles: true,
/* Show circles for nodes */
clip_node_images: true,
@@ -91,9 +92,9 @@
/* Maximum displayed text length */
label_untitled_nodes: "(untitled)",
/* Label to display on untitled nodes */
-
+
/* EDGE DISPLAY OPTIONS */
-
+
edge_stroke_width: 2,
selected_edge_stroke_width: 4,
edge_label_distance: 0,
@@ -102,9 +103,9 @@
edge_arrow_width: 12,
edge_gap_in_bundles: 12,
label_untitled_edges: "",
-
+
/* CONTEXTUAL DISPLAY (TOOLTIP OR EDITOR) OPTIONS */
-
+
tooltip_width: 275,
tooltip_padding: 10,
tooltip_margin: 15,
@@ -114,9 +115,9 @@
tooltip_bottom_color: "#d0d0d0",
tooltip_border_color: "#808080",
tooltip_border_width: 1,
-
+
/* NODE EDITOR OPTIONS */
-
+
show_node_editor_uri: true,
show_node_editor_description: true,
show_node_editor_size: true,
@@ -124,30 +125,30 @@
show_node_editor_image: true,
show_node_editor_creator: true,
uploaded_image_max_kb: 500,
-
+
/* NODE TOOLTIP OPTIONS */
-
+
show_node_tooltip_uri: true,
show_node_tooltip_description: true,
show_node_tooltip_color: true,
show_node_tooltip_image: true,
show_node_tooltip_creator: true,
-
+
/* EDGE EDITOR OPTIONS */
-
+
show_edge_editor_uri: true,
show_edge_editor_color: true,
show_edge_editor_direction: true,
show_edge_editor_nodes: true,
show_edge_editor_creator: true,
-
+
/* EDGE TOOLTIP OPTIONS */
-
+
show_edge_tooltip_uri: true,
show_edge_tooltip_color: true,
show_edge_tooltip_nodes: true,
show_edge_tooltip_creator: true
-
+
/* */
-
+
};
--- a/client/js/main-renderer.js Fri May 16 17:36:21 2014 +0200
+++ b/client/js/main-renderer.js Sun May 18 10:31:12 2014 +0200
@@ -5,6 +5,7 @@
paths: {
'jquery':'../lib/jquery.min',
'underscore':'../lib/underscore-min',
+ 'filesaver' :'../lib/FileSaver',
'requtils':'require-utils'
}
});
--- a/client/js/renderer/scene.js Fri May 16 17:36:21 2014 +0200
+++ b/client/js/renderer/scene.js Sun May 18 10:31:12 2014 +0200
@@ -1,5 +1,5 @@
-define(['jquery', 'underscore', 'requtils', 'renderer/miniframe'], function ($, _, requtils, MiniFrame) {
+define(['jquery', 'underscore', 'filesaver', 'requtils', 'renderer/miniframe'], function ($, _, filesaver, requtils, MiniFrame) {
'use strict';
var Utils = requtils.getUtils();
@@ -255,6 +255,7 @@
bindClick(".Rk-AddEdge-Button", "addEdgeBtn");
bindClick(".Rk-Save-Button", "save");
bindClick(".Rk-Open-Button", "open");
+ bindClick(".Rk-Export-Button", "exportProject");
this.$.find(".Rk-Bookmarklet-Button")
/*jshint scripturl:true */
.attr("href","javascript:" + Utils._BOOKMARKLET_CODE(_renkan))
@@ -455,6 +456,7 @@
'<div class="Rk-TopBar-Tooltip-Contents"><%-translate("Add Node")%></div></div></div><% } %>' +
'<% if (options.show_addedge_button) { %><div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-AddEdge-Button"><div class="Rk-TopBar-Tooltip">' +
'<div class="Rk-TopBar-Tooltip-Contents"><%-translate("Add Edge")%></div></div></div><% } %>' +
+ '<% if (options.show_export_button) { %><div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-Export-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents"><%-translate("Download Project")%></div></div></div><% } %>' +
'<% if (options.show_save_button) { %><div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-Save-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents"> </div></div></div><% } %>' +
'<% if (options.show_open_button) { %><div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-Open-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents"><%-translate("Open Project")%></div></div></div><% } %>' +
'<% if (options.show_bookmarklet) { %><div class="Rk-TopBar-Separator"></div><a class="Rk-TopBar-Button Rk-Bookmarklet-Button" href="#"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents">' +
@@ -1176,6 +1178,33 @@
}
return false;
},
+ exportProject: function() {
+ var projectJSON = this.renkan.project.toJSON(),
+ downloadLink = document.createElement("a"),
+ projectId = projectJSON.id,
+ fileNameToSaveAs = projectId + ".json";
+
+ // clean ids
+ delete projectJSON.id;
+ _.each(projectJSON.nodes, function(e,i,l) {
+ delete e._id;
+ delete e.id;
+ });
+ _.each(projectJSON.edges, function(e,i,l) {
+ delete e._id;
+ delete e.id;
+ });
+ _.each(projectJSON.views, function(e,i,l) {
+ delete e._id;
+ delete e.id;
+ });
+ projectJSON.users = [];
+
+ var projectJSONStr = JSON.stringify(projectJSON, null, 2);
+ var blob = new Blob([projectJSONStr], {type: "application/json;charset=utf-8"});
+ filesaver(blob,fileNameToSaveAs);
+
+ },
foldBins: function() {
var foldBinsButton = this.$.find(".Rk-Fold-Bins"),
bins = this.renkan.$.find(".Rk-Bins");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/client/lib/FileSaver.js Sun May 18 10:31:12 2014 +0200
@@ -0,0 +1,253 @@
+/*! FileSaver.js
+ * A saveAs() FileSaver implementation.
+ * 2014-01-24
+ *
+ * By Eli Grey, http://eligrey.com
+ * License: X11/MIT
+ * See LICENSE.md
+ */
+
+/*global self */
+/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
+
+var saveAs = saveAs
+ // IE 10+ (native saveAs)
+ || (typeof navigator !== "undefined" &&
+ navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
+ // Everyone else
+ || (function(view) {
+ "use strict";
+ // IE <10 is explicitly unsupported
+ if (typeof navigator !== "undefined" &&
+ /MSIE [1-9]\./.test(navigator.userAgent)) {
+ return;
+ }
+ var
+ doc = view.document
+ // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
+ , get_URL = function() {
+ return view.URL || view.webkitURL || view;
+ }
+ , URL = view.URL || view.webkitURL || view
+ , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
+ , can_use_save_link = !view.externalHost && "download" in save_link
+ , click = function(node) {
+ var event = doc.createEvent("MouseEvents");
+ event.initMouseEvent(
+ "click", true, false, view, 0, 0, 0, 0, 0
+ , false, false, false, false, 0, null
+ );
+ node.dispatchEvent(event);
+ }
+ , webkit_req_fs = view.webkitRequestFileSystem
+ , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
+ , throw_outside = function(ex) {
+ (view.setImmediate || view.setTimeout)(function() {
+ throw ex;
+ }, 0);
+ }
+ , force_saveable_type = "application/octet-stream"
+ , fs_min_size = 0
+ , deletion_queue = []
+ , process_deletion_queue = function() {
+ var i = deletion_queue.length;
+ while (i--) {
+ var file = deletion_queue[i];
+ if (typeof file === "string") { // file is an object URL
+ URL.revokeObjectURL(file);
+ } else { // file is a File
+ file.remove();
+ }
+ }
+ deletion_queue.length = 0; // clear queue
+ }
+ , dispatch = function(filesaver, event_types, event) {
+ event_types = [].concat(event_types);
+ var i = event_types.length;
+ while (i--) {
+ var listener = filesaver["on" + event_types[i]];
+ if (typeof listener === "function") {
+ try {
+ listener.call(filesaver, event || filesaver);
+ } catch (ex) {
+ throw_outside(ex);
+ }
+ }
+ }
+ }
+ , FileSaver = function(blob, name) {
+ // First try a.download, then web filesystem, then object URLs
+ var
+ filesaver = this
+ , type = blob.type
+ , blob_changed = false
+ , object_url
+ , target_view
+ , get_object_url = function() {
+ var object_url = get_URL().createObjectURL(blob);
+ deletion_queue.push(object_url);
+ return object_url;
+ }
+ , dispatch_all = function() {
+ dispatch(filesaver, "writestart progress write writeend".split(" "));
+ }
+ // on any filesys errors revert to saving with object URLs
+ , fs_error = function() {
+ // don't create more object URLs than needed
+ if (blob_changed || !object_url) {
+ object_url = get_object_url(blob);
+ }
+ if (target_view) {
+ target_view.location.href = object_url;
+ } else {
+ window.open(object_url, "_blank");
+ }
+ filesaver.readyState = filesaver.DONE;
+ dispatch_all();
+ }
+ , abortable = function(func) {
+ return function() {
+ if (filesaver.readyState !== filesaver.DONE) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+ , create_if_not_found = {create: true, exclusive: false}
+ , slice
+ ;
+ filesaver.readyState = filesaver.INIT;
+ if (!name) {
+ name = "download";
+ }
+ if (can_use_save_link) {
+ object_url = get_object_url(blob);
+ // FF for Android has a nasty garbage collection mechanism
+ // that turns all objects that are not pure javascript into 'deadObject'
+ // this means `doc` and `save_link` are unusable and need to be recreated
+ // `view` is usable though:
+ doc = view.document;
+ save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
+ save_link.href = object_url;
+ save_link.download = name;
+ var event = doc.createEvent("MouseEvents");
+ event.initMouseEvent(
+ "click", true, false, view, 0, 0, 0, 0, 0
+ , false, false, false, false, 0, null
+ );
+ save_link.dispatchEvent(event);
+ filesaver.readyState = filesaver.DONE;
+ dispatch_all();
+ return;
+ }
+ // Object and web filesystem URLs have a problem saving in Google Chrome when
+ // viewed in a tab, so I force save with application/octet-stream
+ // http://code.google.com/p/chromium/issues/detail?id=91158
+ if (view.chrome && type && type !== force_saveable_type) {
+ slice = blob.slice || blob.webkitSlice;
+ blob = slice.call(blob, 0, blob.size, force_saveable_type);
+ blob_changed = true;
+ }
+ // Since I can't be sure that the guessed media type will trigger a download
+ // in WebKit, I append .download to the filename.
+ // https://bugs.webkit.org/show_bug.cgi?id=65440
+ if (webkit_req_fs && name !== "download") {
+ name += ".download";
+ }
+ if (type === force_saveable_type || webkit_req_fs) {
+ target_view = view;
+ }
+ if (!req_fs) {
+ fs_error();
+ return;
+ }
+ fs_min_size += blob.size;
+ req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
+ fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
+ var save = function() {
+ dir.getFile(name, create_if_not_found, abortable(function(file) {
+ file.createWriter(abortable(function(writer) {
+ writer.onwriteend = function(event) {
+ target_view.location.href = file.toURL();
+ deletion_queue.push(file);
+ filesaver.readyState = filesaver.DONE;
+ dispatch(filesaver, "writeend", event);
+ };
+ writer.onerror = function() {
+ var error = writer.error;
+ if (error.code !== error.ABORT_ERR) {
+ fs_error();
+ }
+ };
+ "writestart progress write abort".split(" ").forEach(function(event) {
+ writer["on" + event] = filesaver["on" + event];
+ });
+ writer.write(blob);
+ filesaver.abort = function() {
+ writer.abort();
+ filesaver.readyState = filesaver.DONE;
+ };
+ filesaver.readyState = filesaver.WRITING;
+ }), fs_error);
+ }), fs_error);
+ };
+ dir.getFile(name, {create: false}, abortable(function(file) {
+ // delete file if it already exists
+ file.remove();
+ save();
+ }), abortable(function(ex) {
+ if (ex.code === ex.NOT_FOUND_ERR) {
+ save();
+ } else {
+ fs_error();
+ }
+ }));
+ }), fs_error);
+ }), fs_error);
+ }
+ , FS_proto = FileSaver.prototype
+ , saveAs = function(blob, name) {
+ return new FileSaver(blob, name);
+ }
+ ;
+ FS_proto.abort = function() {
+ var filesaver = this;
+ filesaver.readyState = filesaver.DONE;
+ dispatch(filesaver, "abort");
+ };
+ FS_proto.readyState = FS_proto.INIT = 0;
+ FS_proto.WRITING = 1;
+ FS_proto.DONE = 2;
+
+ FS_proto.error =
+ FS_proto.onwritestart =
+ FS_proto.onprogress =
+ FS_proto.onwrite =
+ FS_proto.onabort =
+ FS_proto.onerror =
+ FS_proto.onwriteend =
+ null;
+
+ view.addEventListener("unload", process_deletion_queue, false);
+ saveAs.unload = function() {
+ process_deletion_queue();
+ view.removeEventListener("unload", process_deletion_queue, false);
+ };
+ return saveAs;
+}(
+ typeof self !== "undefined" && self
+ || typeof window !== "undefined" && window
+ || this.content
+));
+// `self` is undefined in Firefox for Android content script context
+// while `this` is nsIContentFrameMessageManager
+// with an attribute `content` that corresponds to the window
+
+if (typeof module !== "undefined" && module !== null) {
+ module.exports = saveAs;
+} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
+ define([], function() {
+ return saveAs;
+ });
+}
--- a/server/src/main/webapp/static/js/config.js Fri May 16 17:36:21 2014 +0200
+++ b/server/src/main/webapp/static/js/config.js Sun May 18 10:31:12 2014 +0200
@@ -15,6 +15,7 @@
rcolor: 'static/lib/rcolor',
underscore: 'static/lib/underscore-min',
jquery: 'static/lib/jquery.min',
+ filesaver: 'static/lib/FileSaver',
},
packages:[{
name: 'dojo',