cocoapods-dependsay 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +116 -0
- data/LICENSE.txt +22 -0
- data/README.md +11 -0
- data/Rakefile +13 -0
- data/cocoapods-dependsay.gemspec +27 -0
- data/lib/cocoapods-dependsay.rb +1 -0
- data/lib/cocoapods-dependsay/command.rb +1 -0
- data/lib/cocoapods-dependsay/command/dependsay.rb +214 -0
- data/lib/cocoapods-dependsay/gem_version.rb +3 -0
- data/lib/cocoapods_plugin.rb +1 -0
- data/public/css/application.css +113 -0
- data/public/javascript/application.js +205 -0
- data/public/javascript/toolbox.js +223 -0
- data/public/javascript/websocket.js +613 -0
- data/spec/command/dependsay_spec.rb +12 -0
- data/spec/spec_helper.rb +50 -0
- data/views/index.erb +30 -0
- data/views/toolbox.erb +67 -0
- metadata +128 -0
@@ -0,0 +1 @@
|
|
1
|
+
require 'cocoapods-dependsay/command'
|
@@ -0,0 +1,113 @@
|
|
1
|
+
html, body, .dependency_graph, .dependency_graph svg{
|
2
|
+
width: 100%;
|
3
|
+
height: 100%;
|
4
|
+
padding: 0px;
|
5
|
+
margin: 0px;
|
6
|
+
font-size: 13px;
|
7
|
+
overflow: hidden;
|
8
|
+
}
|
9
|
+
|
10
|
+
.link {
|
11
|
+
fill: none;
|
12
|
+
stroke: #666;
|
13
|
+
stroke-width: 1.5px;
|
14
|
+
}
|
15
|
+
|
16
|
+
circle {
|
17
|
+
fill: #fff;
|
18
|
+
stroke: #333;
|
19
|
+
stroke-width: 1.5px;
|
20
|
+
}
|
21
|
+
|
22
|
+
.circular {
|
23
|
+
stroke: #FF0000;
|
24
|
+
}
|
25
|
+
|
26
|
+
.fixed circle {
|
27
|
+
stroke: #FF0000;
|
28
|
+
stroke-width: 3px;
|
29
|
+
}
|
30
|
+
|
31
|
+
text {
|
32
|
+
font: 10px sans-serif;
|
33
|
+
pointer-events: none;
|
34
|
+
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
|
35
|
+
}
|
36
|
+
|
37
|
+
text.type{
|
38
|
+
font: 7px sans-serif;
|
39
|
+
}
|
40
|
+
|
41
|
+
.downlighted {
|
42
|
+
opacity: 0.1;
|
43
|
+
}
|
44
|
+
|
45
|
+
.ignored{
|
46
|
+
display: none;
|
47
|
+
}
|
48
|
+
|
49
|
+
.running circle{
|
50
|
+
stroke: #18b738;
|
51
|
+
fill: #7dea93;
|
52
|
+
}
|
53
|
+
|
54
|
+
.highlighted,
|
55
|
+
.highlighted_by_namespace,
|
56
|
+
.highlighted_by_path,
|
57
|
+
.highlighted_by_type{
|
58
|
+
fill: #008ef3;
|
59
|
+
}
|
60
|
+
|
61
|
+
.highlighted circle,
|
62
|
+
.highlighted_by_namespace circle,
|
63
|
+
.highlighted_by_path circle,
|
64
|
+
.highlighted_by_type circle{
|
65
|
+
stroke: #008ef3;
|
66
|
+
}
|
67
|
+
|
68
|
+
.hide_namespace .namespace, .link.hide_relation{
|
69
|
+
display: none;
|
70
|
+
}
|
71
|
+
|
72
|
+
/* ============================ */
|
73
|
+
/* Toolbox style */
|
74
|
+
/* ============================ */
|
75
|
+
|
76
|
+
.toolbox{
|
77
|
+
position: fixed;
|
78
|
+
top: 0px;
|
79
|
+
left: 0px;
|
80
|
+
max-height: 100%;
|
81
|
+
width: 250px;
|
82
|
+
background: white;
|
83
|
+
overflow: auto;
|
84
|
+
}
|
85
|
+
|
86
|
+
ul, ol{
|
87
|
+
list-style-position: inside;
|
88
|
+
white-space: nowrap;
|
89
|
+
margin: 0px;
|
90
|
+
padding-left: 0px;
|
91
|
+
overflow: auto;
|
92
|
+
}
|
93
|
+
|
94
|
+
#information_panel{
|
95
|
+
position: fixed;
|
96
|
+
bottom: 0px;
|
97
|
+
left: 0px;
|
98
|
+
height: 5rem;
|
99
|
+
background: rgba(255,255,255,0.8);
|
100
|
+
width: 100%;
|
101
|
+
margin-left: 250px;
|
102
|
+
padding-right: 250px;
|
103
|
+
}
|
104
|
+
|
105
|
+
#information_panel div{
|
106
|
+
margin: 1rem;
|
107
|
+
display: inline-block;
|
108
|
+
}
|
109
|
+
|
110
|
+
#information_panel:hover{
|
111
|
+
height: 33%;
|
112
|
+
overflow: auto;
|
113
|
+
}
|
@@ -0,0 +1,205 @@
|
|
1
|
+
var classForCircular = function(d) {
|
2
|
+
return d.circular ? 'circular' : '';
|
3
|
+
};
|
4
|
+
|
5
|
+
var svg = d3.select(".dependency_graph svg"),
|
6
|
+
$svg = $('.dependency_graph svg'),
|
7
|
+
width = $svg.width(),
|
8
|
+
height = $svg.height(),
|
9
|
+
drag = d3.drag()
|
10
|
+
.on("start", dragstarted)
|
11
|
+
.on("drag", dragged)
|
12
|
+
.on("end", dragended),
|
13
|
+
dup_definitions = data.definitions.map(function(d){
|
14
|
+
return {
|
15
|
+
id: d.namespace,
|
16
|
+
file: d.file,
|
17
|
+
type: d.type,
|
18
|
+
lines: d.lines,
|
19
|
+
circular: d.circular
|
20
|
+
};
|
21
|
+
}),
|
22
|
+
definitions = _(dup_definitions).groupBy('id').map(function(group) {
|
23
|
+
return {
|
24
|
+
id: group[0].id,
|
25
|
+
type: group[0].type,
|
26
|
+
lines: _(group).sumBy('lines'),
|
27
|
+
circular: false,
|
28
|
+
files: []
|
29
|
+
};
|
30
|
+
}).value(),
|
31
|
+
namespaces = definitions.map(function(d){ return d.id; }),
|
32
|
+
relations = data.relations.map(function(d){ return {source: d.caller, target: d.resolved_namespace, circular: d.circular}; }),
|
33
|
+
max_lines = _.maxBy(definitions, 'lines').lines,
|
34
|
+
max_circle_r = 50;
|
35
|
+
|
36
|
+
relations = relations.filter(function(d){
|
37
|
+
return namespaces.indexOf(d.source) >= 0 && namespaces.indexOf(d.target) >= 0;
|
38
|
+
});
|
39
|
+
relations = _.uniqWith(relations, _.isEqual);
|
40
|
+
|
41
|
+
var zoom = d3.zoom().on("zoom", function () {
|
42
|
+
container.attr("transform", d3.event.transform);
|
43
|
+
});
|
44
|
+
|
45
|
+
svg.call(zoom)
|
46
|
+
.on("dblclick.zoom", null);
|
47
|
+
|
48
|
+
var container = svg.append('g'),
|
49
|
+
simulation = d3.forceSimulation()
|
50
|
+
.force("link", d3.forceLink().id(function(d) { return d.id; }))
|
51
|
+
.force("charge", d3.forceManyBody())
|
52
|
+
.force("center", d3.forceCenter(width / 2, height / 2))
|
53
|
+
.force("forceCollide", d3.forceCollide(80));
|
54
|
+
|
55
|
+
simulation
|
56
|
+
.nodes(definitions)
|
57
|
+
.on("tick", ticked);
|
58
|
+
|
59
|
+
simulation.force("link")
|
60
|
+
.links(relations);
|
61
|
+
|
62
|
+
var link = container.append("g")
|
63
|
+
.attr("class", "links")
|
64
|
+
.selectAll("path")
|
65
|
+
.data(relations)
|
66
|
+
.enter().append("path")
|
67
|
+
.attr("class", function(d) { return 'link ' + classForCircular(d); })
|
68
|
+
.attr("marker-end", function(d){ return "url(#" + d.target.id + ")"; }),
|
69
|
+
node = container.append("g")
|
70
|
+
.attr("class", "nodes")
|
71
|
+
.selectAll("g")
|
72
|
+
.data(definitions)
|
73
|
+
.enter().append("g")
|
74
|
+
.call(drag)
|
75
|
+
.on("dblclick", dblclick),
|
76
|
+
circle = node
|
77
|
+
.append("circle")
|
78
|
+
.attr("r", function(d) { return d.lines / max_lines * max_circle_r + 6; })
|
79
|
+
.attr("class", function (d) { return classForCircular(d) ; }),
|
80
|
+
type = node
|
81
|
+
.append("text")
|
82
|
+
.attr("class", "type")
|
83
|
+
.attr("x", "-0.4em")
|
84
|
+
.attr("y", "0.4em")
|
85
|
+
.text(function(d) { return d.type[0]; }),
|
86
|
+
text = node
|
87
|
+
.append("text")
|
88
|
+
.attr("class", "namespace")
|
89
|
+
.attr("x", function(d) { return d.lines / max_lines * max_circle_r + 8; })
|
90
|
+
.attr("y", ".31em")
|
91
|
+
.text(function(d) { return d.id; });
|
92
|
+
|
93
|
+
container.append("defs").selectAll("marker")
|
94
|
+
.data(definitions)
|
95
|
+
.enter().append("marker")
|
96
|
+
.attr("id", function(d) { return d.id; })
|
97
|
+
.attr("viewBox", "0 -5 10 10")
|
98
|
+
.attr("refX", function(d){ return d.lines / max_lines * max_circle_r + 20; })
|
99
|
+
.attr("refY", 0)
|
100
|
+
.attr("markerWidth", 6)
|
101
|
+
.attr("markerHeight", 6)
|
102
|
+
.attr("orient", "auto")
|
103
|
+
.append("path")
|
104
|
+
.attr("d", "M0,-5L10,0L0,5");
|
105
|
+
|
106
|
+
function ticked() {
|
107
|
+
link.attr("d", linkArc);
|
108
|
+
node.attr("transform", transform);
|
109
|
+
}
|
110
|
+
|
111
|
+
function linkArc(d) {
|
112
|
+
var dx = d.target.x - d.source.x,
|
113
|
+
dy = d.target.y - d.source.y,
|
114
|
+
dr = 0;
|
115
|
+
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
|
116
|
+
}
|
117
|
+
|
118
|
+
function dragstarted(d) {
|
119
|
+
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
|
120
|
+
d3.select(this).classed("fixed", true);
|
121
|
+
d.fx = d.x;
|
122
|
+
d.fy = d.y;
|
123
|
+
}
|
124
|
+
|
125
|
+
function dragged(d) {
|
126
|
+
d.fx = d3.event.x;
|
127
|
+
d.fy = d3.event.y;
|
128
|
+
}
|
129
|
+
|
130
|
+
function dragended(d) {
|
131
|
+
if (!d3.event.active) simulation.alphaTarget(0);
|
132
|
+
}
|
133
|
+
|
134
|
+
function dblclick(d) {
|
135
|
+
d3.select(this).classed("fixed", false);
|
136
|
+
d.fx = null;
|
137
|
+
d.fy = null;
|
138
|
+
}
|
139
|
+
|
140
|
+
function transform(d) {
|
141
|
+
return "translate(" + d.x + "," + d.y + ")";
|
142
|
+
}
|
143
|
+
|
144
|
+
var state = {
|
145
|
+
get: function(){
|
146
|
+
var positions = [];
|
147
|
+
rubrowser.definitions.forEach(function(elem){
|
148
|
+
if( elem.fx !== undefined && elem.fy !== undefined) {
|
149
|
+
positions.push({
|
150
|
+
id: elem.id,
|
151
|
+
x: elem.fx,
|
152
|
+
y: elem.fy
|
153
|
+
});
|
154
|
+
}
|
155
|
+
});
|
156
|
+
return positions;
|
157
|
+
},
|
158
|
+
|
159
|
+
set: function(layout){
|
160
|
+
if ( !layout ) { return; }
|
161
|
+
layout.forEach(function(pos) {
|
162
|
+
var definition = node.filter(function(e) { return e.id == pos.id; })
|
163
|
+
definition.classed("fixed", true);
|
164
|
+
|
165
|
+
var datum = definition.data()[0]
|
166
|
+
if( datum ) {
|
167
|
+
datum.fx = pos.x
|
168
|
+
datum.fy = pos.y
|
169
|
+
}
|
170
|
+
});
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
node.on('mouseover', function(d) {
|
175
|
+
var relatives = [];
|
176
|
+
link.classed('downlighted', function(l) {
|
177
|
+
if (d === l.source || d === l.target){
|
178
|
+
relatives.push(l.source);
|
179
|
+
relatives.push(l.target);
|
180
|
+
return false;
|
181
|
+
}else{
|
182
|
+
return true;
|
183
|
+
}
|
184
|
+
});
|
185
|
+
node.classed('downlighted', function(n) {
|
186
|
+
return !(n == d || relatives.indexOf(n) > -1);
|
187
|
+
});
|
188
|
+
});
|
189
|
+
|
190
|
+
node.on('mouseout', function() {
|
191
|
+
link.classed('downlighted', false);
|
192
|
+
node.classed('downlighted', false);
|
193
|
+
});
|
194
|
+
|
195
|
+
window.rubrowser = {
|
196
|
+
data: data,
|
197
|
+
definitions: definitions,
|
198
|
+
relations: relations,
|
199
|
+
simulation: simulation,
|
200
|
+
node: node,
|
201
|
+
link: link,
|
202
|
+
state: state
|
203
|
+
};
|
204
|
+
|
205
|
+
rubrowser.state.set(layout);
|
@@ -0,0 +1,223 @@
|
|
1
|
+
$(document).on('click', '.card-header', function(){
|
2
|
+
$(this).siblings().toggle();
|
3
|
+
});
|
4
|
+
|
5
|
+
// --------------------------------
|
6
|
+
// Details Panel
|
7
|
+
// --------------------------------
|
8
|
+
rubrowser.node.on('click', function(d){
|
9
|
+
var namespace = d.id;
|
10
|
+
var lines = d.lines;
|
11
|
+
var dependents = rubrowser.relations.filter(function(i){ return i.target.id == namespace; });
|
12
|
+
var dependencies = rubrowser.relations.filter(function(i){ return i.source.id == namespace; });
|
13
|
+
var definitions = rubrowser.data.definitions.filter(function(i){ return i.namespace == namespace; });
|
14
|
+
var relations = rubrowser.data.relations.filter(function(i){ return i.resolved_namespace == namespace || i.caller == namespace; });
|
15
|
+
|
16
|
+
var content = $('<div>');
|
17
|
+
content.append('<label><strong>'+namespace+' ('+d.lines+' Lines)</strong></label>');
|
18
|
+
|
19
|
+
content.append('<strong>Defined in:</strong>');
|
20
|
+
var definitions_ol = $("<ol>");
|
21
|
+
for(var i=0; i<definitions.length; i++){
|
22
|
+
definitions_ol.append("<li>"+definitions[i].file+":"+definitions[i].line.toString()+"</li>");
|
23
|
+
}
|
24
|
+
content.append(definitions_ol);
|
25
|
+
|
26
|
+
if( dependents.length > 0 ){
|
27
|
+
content.append('<strong>Dependents:</strong>');
|
28
|
+
var dependents_ol = $("<ol>");
|
29
|
+
for(var i=0; i<dependents.length; i++){
|
30
|
+
dependents_ol.append("<li>"+dependents[i].source.id+"</li>");
|
31
|
+
}
|
32
|
+
content.append(dependents_ol);
|
33
|
+
}
|
34
|
+
|
35
|
+
if( dependencies.length > 0 ){
|
36
|
+
content.append('<strong>Dependencies:</strong>');
|
37
|
+
var dependencies_ol = $("<ol>");
|
38
|
+
for(var i=0; i<dependencies.length; i++){
|
39
|
+
dependencies_ol.append("<li>"+dependencies[i].target.id+"</li>");
|
40
|
+
}
|
41
|
+
content.append(dependencies_ol);
|
42
|
+
}
|
43
|
+
|
44
|
+
$('#information_panel').html(content);
|
45
|
+
return true;
|
46
|
+
});
|
47
|
+
|
48
|
+
|
49
|
+
// --------------------------------
|
50
|
+
// Search Panel
|
51
|
+
// --------------------------------
|
52
|
+
$(document).on('change', '#highlight_by_namespace', function(){
|
53
|
+
var highlights_entries = $(this).val().trim();
|
54
|
+
var highlights = _(highlights_entries.split("\n"));
|
55
|
+
|
56
|
+
rubrowser.node.classed('highlighted_by_namespace', function(d){
|
57
|
+
if(highlights_entries.length == 0){ return false; }
|
58
|
+
return highlights.some(function(i){ return d.id.indexOf(i) > -1; });
|
59
|
+
});
|
60
|
+
});
|
61
|
+
|
62
|
+
$(document).on('change', '#highlight_by_file_path', function(){
|
63
|
+
var highlights_entries = $(this).val().trim();
|
64
|
+
var highlights = _(highlights_entries.split("\n"));
|
65
|
+
|
66
|
+
rubrowser.node.classed('highlighted_by_path', function(d){
|
67
|
+
if(highlights_entries.length == 0){ return false; }
|
68
|
+
return highlights.some(function(i){
|
69
|
+
return _(d.files).some(function(f) {
|
70
|
+
return f.indexOf(i) > -1;
|
71
|
+
});
|
72
|
+
});
|
73
|
+
});
|
74
|
+
});
|
75
|
+
|
76
|
+
$(document).on('change', '#highlight_modules, #highlight_classes', function(){
|
77
|
+
var modules_highlighted = $('#highlight_modules').is(':checked'),
|
78
|
+
classes_highlighted = $('#highlight_classes').is(':checked');
|
79
|
+
|
80
|
+
rubrowser.node.classed('highlighted_by_type', function(d){
|
81
|
+
return (d.type == 'Module' && modules_highlighted) || (d.type == 'Class' && classes_highlighted);
|
82
|
+
});
|
83
|
+
});
|
84
|
+
|
85
|
+
// --------------------------------
|
86
|
+
// Ignore Panel
|
87
|
+
// --------------------------------
|
88
|
+
var ignoring_functions = {};
|
89
|
+
|
90
|
+
function updateNodes() {
|
91
|
+
|
92
|
+
function ignoreNode(d) {
|
93
|
+
return _(ignoring_functions).some(function(ignoring_function) {
|
94
|
+
return ignoring_function(d);
|
95
|
+
});
|
96
|
+
}
|
97
|
+
|
98
|
+
function notIgnoreNode(d){
|
99
|
+
return !ignoreNode(d);
|
100
|
+
}
|
101
|
+
function ignoreRelation(r){
|
102
|
+
return ignoreNode(r.source) || ignoreNode(r.target);
|
103
|
+
}
|
104
|
+
|
105
|
+
function notIgnoreRelation(r){
|
106
|
+
return !ignoreRelation(r);
|
107
|
+
}
|
108
|
+
|
109
|
+
var filtered_definitions = rubrowser.definitions.filter(notIgnoreNode);
|
110
|
+
rubrowser.simulation.nodes(filtered_definitions);
|
111
|
+
rubrowser.node.classed('ignored', ignoreNode);
|
112
|
+
|
113
|
+
var filtered_relations = rubrowser.relations.filter(notIgnoreRelation);
|
114
|
+
rubrowser.simulation.force("link").links(filtered_relations);
|
115
|
+
rubrowser.link.classed('ignored', ignoreRelation);
|
116
|
+
}
|
117
|
+
|
118
|
+
$(document).on('change', '#ignore_by_namespace', function(){
|
119
|
+
var ignores_entries = $(this).val().trim();
|
120
|
+
var ignores = ignores_entries.split("\n");
|
121
|
+
|
122
|
+
if(ignores_entries.length == 0){
|
123
|
+
delete ignoring_functions["ignore_by_name"];
|
124
|
+
}else{
|
125
|
+
ignoring_functions["ignore_by_name"] = function(d){
|
126
|
+
return ignores.some(function(i){ return d.id.indexOf(i) > -1; });
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
updateNodes();
|
131
|
+
});
|
132
|
+
|
133
|
+
$(document).on('change', '#ignore_by_file_path', function(){
|
134
|
+
var ignores_entries = $(this).val().trim();
|
135
|
+
var ignores = ignores_entries.split("\n");
|
136
|
+
|
137
|
+
if(ignores_entries.length == 0){
|
138
|
+
delete ignoring_functions["ignore_by_file_path"];
|
139
|
+
}else{
|
140
|
+
ignoring_functions["ignore_by_file_path"] = function(d){
|
141
|
+
return ignores.some(function(i){
|
142
|
+
return _(d.files).every(function(f){
|
143
|
+
return f.indexOf(i) > -1;
|
144
|
+
});
|
145
|
+
});
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
updateNodes();
|
150
|
+
});
|
151
|
+
|
152
|
+
$(document).on('change', '#ignore_modules, #ignore_classes', function(){
|
153
|
+
var modules_ignored = $('#ignore_modules').is(':checked'),
|
154
|
+
classes_ignored = $('#ignore_classes').is(':checked');
|
155
|
+
|
156
|
+
if( modules_ignored ){
|
157
|
+
ignoring_functions["ignore_modules"] = function(d) {
|
158
|
+
return d.type == 'Module';
|
159
|
+
}
|
160
|
+
}else{
|
161
|
+
delete ignoring_functions["ignore_modules"];
|
162
|
+
}
|
163
|
+
|
164
|
+
if( classes_ignored ){
|
165
|
+
ignoring_functions["ignore_classes"] = function(d) {
|
166
|
+
return d.type == 'Class';
|
167
|
+
}
|
168
|
+
}else{
|
169
|
+
delete ignoring_functions["ignore_classes"];
|
170
|
+
}
|
171
|
+
|
172
|
+
updateNodes();
|
173
|
+
});
|
174
|
+
|
175
|
+
// --------------------------------
|
176
|
+
// Display Panel
|
177
|
+
// --------------------------------
|
178
|
+
$(document).on('change', "#force_collide", function(){
|
179
|
+
var new_value = $(this).val();
|
180
|
+
rubrowser.simulation.force("forceCollide", d3.forceCollide(new_value));
|
181
|
+
});
|
182
|
+
|
183
|
+
$(document).on('change', "#hide_relations", function(){
|
184
|
+
var hide_relations = $('#hide_relations').is(':checked');
|
185
|
+
rubrowser.link.classed("hide_relation", hide_relations);
|
186
|
+
});
|
187
|
+
|
188
|
+
$(document).on('change', "#hide_namespaces", function(){
|
189
|
+
var hide_namespaces = $('#hide_namespaces').is(':checked');
|
190
|
+
rubrowser.node.classed("hide_namespace", hide_namespaces);
|
191
|
+
});
|
192
|
+
|
193
|
+
$(document).on('click', "#pause_simulation", function(){
|
194
|
+
rubrowser.simulation.stop();
|
195
|
+
});
|
196
|
+
|
197
|
+
$(document).on('click', "#fix_all", function(){
|
198
|
+
rubrowser.node.classed("fixed", true);
|
199
|
+
rubrowser.node.each(function(d){
|
200
|
+
d.fx = d.x;
|
201
|
+
d.fy = d.y;
|
202
|
+
});
|
203
|
+
});
|
204
|
+
|
205
|
+
$(document).on('click', "#release_all", function(){
|
206
|
+
rubrowser.node.classed("fixed", false);
|
207
|
+
rubrowser.node.each(function(d){
|
208
|
+
delete d["fx"];
|
209
|
+
delete d["fy"];
|
210
|
+
});
|
211
|
+
});
|
212
|
+
|
213
|
+
$(document).on('click', "#download_layout", function(){
|
214
|
+
var json = JSON.stringify(rubrowser.state.get());
|
215
|
+
var element = document.createElement('a');
|
216
|
+
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(json));
|
217
|
+
element.setAttribute('download', 'layout.json');
|
218
|
+
|
219
|
+
element.style.display = 'none';
|
220
|
+
document.body.appendChild(element);
|
221
|
+
element.click();
|
222
|
+
document.body.removeChild(element);
|
223
|
+
});
|