sunburst directive : externalized in a module, react to vm changes
This commit is contained in:
committed by
Fabrice Marsaud
parent
a6bedea4b6
commit
f0f4e0985a
@@ -4,3 +4,5 @@
|
||||
/node_modules/*
|
||||
!/node_modules/*.js
|
||||
/node_modules/*.js/
|
||||
|
||||
jsconfig.json
|
||||
@@ -1,425 +1,126 @@
|
||||
import angular from 'angular';
|
||||
import uiRouter from 'angular-ui-router';
|
||||
import uiSelect from 'angular-ui-select';
|
||||
import d3 from 'd3';
|
||||
import filter from 'lodash.filter';
|
||||
'use strict'
|
||||
|
||||
import xoApi from 'xo-api';
|
||||
import xoServices from 'xo-services';
|
||||
import angular from 'angular'
|
||||
import uiRouter from 'angular-ui-router'
|
||||
import uiSelect from 'angular-ui-select'
|
||||
import foreach from 'lodash.foreach'
|
||||
|
||||
import view from './view';
|
||||
import xoApi from 'xo-api'
|
||||
import xoServices from 'xo-services'
|
||||
|
||||
import sunburstChart from 'xo-sunburst-d3'
|
||||
|
||||
import view from './view'
|
||||
|
||||
export default angular.module('dashboard.dataviz', [
|
||||
uiRouter,
|
||||
uiSelect,
|
||||
|
||||
xoApi,
|
||||
xoServices,
|
||||
xoServices
|
||||
])
|
||||
.config(function ($stateProvider) {
|
||||
$stateProvider.state('dashboard.dataviz', {
|
||||
controller: 'Dataviz as ctrl',
|
||||
url: '/dataviz',
|
||||
template: view
|
||||
});
|
||||
})
|
||||
})
|
||||
.controller('Dataviz', function (xoApi, $scope, $timeout) {
|
||||
|
||||
.directive('sunburstChart', function ($parse) {
|
||||
function link(scope, element, attrs) {
|
||||
if (!scope.chartData) {
|
||||
console.log(' no data, no graph')
|
||||
return;
|
||||
}
|
||||
|
||||
var width = attrs.width ? parseInt(attrs.width, 10) : 2000,
|
||||
height = attrs.height ? parseInt(attrs.height, 10) : width + 50 /*breadcrumbs*/,
|
||||
radius = Math.min(width, height ) / 2,
|
||||
color = d3.scale.category20c(),
|
||||
b = {
|
||||
w: 75, h: 30, s: 3, t: 10
|
||||
};
|
||||
|
||||
|
||||
var breadcrumbs = d3.select(element[0])
|
||||
.append('div')
|
||||
.attr('class', 'breadcrumbs-container')
|
||||
.append("svg:svg")
|
||||
.attr('class', 'breadcrumbs')
|
||||
.attr("width", width)
|
||||
.attr("height", 50);
|
||||
|
||||
//took responsive svg trick from http://demosthenes.info/blog/744/Make-SVG-Responsive
|
||||
var svg = d3.select(element[0])
|
||||
.append('div')
|
||||
.attr('style','display: inline-block;position: relative;width: 80%;padding-bottom: 100%; vertical-align: middle;overflow: hidden;' )
|
||||
|
||||
.append("svg")
|
||||
.attr('style','display: block;position: absolute; top: 0;left: 0;')
|
||||
.attr('width', '100%')
|
||||
.attr('height', '100%')
|
||||
|
||||
.attr('preserveAspectRatio','xMinYMin meet')
|
||||
.attr('viewBox','0 0 2000 2000')
|
||||
.append("g")
|
||||
.attr('transform', 'translate(' + width / 2 + "," + height * .52 +')');
|
||||
|
||||
var partition = d3.layout.partition()
|
||||
.sort(null)
|
||||
.size([2 * Math.PI, radius * radius])
|
||||
.value(function (d) {
|
||||
return d.size;
|
||||
});
|
||||
|
||||
var arc = d3.svg.arc()
|
||||
.startAngle(function (d) {
|
||||
return d.x;
|
||||
$scope.charts = {
|
||||
selected: {},
|
||||
ram: {
|
||||
name: 'ram',
|
||||
children: []
|
||||
},
|
||||
cpu: {
|
||||
name: 'cpu',
|
||||
children: []
|
||||
},
|
||||
over: function (d) {
|
||||
$scope.$apply(function () {
|
||||
$scope.charts.selected = d
|
||||
})
|
||||
.endAngle(function (d) {
|
||||
return d.x + d.dx;
|
||||
})
|
||||
.innerRadius(function (d) {
|
||||
return Math.sqrt(d.y);
|
||||
})
|
||||
.outerRadius(function (d) {
|
||||
return Math.sqrt(d.y + d.dy);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var path = svg.datum(scope.chartData).selectAll("path")
|
||||
.data(partition.nodes)
|
||||
.enter().append("path")
|
||||
.attr("display", function (d) {
|
||||
return d.depth ? null : "none";
|
||||
}) // hide inner ring
|
||||
.attr("d", arc)
|
||||
.style("stroke", "#fff")
|
||||
.style("fill", function (d) {
|
||||
return d.color ? d.color : color((d.children ? d : d.parent).name);
|
||||
})
|
||||
.style("fill-rule", "evenodd")
|
||||
.on("mouseover", mouseover)
|
||||
.on("click", click);
|
||||
function populateChartsData() {
|
||||
let ram_children, cpu_children, pools, vmsByContainer, hostsByPool
|
||||
|
||||
function mouseover(d) {
|
||||
if (scope.over) {
|
||||
scope.over.apply(null, [{d: d}]);
|
||||
ram_children = []
|
||||
cpu_children = []
|
||||
pools = xoApi.getView('pools')
|
||||
vmsByContainer = xoApi.getIndex('vmsByContainer')
|
||||
hostsByPool = xoApi.getIndex('hostsByPool')
|
||||
|
||||
foreach(pools.all, function (pool, pool_id) {
|
||||
let pool_cpu = {
|
||||
name: pool.name_label,
|
||||
id: pool_id,
|
||||
children: []
|
||||
}
|
||||
}
|
||||
|
||||
function click(d) {
|
||||
if (scope.click) {
|
||||
scope.click.apply(null, [{d: d}]);
|
||||
let pool_ram = {
|
||||
name: pool.name_label,
|
||||
id: pool_id,
|
||||
children: []
|
||||
}
|
||||
}
|
||||
|
||||
function highlight(id) {
|
||||
|
||||
var sequenceArray = getAncestors(id);
|
||||
updateBreadcrumbs(sequenceArray);
|
||||
// Fade all the segments.
|
||||
svg.selectAll("path")
|
||||
.style("opacity", 0.3);
|
||||
|
||||
// Then highlight only those that are an ancestor of the current segment.
|
||||
svg.selectAll("path")
|
||||
.filter(function (node) {
|
||||
return (sequenceArray.indexOf(node) >= 0);
|
||||
})
|
||||
.style("opacity", 1);
|
||||
}
|
||||
|
||||
// Generate a string that describes the points of a breadcrumb polygon.
|
||||
function breadcrumbPoints(d, i) {
|
||||
var points = [];
|
||||
points.push("0,0");
|
||||
points.push(b.w + ",0");
|
||||
points.push(b.w + b.t + "," + (b.h / 2));
|
||||
points.push(b.w + "," + b.h);
|
||||
points.push("0," + b.h);
|
||||
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
|
||||
points.push(b.t + "," + (b.h / 2));
|
||||
}
|
||||
return points.join(" ");
|
||||
}
|
||||
|
||||
|
||||
// Update the breadcrumb trail to show the current sequence and percentage.
|
||||
function updateBreadcrumbs(nodeArray, percentageString) {
|
||||
|
||||
var g = breadcrumbs
|
||||
.selectAll("g")
|
||||
.data(nodeArray, function (d) {
|
||||
return d.name + d.depth;
|
||||
});
|
||||
|
||||
// Add breadcrumb and label for entering nodes.
|
||||
var entering = g.enter().append("svg:g");
|
||||
|
||||
entering.append("svg:polygon")
|
||||
.attr("points", breadcrumbPoints)
|
||||
.style("fill", function (d) {
|
||||
return d.color ? d.color : color((d.children ? d : d.parent).name);
|
||||
|
||||
});
|
||||
|
||||
entering.append("svg:text")
|
||||
.attr("x", (b.w + b.t) / 2)
|
||||
.attr("y", b.h / 2)
|
||||
.attr("dy", "0.35em")
|
||||
.attr("text-anchor", "middle")
|
||||
.text(function (d) {
|
||||
return d.name;
|
||||
});
|
||||
|
||||
// Set position for entering and updating nodes.
|
||||
g.attr("transform", function (d, i) {
|
||||
return "translate(" + i * (b.w + b.s) + ", 0)";
|
||||
});
|
||||
|
||||
// Remove exiting nodes.
|
||||
g.exit().remove();
|
||||
|
||||
// Make the breadcrumb trail visible, if it's hidden.
|
||||
d3.select(".breadcrumbs")
|
||||
.style("visibility", "");
|
||||
|
||||
}
|
||||
|
||||
scope.$watch('selected', function (newVal) {
|
||||
if (newVal && newVal.id) {
|
||||
highlight(newVal.id);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
function getAncestors(id) {
|
||||
var path = [];
|
||||
svg.selectAll("path")
|
||||
.each(function (node) {
|
||||
if (node.id === id) {
|
||||
var current = node;
|
||||
while (current.parent) {
|
||||
path.unshift(current);
|
||||
current = current.parent;
|
||||
}
|
||||
let hosts = hostsByPool[pool_id]
|
||||
foreach(hosts, function (host, host_id) {
|
||||
let host_cpu = {
|
||||
name: host.name_label,
|
||||
id: host_id,
|
||||
children: []
|
||||
}
|
||||
let host_ram = {
|
||||
name: host.name_label,
|
||||
id: host_id,
|
||||
children: []
|
||||
}
|
||||
let VMs = vmsByContainer[host_id]
|
||||
foreach(VMs, function (VM, vm_id) {
|
||||
let vm_ram = {
|
||||
name: VM.name_label,
|
||||
id: vm_id,
|
||||
size: VM.memory.size
|
||||
}
|
||||
let vm_cpu = {
|
||||
name: VM.name_label,
|
||||
id: vm_id,
|
||||
size: VM.CPUs.number
|
||||
}
|
||||
if (vm_cpu.size) {
|
||||
host_cpu.children.push(vm_cpu)
|
||||
}
|
||||
if (vm_ram.size) {
|
||||
host_ram.children.push(vm_ram)
|
||||
}
|
||||
})
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
resctict: 'E',
|
||||
replace: false,
|
||||
scope: {
|
||||
chartData: '=',
|
||||
selected: '=',
|
||||
over: '&',
|
||||
click: '&'
|
||||
},
|
||||
link: link
|
||||
}
|
||||
|
||||
})
|
||||
.controller('Dataviz', function (xoApi, $scope) {
|
||||
$scope.charts = {};
|
||||
|
||||
$scope.charts.selected = {};
|
||||
|
||||
$scope.charts.over = function (d) {
|
||||
$scope.$apply(function () {
|
||||
$scope.charts.selected = d;
|
||||
|
||||
if (host_ram.children.length) {
|
||||
pool_ram.children.push(host_ram)
|
||||
pool_cpu.children.push(host_cpu)
|
||||
}
|
||||
})
|
||||
if (pool_ram.children.length) {
|
||||
ram_children.push(pool_ram)
|
||||
cpu_children.push(pool_cpu)
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
$scope.charts.ram =
|
||||
{
|
||||
name: "ram",
|
||||
children: [{
|
||||
name: " first pool",
|
||||
id: 'pool1',
|
||||
children: [{
|
||||
id: 'server11',
|
||||
name: "server1 ",
|
||||
children: [{
|
||||
id: 'vm11',
|
||||
name: "VM11",
|
||||
size: 512
|
||||
}, {
|
||||
id: 'vm12',
|
||||
name: "VM12",
|
||||
size: 1024
|
||||
},
|
||||
{
|
||||
id: 'server11unallocated',
|
||||
name: "unallocated",
|
||||
size: 8048,
|
||||
color: '#000'
|
||||
}
|
||||
]
|
||||
}, {
|
||||
name: "server2",
|
||||
id: 'server12',
|
||||
children: [{
|
||||
id: 'VM121',
|
||||
name: "VM21 et gs ert",
|
||||
size: 256
|
||||
}, {
|
||||
id: 'VM122',
|
||||
name: "VM22 seghtrsedg",
|
||||
size: 2048
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: " second pool",
|
||||
id: 'pool2',
|
||||
children: [{
|
||||
id: 'server21',
|
||||
name: "server1 ",
|
||||
children: [{
|
||||
name: "VM11",
|
||||
size: 768
|
||||
}, {
|
||||
id: 'vm211',
|
||||
name: "VM12",
|
||||
size: 1284
|
||||
},
|
||||
{
|
||||
id: 'server21unallocated',
|
||||
name: "unallocated",
|
||||
size: 512,
|
||||
color: '#000'
|
||||
}
|
||||
]
|
||||
}, {
|
||||
name: "server2",
|
||||
id: 'server22',
|
||||
children: [{
|
||||
id: 'VMM221',
|
||||
name: "VM21",
|
||||
size: 256
|
||||
}, {
|
||||
name: "VM22",
|
||||
id: 'server222',
|
||||
size: 2048
|
||||
},
|
||||
{
|
||||
id: 'server22unallocated',
|
||||
name: "unallocated",
|
||||
size: 4000,
|
||||
color: '#000'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
|
||||
$scope.charts.cpu =
|
||||
{
|
||||
name: "ram",
|
||||
children: [{
|
||||
name: " first pool",
|
||||
id: 'pool1',
|
||||
children: [{
|
||||
id: 'server11',
|
||||
name: "server1 ",
|
||||
children: [{
|
||||
id: 'vm11',
|
||||
name: "VM11",
|
||||
size: 512
|
||||
}, {
|
||||
id: 'vm12',
|
||||
name: "VM12",
|
||||
size: 1024
|
||||
},
|
||||
{
|
||||
id: 'server11unallocated',
|
||||
name: "unallocated",
|
||||
size: 8048,
|
||||
color: '#000'
|
||||
}
|
||||
]
|
||||
}, {
|
||||
name: "server2",
|
||||
id: 'server12',
|
||||
children: [{
|
||||
id: 'VM121',
|
||||
name: "VM21 et gs ert",
|
||||
size: 256
|
||||
}, {
|
||||
id: 'VM122',
|
||||
name: "VM22 seghtrsedg",
|
||||
size: 2048
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: " second pool",
|
||||
id: 'pool2',
|
||||
children: [{
|
||||
id: 'server21',
|
||||
name: "server1 ",
|
||||
children: [{
|
||||
name: "VM11",
|
||||
size: 768
|
||||
}, {
|
||||
id: 'vm211',
|
||||
name: "VM12",
|
||||
size: 1284
|
||||
},
|
||||
{
|
||||
id: 'server21unallocated',
|
||||
name: "unallocated",
|
||||
size: 512,
|
||||
color: '#000'
|
||||
}
|
||||
]
|
||||
}, {
|
||||
name: "server2",
|
||||
id: 'server22',
|
||||
children: [{
|
||||
id: 'VMM221',
|
||||
name: "VM21",
|
||||
size: 256
|
||||
}, {
|
||||
name: "VM22",
|
||||
id: 'server222',
|
||||
size: 2048
|
||||
},
|
||||
{
|
||||
id: 'server22unallocated',
|
||||
name: "unallocated",
|
||||
size: 4000,
|
||||
color: '#000'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
$scope.charts.cpu.children = cpu_children
|
||||
$scope.charts.ram.children = ram_children
|
||||
}
|
||||
|
||||
|
||||
//extract cpu from bytypes
|
||||
$scope.$watch(() => xoApi.all, function () {
|
||||
console.log(' XOAPI .ALL CHANGHE')
|
||||
$timeout(function () { // all semmes to be unpopulated for now
|
||||
populateChartsData()
|
||||
}, 0)
|
||||
},
|
||||
true)
|
||||
|
||||
|
||||
/*
|
||||
Object.defineProperties($scope, {
|
||||
xo: { get: -> xoApi.byTypes.xo?[0] },
|
||||
pools: { get: -> xoApi.byTypes.pool },
|
||||
hosts: { get: -> xoApi.byTypes.host },
|
||||
VMs: { get: -> xoApi.byTypes.VM },
|
||||
})*/
|
||||
})
|
||||
|
||||
// A module exports its name.
|
||||
.name
|
||||
;
|
||||
// A module exports its name.
|
||||
.name
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
'use strict'
|
||||
|
||||
import angular from 'angular'
|
||||
import d3 from 'd3'
|
||||
import clone from 'lodash.clonedeep'
|
||||
|
||||
module.exports = angular.module('xoWebApp.directives')
|
||||
.directive('sunburstChart', function ($parse) {
|
||||
function link(scope, element, attrs) {
|
||||
var width, height, radius, color, b, breadcrumbs, svg, arc, partition
|
||||
|
||||
width = attrs.width ? parseInt(attrs.width, 10) : 2000
|
||||
height = attrs.height ? parseInt(attrs.height, 10) : width + 50 /* breadcrumbs */
|
||||
radius = Math.min(width, height) / 2
|
||||
color = d3.scale.category20c()
|
||||
b = {
|
||||
w: 75, h: 30, s: 3, t: 10
|
||||
}
|
||||
|
||||
breadcrumbs = d3.select(element[0])
|
||||
.append('div')
|
||||
.attr('class', 'breadcrumbs-container')
|
||||
.append('svg:svg')
|
||||
.attr('class', 'breadcrumbs')
|
||||
.attr('width', width)
|
||||
.attr('height', 50)
|
||||
|
||||
// took responsive svg trick from http://demosthenes.info/blog/744/Make-SVG-Responsive
|
||||
svg = d3.select(element[0])
|
||||
.append('div')
|
||||
.attr('style', 'display: inline-block;position: relative;width: 80%;padding-bottom: 100%; vertical-align: middle;overflow: hidden;')
|
||||
.append('svg')
|
||||
.attr('style', 'display: block;position: absolute; top: 0;left: 0;')
|
||||
.attr('width', '100%')
|
||||
.attr('height', '100%')
|
||||
.attr('preserveAspectRatio', 'xMinYMin meet')
|
||||
.attr('viewBox', '0 0 2000 2000')
|
||||
.append('g')
|
||||
.attr('transform', 'translate(' + width / 2 + ',' + height * 0.52 + ')')
|
||||
|
||||
arc = d3.svg.arc()
|
||||
.startAngle((d) => d.x)
|
||||
.endAngle((d) => d.x + d.dx)
|
||||
.innerRadius((d) => Math.sqrt(d.y))
|
||||
.outerRadius((d) => Math.sqrt(d.y + d.dy))
|
||||
|
||||
scope.$watch('selected', function (newVal) {
|
||||
if (newVal && newVal.id) {
|
||||
highlight(newVal.id)
|
||||
}
|
||||
})
|
||||
|
||||
partition = d3.layout.partition()
|
||||
.sort(null)
|
||||
.size([2 * Math.PI, radius * radius])
|
||||
.value((d) => d.size)
|
||||
|
||||
scope.$watch(() => scope.chartData, function (newVal) {
|
||||
var g, chart_data
|
||||
if (!scope.chartData || !scope.chartData.children || !scope.chartData.children.length) {
|
||||
svg.selectAll('path').remove()
|
||||
breadcrumbs.selectAll('g').remove()
|
||||
return
|
||||
}
|
||||
//d3 add a lot aff data to nodex. It triggers watch without rest
|
||||
chart_data = clone(scope.chartData)
|
||||
|
||||
g = svg
|
||||
.datum(chart_data)
|
||||
.selectAll('path')
|
||||
.data(partition.nodes)
|
||||
|
||||
g.enter().append('path')
|
||||
|
||||
g.attr('display', (d) => d.depth ? null : 'none') // hide inner ring
|
||||
.attr('d', arc)
|
||||
.style('stroke', '#fff')
|
||||
.style('fill', function (d) {
|
||||
return d.color ? d.color : color((d.children ? d : d.parent).name)
|
||||
})
|
||||
.style('fill-rule', 'evenodd')
|
||||
.on('mouseover', mouseover)
|
||||
.on('click', click)
|
||||
|
||||
g.exit().remove()
|
||||
}, true)
|
||||
|
||||
function mouseover(d) {
|
||||
if (scope.over) {
|
||||
scope.over.apply(null, [{ d: d }])
|
||||
}
|
||||
}
|
||||
|
||||
function click(d) {
|
||||
if (scope.click) {
|
||||
scope.click.apply(null, [{ d: d }])
|
||||
}
|
||||
}
|
||||
|
||||
function highlight(id) {
|
||||
var sequenceArray = getAncestors(id)
|
||||
updateBreadcrumbs(sequenceArray)
|
||||
// Fade all the segments.
|
||||
svg.selectAll('path')
|
||||
.style('opacity', 0.3)
|
||||
|
||||
// Then highlight only those that are an ancestor of the current segment.
|
||||
svg.selectAll('path')
|
||||
.filter(function (node) {
|
||||
return (sequenceArray.indexOf(node) >= 0)
|
||||
})
|
||||
.style('opacity', 1)
|
||||
}
|
||||
|
||||
// Generate a string that describes the points of a breadcrumb polygon.
|
||||
function breadcrumbPoints(d, i) {
|
||||
var points = []
|
||||
points.push('0,0')
|
||||
points.push(b.w + ',0')
|
||||
points.push(b.w + b.t + ',' + (b.h / 2))
|
||||
points.push(b.w + ',' + b.h)
|
||||
points.push('0,' + b.h)
|
||||
if (i > 0) { // Leftmost breadcrumb don't include 6th vertex.
|
||||
points.push(b.t + ',' + (b.h / 2))
|
||||
}
|
||||
return points.join(' ')
|
||||
}
|
||||
|
||||
// Update the breadcrumb trail to show the current sequence and percentage.
|
||||
function updateBreadcrumbs(nodeArray, percentageString) {
|
||||
var g = breadcrumbs
|
||||
.selectAll('g')
|
||||
.data(nodeArray, function (d) {
|
||||
return d.name + d.depth
|
||||
})
|
||||
|
||||
// Add breadcrumb and label for entering nodes.
|
||||
var entering = g.enter().append('svg:g')
|
||||
|
||||
entering.append('svg:polygon')
|
||||
.attr('points', breadcrumbPoints)
|
||||
.style('fill', function (d) {
|
||||
return d.color ? d.color : color((d.children ? d : d.parent).name)
|
||||
|
||||
})
|
||||
|
||||
entering.append('svg:text')
|
||||
.attr('x', (b.w + b.t) / 2)
|
||||
.attr('y', b.h / 2)
|
||||
.attr('dy', '0.35em')
|
||||
.attr('text-anchor', 'middle')
|
||||
.text((d) => d.name)
|
||||
|
||||
// Set position for entering and updating nodes.
|
||||
g.attr('transform', function (d, i) {
|
||||
return 'translate(' + i * (b.w + b.s) + ', 0)'
|
||||
})
|
||||
|
||||
// Remove exiting nodes.
|
||||
g.exit().remove()
|
||||
|
||||
// Make the breadcrumb trail visible, if it's hidden.
|
||||
d3.select('.breadcrumbs')
|
||||
.style('visibility', '')
|
||||
|
||||
}
|
||||
|
||||
function getAncestors(id) {
|
||||
var path = []
|
||||
svg.selectAll('path')
|
||||
.each(function (node) {
|
||||
if (node.id === id) {
|
||||
var current = node
|
||||
while (current.parent) {
|
||||
path.unshift(current)
|
||||
current = current.parent
|
||||
}
|
||||
}
|
||||
})
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
resctict: 'E',
|
||||
replace: false,
|
||||
scope: {
|
||||
chartData: '=',
|
||||
selected: '=',
|
||||
over: '&',
|
||||
click: '&'
|
||||
},
|
||||
link: link
|
||||
}
|
||||
|
||||
})
|
||||
// A module exports its name.
|
||||
.name
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"private": true,
|
||||
"browserify": {
|
||||
"transform": [
|
||||
"babelify"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user