mirror of
https://gitee.com/antv/g6.git
synced 2024-12-04 12:49:04 +08:00
commit
a2ad884337
@ -1,10 +1,26 @@
|
||||
module.exports = {
|
||||
babelrc: {
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'transform-remove-strict-mode',
|
||||
[
|
||||
"module-resolver",
|
||||
{
|
||||
"alias": {
|
||||
'@antv/g6': './src/index'
|
||||
},
|
||||
}
|
||||
],
|
||||
],
|
||||
presets: [
|
||||
"@babel/preset-env",
|
||||
],
|
||||
sourceMaps: 'inline',
|
||||
},
|
||||
extensions: ['.js'],
|
||||
exclude: ['node_modules', 'bower_components']
|
||||
include: [
|
||||
'src/**/*.js',
|
||||
'plugins/**/*.js',
|
||||
],
|
||||
exclude: /(node_modules|bower_components)/
|
||||
}
|
||||
|
@ -9,7 +9,15 @@
|
||||
<button id="download">点击下载图片</button>
|
||||
<script src="assets/hierarchy.js"></script>
|
||||
<script src="../build/g6.js"></script>
|
||||
<script src="../build/minimap.js"></script>
|
||||
<script src="./assets/jquery-3.2.1.min.js"></script>
|
||||
<style>
|
||||
.g6-minimap{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
const COLLAPSE_ICON = function(x, y, r) {
|
||||
return [
|
||||
@ -70,10 +78,12 @@ G6.registerNode('tree-node', {
|
||||
return rect;
|
||||
}
|
||||
}, 'single-shape');
|
||||
const minimap = new Minimap({ size: [200, 150] });
|
||||
const graph = new G6.TreeGraph({
|
||||
container: 'mountNode',
|
||||
width: 800,
|
||||
height: 600,
|
||||
plugins: [ minimap ],
|
||||
modes: {
|
||||
default: [{
|
||||
type: 'collapse-expand',
|
||||
@ -88,7 +98,7 @@ const graph = new G6.TreeGraph({
|
||||
data.collapsed = collapsed;
|
||||
return true;
|
||||
}
|
||||
}, 'drag-canvas']
|
||||
}, 'drag-canvas', 'zoom-canvas']
|
||||
},
|
||||
defaultNode: {
|
||||
shape: 'tree-node',
|
||||
|
36
demos/default-shapes.html
Normal file
36
demos/default-shapes.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>内置Shapes</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mountNode"></div>
|
||||
<script src="../build/minimap.js"></script>
|
||||
<script src="../build/g6.js"></script>
|
||||
<script>
|
||||
const graph = new G6.Graph({
|
||||
container: 'mountNode',
|
||||
width: 800,
|
||||
height: 600,
|
||||
defaultNode: {
|
||||
size: [40, 40]
|
||||
},
|
||||
modes: {
|
||||
default: [ 'zoom-canvas', 'drag-canvas' ]
|
||||
}
|
||||
});
|
||||
graph.addItem('node', { id: 'circle', shape:'circle', label: 'circle', x: 100, y: 100 });
|
||||
graph.addItem('node', { id: 'rect', shape: 'rect', label: 'rect', labelCfg: { position: 'top' }, x: 550, y: 80, color: '#fe8550' });
|
||||
graph.addItem('node', { id: 'ellipse', shape: 'ellipse', label: 'ellipse', labelCfg: { position: 'right' }, x: 300, y: 300, style: { fill: '#1890FF', stroke: '#1890FF' }, size: [60, 40] });
|
||||
graph.addItem('node', { id: 'image', shape: 'image', label: 'image', labelCfg: { position: 'bottom'}, img: 'https://cdn.nlark.com/yuque/0/2019/png/174835/1553139586119-0360149e-a558-4712-9b71-3592dc1f52e4.png', x: 400, y: 500 });
|
||||
graph.addItem('edge', { id: 'edge0', source: 'circle', target: 'rect', label: 'line' });
|
||||
graph.addItem('edge', { id: 'edge1', source: 'circle', target: 'ellipse', shape: 'polyline', label: 'polyline', labelCfg: { position: 'center' }, controlPoints: [{ x: 100, y: 300 }]});
|
||||
graph.addItem('edge', { id: 'edge2', source: 'ellipse', target: 'image', shape: 'cubic-vertical', labelCfg: { position: 'start', refY: -15 }, label: 'cubic-vertical', size: 2 });
|
||||
graph.addItem('edge', { id: 'edge3', source: 'ellipse', target: 'rect', shape: 'quadratic', label: 'quadratic', controlPoints: [ { x: 300, y: 80 } ], labelCfg: { autoRotate: true }, });
|
||||
graph.addItem('edge', { id: 'edge4', source: 'rect', target: 'rect', shape: 'loop', loopCfg: { dist: 60, position: 'bottom' }, size: 2, style: { endArrow: true } });
|
||||
graph.addItem('edge', { id: 'edge4', source: 'rect', target: 'rect', shape: 'loop', loopCfg: { dist: 90, position: 'bottom' }, label: 'loop', labelCfg: { refY: -10 }, size: 2, style: { endArrow: true } });
|
||||
graph.addItem('edge', { id: 'edge5', source: { x: 550, y: 300 }, target: { x: 600, y: 650 }, label: 'standalone line' });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -7,6 +7,7 @@
|
||||
<body>
|
||||
<div id="mountNode"></div>
|
||||
<script src="../build/g6.js"></script>
|
||||
<script src="../build/minimap.js"></script>
|
||||
<script>
|
||||
|
||||
G6.registerEdge('relation', {
|
||||
@ -71,15 +72,16 @@
|
||||
}
|
||||
});
|
||||
return keyShape;
|
||||
console.log(point, tangent);
|
||||
}
|
||||
});
|
||||
const minimap = new Minimap({ size: [ 160, 100 ] });
|
||||
const graph = new G6.Graph({
|
||||
container: 'mountNode',
|
||||
width: 800,
|
||||
height: 500,
|
||||
plugins: [ minimap ],
|
||||
modes: {
|
||||
default: [ 'drag-node']
|
||||
default: [ 'drag-node', 'drag-canvas', 'zoom-canvas']
|
||||
}
|
||||
});
|
||||
const data = {
|
||||
|
@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
<script src="../src/behavior/tooltip.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mountNode"></div>
|
||||
|
@ -25,7 +25,7 @@
|
||||
x: 300,
|
||||
y: 200,
|
||||
shape: 'background-animate',
|
||||
color: 'red',
|
||||
color: '#b5b5b5',
|
||||
size: 20,
|
||||
label: '背景动画',
|
||||
labelCfg: {
|
||||
@ -66,7 +66,7 @@
|
||||
target: 'node4'
|
||||
}
|
||||
]};
|
||||
|
||||
|
||||
// 放大、变小动画
|
||||
G6.registerNode('circle-animate', {
|
||||
afterDraw(cfg, group) {
|
||||
@ -102,7 +102,7 @@
|
||||
x: 0,
|
||||
y: 0,
|
||||
r,
|
||||
fill: 'blue', // 为了显示清晰,随意设置了颜色
|
||||
fill: cfg.color, // 为了显示清晰,随意设置了颜色
|
||||
opacity: 0.6
|
||||
}
|
||||
});
|
||||
@ -113,7 +113,7 @@
|
||||
x: 0,
|
||||
y: 0,
|
||||
r,
|
||||
fill: 'green',
|
||||
fill: cfg.color,
|
||||
opacity: 0.6
|
||||
}
|
||||
});
|
||||
@ -176,4 +176,4 @@
|
||||
graph.render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -11,8 +11,8 @@
|
||||
<script src="./assets/sankey.js"></script>
|
||||
<script src="./assets/jquery-3.2.1.min.js"></script>
|
||||
<script>
|
||||
const width = 960;
|
||||
const height = 500;
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
const colors = ['#FD8C3D', '#D83F43', '#F7BED6', '#E487C7', '#46A848', '#D83F43', '#3B85BA', '#48335B', '#B7CDE9'];
|
||||
|
||||
G6.registerEdge('sankey', {
|
||||
|
120
demos/setState.html
Normal file
120
demos/setState.html
Normal file
@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
<style>
|
||||
.g6-tooltip {
|
||||
border: 1px solid #e2e2e2;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: #545454;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
padding: 10px 8px;
|
||||
box-shadow: rgb(174, 174, 174) 0px 0px 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mountNode"></div>
|
||||
<script src="../build/g6.js"></script>
|
||||
<script src="../build/minimap.js"></script>
|
||||
<script src="./assets/d3-4.13.0.min.js"></script>
|
||||
<script src="./assets/jquery-3.2.1.min.js"></script>
|
||||
<script>
|
||||
const minimap = new Minimap({ size: [ 200, 160 ] });
|
||||
const graph = new G6.Graph({
|
||||
container: 'mountNode',
|
||||
width: 1000,
|
||||
height: 800,
|
||||
autoPaint: false,
|
||||
plugins: [ minimap ],
|
||||
modes: {
|
||||
default: ['drag-canvas', {
|
||||
type: 'tooltip',
|
||||
formatText: model => { return model.name}
|
||||
}, {
|
||||
type: 'edge-tooltip',
|
||||
formatText: (model, e) => {
|
||||
const edge = e.item;
|
||||
return '来源:' + edge.getSource().getModel().name + '<br/>去向:' + edge.getTarget().getModel().name;
|
||||
}
|
||||
}]
|
||||
},
|
||||
defaultNode: {
|
||||
size: [10, 10],
|
||||
color: 'steelblue'
|
||||
},
|
||||
defaultEdge: {
|
||||
size: 1
|
||||
},
|
||||
nodeStyle: {
|
||||
default: {
|
||||
lineWidth: 2,
|
||||
fill: '#fff',
|
||||
opacity: 0.8
|
||||
},
|
||||
highlight: {
|
||||
opacity: 1
|
||||
},
|
||||
dark: {
|
||||
opacity: 0.2
|
||||
}
|
||||
},
|
||||
edgeStyle: {
|
||||
default: { stroke: '#e2e2e2', lineAppendWidth: 2 },
|
||||
highlight: { stroke: '#999' }
|
||||
}
|
||||
});
|
||||
graph.on('node:click', e => {
|
||||
const item = e.item;
|
||||
graph.setAutoPaint(false);
|
||||
graph.getNodes().forEach(node => {
|
||||
graph.clearItemStates(node);
|
||||
graph.setItemState(node, 'dark', true);
|
||||
});
|
||||
graph.setItemState(item, 'dark', false);
|
||||
graph.setItemState(item, 'highlight', true);
|
||||
graph.getEdges().forEach(edge => {
|
||||
if (edge.getSource() === item) {
|
||||
graph.setItemState(edge.getTarget(), 'dark', false);
|
||||
graph.setItemState(edge.getTarget(), 'highlight', true);
|
||||
graph.setItemState(edge, 'highlight', true);
|
||||
edge.toFront();
|
||||
} else if (edge.getTarget() === item) {
|
||||
graph.setItemState(edge.getSource(), 'dark', false);
|
||||
graph.setItemState(edge.getSource(), 'highlight', true);
|
||||
graph.setItemState(edge, 'highlight', true);
|
||||
edge.toFront();
|
||||
} else {
|
||||
graph.setItemState(edge, 'highlight', false);
|
||||
}
|
||||
});
|
||||
graph.paint();
|
||||
graph.setAutoPaint(true);
|
||||
});
|
||||
$.getJSON('./assets/data/xiaomi.json', data => {
|
||||
graph.data({ nodes: data.nodes, edges: data.edges.map((edge, i) => {
|
||||
edge.id = 'edge' + i;
|
||||
return Object.assign({}, edge);
|
||||
}) });
|
||||
const simulation = d3.forceSimulation()
|
||||
.force("link", d3.forceLink().id(function(d) { return d.id; }).strength(0.5))
|
||||
.force("charge", d3.forceManyBody())
|
||||
.force("center", d3.forceCenter(500,300));
|
||||
simulation
|
||||
.nodes(data.nodes)
|
||||
.on("tick", ticked);
|
||||
simulation.force("link")
|
||||
.links(data.edges);
|
||||
|
||||
graph.render();
|
||||
|
||||
function ticked() {
|
||||
graph.refreshPositions();
|
||||
graph.paint();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -110,7 +110,7 @@
|
||||
"silent": false
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g": "~3.4.0",
|
||||
"@antv/g": "~3.4.1",
|
||||
"@antv/util": "~1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
45
plugins/base.js
Normal file
45
plugins/base.js
Normal file
@ -0,0 +1,45 @@
|
||||
const deepMix = require('@antv/util/lib/deep-mix');
|
||||
const each = require('@antv/util/lib/each');
|
||||
const wrapBehavior = require('@antv/util/lib/event/wrap-behavior');
|
||||
|
||||
class PluginBase {
|
||||
constructor(cfgs) {
|
||||
this._cfgs = deepMix(this.getDefaultCfg(), cfgs);
|
||||
}
|
||||
getDefaultCfg() {
|
||||
return {};
|
||||
}
|
||||
init(graph) {
|
||||
const self = this;
|
||||
self.set('graph', graph);
|
||||
const events = self.getEvents();
|
||||
const bindEvents = {};
|
||||
each(events, (v, k) => {
|
||||
const event = wrapBehavior(self, v);
|
||||
bindEvents[k] = event;
|
||||
graph.on(k, event);
|
||||
});
|
||||
this._events = bindEvents;
|
||||
}
|
||||
getEvents() {
|
||||
return {};
|
||||
}
|
||||
get(key) {
|
||||
return this._cfgs[key];
|
||||
}
|
||||
set(key, val) {
|
||||
this._cfgs[key] = val;
|
||||
}
|
||||
destroy() {
|
||||
const graph = this.get('graph');
|
||||
const events = this._events;
|
||||
each(events, (v, k) => {
|
||||
graph.off(k, v);
|
||||
});
|
||||
this._events = null;
|
||||
this._cfgs = null;
|
||||
this.destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PluginBase;
|
5
plugins/index.js
Normal file
5
plugins/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
const G6Plugins = {
|
||||
minimap: require('./minimap')
|
||||
};
|
||||
|
||||
module.exports = G6Plugins;
|
247
plugins/minimap/index.js
Normal file
247
plugins/minimap/index.js
Normal file
@ -0,0 +1,247 @@
|
||||
const G = require('@antv/g6').G;
|
||||
const Base = require('../base');
|
||||
const isString = require('@antv/util/lib/type/is-string');
|
||||
const isNil = require('@antv/util/lib/type/is-nil');
|
||||
const createDOM = require('@antv/util/lib/dom/create-dom');
|
||||
const modifyCSS = require('@antv/util/lib/dom/modify-css');
|
||||
const each = require('@antv/util/lib/each');
|
||||
|
||||
const max = Math.max;
|
||||
|
||||
class Minimap extends Base {
|
||||
init(graph) {
|
||||
super.init(graph);
|
||||
this.initContainer();
|
||||
}
|
||||
getDefaultCfg() {
|
||||
return {
|
||||
container: null,
|
||||
className: 'g6-minimap',
|
||||
viewportClassName: 'g6-minimap-viewport',
|
||||
keyShapeOnly: false,
|
||||
size: [ 200, 120 ]
|
||||
};
|
||||
}
|
||||
getEvents() {
|
||||
return { beforepaint: 'updateCanvas' };
|
||||
}
|
||||
initContainer() {
|
||||
const self = this;
|
||||
const graph = self.get('graph');
|
||||
const size = self.get('size');
|
||||
const className = self.get('className');
|
||||
let container = self.get('container');
|
||||
if (isString(container)) {
|
||||
container = document.getElementById(container);
|
||||
}
|
||||
if (container) {
|
||||
container.classList.add(className);
|
||||
modifyCSS(container, {
|
||||
width: size[0] + 'px',
|
||||
height: size[1] + 'px'
|
||||
});
|
||||
} else {
|
||||
container = createDOM('<div class="' + className + '" style="width:' + size[0] + 'px; height:' + size[1] + 'px"></div>');
|
||||
graph.get('container').appendChild(container);
|
||||
}
|
||||
self.set('container', container);
|
||||
const containerDOM = createDOM('<div class="g6-minimap-container"></div>');
|
||||
container.appendChild(containerDOM);
|
||||
const canvas = new G.Canvas({
|
||||
containerDOM,
|
||||
width: size[0],
|
||||
height: size[1],
|
||||
pixelRatio: graph.get('pixelRatio')
|
||||
});
|
||||
self.set('canvas', canvas);
|
||||
self.updateCanvas();
|
||||
}
|
||||
initViewport() {
|
||||
const cfgs = this._cfgs;
|
||||
const size = cfgs.size;
|
||||
const graph = cfgs.graph;
|
||||
const pixelRatio = graph.get('pixelRatio') || graph.get('canvas').get('pixelRatio');
|
||||
const widthRatio = graph.get('width') / size[0] * pixelRatio;
|
||||
const heightRatio = graph.get('height') / size[1] * pixelRatio;
|
||||
const canvas = this.get('canvas');
|
||||
const containerDOM = canvas.get('containerDOM');
|
||||
const viewport = createDOM('<div class="' + cfgs.viewportClassName + '" style="position:absolute;left:0;top:0;box-sizing:border-box;border: 2px solid #1980ff"></div>');
|
||||
let x, // 计算拖拽水平方向距离
|
||||
y, // 计算拖拽垂直方向距离
|
||||
dragging, // 是否在拖拽minimap的视口
|
||||
left, // 缓存viewport当前对于画布的x
|
||||
top, // 缓存viewport当前对于画布的y
|
||||
width, // 缓存viewport当前宽度
|
||||
height; // 缓存viewport当前高度
|
||||
containerDOM.addEventListener('mousedown', e => {
|
||||
if (e.target !== viewport) {
|
||||
return;
|
||||
}
|
||||
// 如果视口已经最大了,不需要拖拽
|
||||
const style = viewport.style;
|
||||
left = parseInt(style.left, 10);
|
||||
top = parseInt(style.top, 10);
|
||||
width = parseInt(style.width, 10);
|
||||
height = parseInt(style.height, 10);
|
||||
if (width >= size[0] || height >= size[1]) {
|
||||
return;
|
||||
}
|
||||
dragging = true;
|
||||
x = e.clientX;
|
||||
y = e.clientY;
|
||||
}, false);
|
||||
containerDOM.addEventListener('mousemove', e => {
|
||||
if (!dragging || isNil(e.clientX) || isNil(e.clientY)) {
|
||||
return;
|
||||
}
|
||||
let dx = x - e.clientX;
|
||||
let dy = y - e.clientY;
|
||||
// 若视口移动到最左边或最右边了,仅移动到边界
|
||||
if (left - dx < 0) {
|
||||
dx = left;
|
||||
} else if (left - dx + width > size[0]) {
|
||||
dx = left + width - size[0];
|
||||
}
|
||||
// 若视口移动到最上或最下边了,仅移动到边界
|
||||
if (top - dy < 0) {
|
||||
dy = top;
|
||||
} else if (top - dy + height > size[1]) {
|
||||
dy = top + height - size[1];
|
||||
}
|
||||
left -= dx;
|
||||
top -= dy;
|
||||
// 先移动视口,避免移动到边上以后出现视口闪烁
|
||||
modifyCSS(viewport, {
|
||||
left: left + 'px',
|
||||
top: top + 'px'
|
||||
});
|
||||
graph.translate(dx * widthRatio, dy * heightRatio);
|
||||
x = e.clientX;
|
||||
y = e.clientY;
|
||||
}, false);
|
||||
containerDOM.addEventListener('mouseleave', () => {
|
||||
dragging = false;
|
||||
}, false);
|
||||
containerDOM.addEventListener('mouseup', () => {
|
||||
dragging = false;
|
||||
}, false);
|
||||
this.set('viewport', viewport);
|
||||
containerDOM.appendChild(viewport);
|
||||
}
|
||||
updateCanvas() {
|
||||
const size = this.get('size');
|
||||
const graph = this.get('graph');
|
||||
const canvas = this.get('canvas');
|
||||
// 根据cfgs更新画布内容
|
||||
if (this.get('keyShapeOnly')) {
|
||||
this._updateKeyShapes();
|
||||
} else {
|
||||
this._updateGraphShapes();
|
||||
}
|
||||
const bbox = canvas.getBBox();
|
||||
// 刷新后bbox可能会变,需要重置画布矩阵以缩放到合适的大小
|
||||
const width = max(bbox.width, graph.get('width'));
|
||||
const height = max(bbox.height, graph.get('height'));
|
||||
const pixelRatio = canvas.get('pixelRatio');
|
||||
const ratio = Math.min(size[0] / width, size[1] / height);
|
||||
canvas.resetMatrix();
|
||||
canvas.scale(ratio * pixelRatio, ratio * pixelRatio);
|
||||
// 缩放到适合视口后, 平移到画布中心
|
||||
const dx = (size[0] - width * ratio) / 2;
|
||||
const dy = (size[1] - height * ratio) / 2;
|
||||
canvas.translate(dx * pixelRatio, dy * pixelRatio);
|
||||
canvas.draw();
|
||||
// 更新minimap视口
|
||||
this._updateViewport(ratio, dx, dy);
|
||||
}
|
||||
// 仅在minimap上绘制keyShape
|
||||
// FIXME 如果用户自定义绘制了其他内容,minimap上就无法画出
|
||||
_updateKeyShapes() {
|
||||
const graph = this._cfgs.graph;
|
||||
const canvas = this.get('canvas');
|
||||
const group = canvas.get('children')[0] || canvas.addGroup();
|
||||
const nodes = graph.getNodes();
|
||||
const edges = graph.getEdges();
|
||||
group.clear();
|
||||
// 边可以直接使用keyShape
|
||||
each(edges, edge => {
|
||||
group.add(edge.get('keyShape').clone());
|
||||
});
|
||||
// 节点需要group配合keyShape
|
||||
each(nodes, node => {
|
||||
const parent = group.addGroup();
|
||||
parent.setMatrix(node.get('group').attr('matrix'));
|
||||
parent.add(node.get('keyShape').clone());
|
||||
});
|
||||
}
|
||||
// 将主图上的图形完全复制到小图
|
||||
_updateGraphShapes() {
|
||||
const graph = this.get('graph');
|
||||
const canvas = this.get('canvas');
|
||||
const graphGroup = graph.get('group');
|
||||
const clonedGroup = graphGroup.clone();
|
||||
clonedGroup.resetMatrix();
|
||||
canvas.get('children')[0] = clonedGroup;
|
||||
}
|
||||
// 绘制minimap视口
|
||||
_updateViewport(ratio, dx, dy) {
|
||||
const graph = this.get('graph');
|
||||
const size = this.get('size');
|
||||
const graphWidth = graph.get('width');
|
||||
const graphHeight = graph.get('height');
|
||||
const topLeft = graph.getPointByCanvas(0, 0);
|
||||
const bottomRight = graph.getPointByCanvas(graphWidth, graphHeight);
|
||||
const viewport = this.get('viewport');
|
||||
if (!viewport) {
|
||||
this.initViewport();
|
||||
}
|
||||
// viewport宽高,左上角点的计算
|
||||
let width = (bottomRight.x - topLeft.x) * ratio;
|
||||
let height = (bottomRight.y - topLeft.y) * ratio;
|
||||
const left = topLeft.x * ratio + dx;
|
||||
const top = topLeft.y * ratio + dy;
|
||||
if (width > size[0]) {
|
||||
width = size[0];
|
||||
}
|
||||
if (height > size[1]) {
|
||||
height = size[1];
|
||||
}
|
||||
|
||||
modifyCSS(viewport, {
|
||||
left: left > 0 ? left + 'px' : 0,
|
||||
top: top > 0 ? top + 'px' : 0,
|
||||
width: width + 'px',
|
||||
height: height + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取minimap的画布
|
||||
* @return {object} G的canvas实例
|
||||
*/
|
||||
getCanvas() {
|
||||
return this.get('canvas');
|
||||
}
|
||||
/**
|
||||
* 获取minimap的窗口
|
||||
* @return {object} 窗口的dom实例
|
||||
*/
|
||||
getViewport() {
|
||||
return this.get('viewport');
|
||||
}
|
||||
/**
|
||||
* 获取minimap的容器dom
|
||||
* @return {object} dom
|
||||
*/
|
||||
getContainer() {
|
||||
return this.get('container');
|
||||
}
|
||||
destroy() {
|
||||
const container = this.get('canvas');
|
||||
this.get('canvas').destroy();
|
||||
container.innerHTML = '';
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Minimap;
|
18
src/behavior/edge-tooltip.js
Normal file
18
src/behavior/edge-tooltip.js
Normal file
@ -0,0 +1,18 @@
|
||||
const Util = require('../util');
|
||||
const base = require('./tooltip-base');
|
||||
|
||||
module.exports = Util.mix({
|
||||
getDefaultCfg() {
|
||||
return {
|
||||
item: 'edge',
|
||||
formatText(model) { return 'source:' + model.source + ' target:' + model.target; }
|
||||
};
|
||||
},
|
||||
getEvents() {
|
||||
return {
|
||||
'edge:mouseenter': 'onMouseEnter',
|
||||
'edge:mouseleave': 'onMouseLeave',
|
||||
'edge:mousemove': 'onMouseMove'
|
||||
};
|
||||
}
|
||||
}, base);
|
@ -6,6 +6,7 @@ const behaviors = {
|
||||
'drag-node': require('./drag-node'),
|
||||
'click-select': require('./click-select'),
|
||||
tooltip: require('./tooltip'),
|
||||
'edge-tooltip': require('./edge-tooltip'),
|
||||
'collapse-expand': require('./collapse-expand')
|
||||
};
|
||||
Util.each(behaviors, (behavior, type) => {
|
||||
|
89
src/behavior/tooltip-base.js
Normal file
89
src/behavior/tooltip-base.js
Normal file
@ -0,0 +1,89 @@
|
||||
const Util = require('../util');
|
||||
const OFFSET = 12;
|
||||
|
||||
module.exports = {
|
||||
onMouseEnter(e) {
|
||||
const self = this;
|
||||
if (!self.shouldBegin(e)) {
|
||||
return;
|
||||
}
|
||||
const item = e.item;
|
||||
self.currentTarget = item;
|
||||
if (self.shouldUpdate(e)) {
|
||||
self.showTooltip(e, 'show');
|
||||
self.graph.emit('tooltipchange', { item: e.item, action: 'show' });
|
||||
}
|
||||
},
|
||||
onMouseMove(e) {
|
||||
if (!this.shouldUpdate(e)) {
|
||||
return;
|
||||
}
|
||||
if (!this.currentTarget || e.item !== this.currentTarget) {
|
||||
return;
|
||||
}
|
||||
this.updatePosition(e);
|
||||
},
|
||||
onMouseLeave(e) {
|
||||
if (!this.shouldEnd(e)) {
|
||||
return;
|
||||
}
|
||||
this.hideTooltip();
|
||||
this.graph.emit('tooltipchange', { item: this.currentTarget, action: 'hide' });
|
||||
this.currentTarget = null;
|
||||
},
|
||||
showTooltip(e) {
|
||||
const self = this;
|
||||
if (!e.item) {
|
||||
return;
|
||||
}
|
||||
let container = self.container;
|
||||
if (!container) {
|
||||
container = self._createTooltip(self.graph.get('canvas'));
|
||||
self.container = container;
|
||||
}
|
||||
const text = self.formatText(e.item.get('model'), e);
|
||||
container.innerHTML = text;
|
||||
this.updatePosition(e);
|
||||
Util.modifyCSS(this.container, { visibility: 'visible' });
|
||||
},
|
||||
hideTooltip() {
|
||||
Util.modifyCSS(this.container, {
|
||||
visibility: 'hidden'
|
||||
});
|
||||
},
|
||||
updatePosition(e) {
|
||||
const width = this.width;
|
||||
const height = this.height;
|
||||
const container = this.container;
|
||||
let x = e.x;
|
||||
let y = e.y;
|
||||
const bbox = container.getBoundingClientRect();
|
||||
if (x > width / 2) {
|
||||
x -= (bbox.width);
|
||||
} else {
|
||||
x += OFFSET;
|
||||
}
|
||||
if (y > height / 2) {
|
||||
y -= (bbox.height);
|
||||
} else {
|
||||
y += OFFSET;
|
||||
}
|
||||
const left = x + 'px';
|
||||
const top = y + 'px';
|
||||
Util.modifyCSS(this.container, { left, top });
|
||||
},
|
||||
_createTooltip(canvas) {
|
||||
const el = canvas.get('el');
|
||||
el.style.position = 'relative';
|
||||
const container = Util.createDom('<div class="g6-tooltip g6-' + this.item + '-tooltip"></div>');
|
||||
el.parentNode.appendChild(container);
|
||||
Util.modifyCSS(container, {
|
||||
position: 'absolute',
|
||||
visibility: 'visible'
|
||||
});
|
||||
this.width = canvas.get('width');
|
||||
this.height = canvas.get('height');
|
||||
this.container = container;
|
||||
return container;
|
||||
}
|
||||
};
|
@ -1,9 +1,10 @@
|
||||
const Util = require('../util');
|
||||
const OFFSET = 12;
|
||||
const base = require('./tooltip-base');
|
||||
|
||||
module.exports = {
|
||||
module.exports = Util.mix({
|
||||
getDefaultCfg() {
|
||||
return {
|
||||
item: 'node',
|
||||
formatText(model) { return model.label; }
|
||||
};
|
||||
},
|
||||
@ -13,89 +14,5 @@ module.exports = {
|
||||
'node:mouseleave': 'onMouseLeave',
|
||||
'node:mousemove': 'onMouseMove'
|
||||
};
|
||||
},
|
||||
onMouseEnter(e) {
|
||||
const self = this;
|
||||
if (!self.shouldBegin(e)) {
|
||||
return;
|
||||
}
|
||||
const item = e.item;
|
||||
self.currentTarget = item;
|
||||
if (self.shouldUpdate(e)) {
|
||||
self.showTooltip(e, 'show');
|
||||
self.graph.emit('tooltipchange', { item: e.item, action: 'show' });
|
||||
}
|
||||
},
|
||||
onMouseMove(e) {
|
||||
if (!this.shouldUpdate(e)) {
|
||||
return;
|
||||
}
|
||||
if (!this.currentTarget || e.item !== this.currentTarget) {
|
||||
return;
|
||||
}
|
||||
this.updatePosition(e);
|
||||
},
|
||||
onMouseLeave(e) {
|
||||
if (!this.shouldEnd(e)) {
|
||||
return;
|
||||
}
|
||||
this.hideTooltip();
|
||||
this.graph.emit('tooltipchange', { item: this.currentTarget, action: 'hide' });
|
||||
this.currentTarget = null;
|
||||
},
|
||||
showTooltip(e) {
|
||||
const self = this;
|
||||
if (!e.item) {
|
||||
return;
|
||||
}
|
||||
let container = self.container;
|
||||
if (!container) {
|
||||
container = self._createTooltip(self.graph.get('canvas'));
|
||||
self.container = container;
|
||||
}
|
||||
const text = self.formatText(e.item.get('model'), e);
|
||||
container.innerHTML = text;
|
||||
this.updatePosition(e);
|
||||
Util.modifyCSS(this.container, { visibility: 'visible' });
|
||||
},
|
||||
hideTooltip() {
|
||||
Util.modifyCSS(this.container, {
|
||||
visibility: 'hidden'
|
||||
});
|
||||
},
|
||||
updatePosition(e) {
|
||||
const width = this.width;
|
||||
const height = this.height;
|
||||
const container = this.container;
|
||||
let x = e.x;
|
||||
let y = e.y;
|
||||
const bbox = container.getBoundingClientRect();
|
||||
if (x > width / 2) {
|
||||
x -= (bbox.width + OFFSET);
|
||||
} else {
|
||||
x += OFFSET;
|
||||
}
|
||||
if (y > height / 2) {
|
||||
y -= (bbox.height + OFFSET);
|
||||
} else {
|
||||
y += OFFSET;
|
||||
}
|
||||
const left = x + 'px';
|
||||
const top = y + 'px';
|
||||
Util.modifyCSS(this.container, { left, top });
|
||||
},
|
||||
_createTooltip(canvas) {
|
||||
const el = canvas.get('el');
|
||||
el.style.position = 'relative';
|
||||
const container = Util.createDom('<div class="g6-tooltip"></div>');
|
||||
el.parentNode.appendChild(container);
|
||||
Util.modifyCSS(container, {
|
||||
position: 'absolute',
|
||||
visibility: 'visible'
|
||||
});
|
||||
this.width = canvas.get('width');
|
||||
this.height = canvas.get('height');
|
||||
this.container = container;
|
||||
return container;
|
||||
}
|
||||
};
|
||||
}, base);
|
||||
|
@ -18,7 +18,7 @@ module.exports = {
|
||||
defaultEdge: {
|
||||
shape: 'line',
|
||||
style: {},
|
||||
size: 2,
|
||||
size: 1,
|
||||
color: '#333'
|
||||
},
|
||||
nodeLabel: {
|
||||
|
@ -48,6 +48,10 @@ class Graph extends EventEmitter {
|
||||
* @type Array
|
||||
*/
|
||||
mode: [],
|
||||
/**
|
||||
* 注册插件
|
||||
*/
|
||||
plugins: [],
|
||||
/**
|
||||
* source data
|
||||
* @type object
|
||||
@ -185,6 +189,7 @@ class Graph extends EventEmitter {
|
||||
const modeController = new Controller.Mode(this);
|
||||
const itemController = new Controller.Item(this);
|
||||
this.set({ eventController, viewController, modeController, itemController });
|
||||
this._initPlugins();
|
||||
}
|
||||
_initCanvas() {
|
||||
let container = this.get('container');
|
||||
@ -216,6 +221,14 @@ class Graph extends EventEmitter {
|
||||
}
|
||||
this.set('group', group);
|
||||
}
|
||||
_initPlugins() {
|
||||
const self = this;
|
||||
Util.each(self.get('plugins'), plugin => {
|
||||
if (!plugin.destroyed && plugin.init) {
|
||||
plugin.init(self);
|
||||
}
|
||||
});
|
||||
}
|
||||
get(key) {
|
||||
return this._cfg[key];
|
||||
}
|
||||
@ -907,6 +920,9 @@ class Graph extends EventEmitter {
|
||||
*/
|
||||
destroy() {
|
||||
this.clear();
|
||||
Util.each(this.get('plugins'), plugin => {
|
||||
plugin.destroy();
|
||||
});
|
||||
this.get('eventController').destroy();
|
||||
this.get('itemController').destroy();
|
||||
this.get('modeController').destroy();
|
||||
|
@ -49,14 +49,14 @@ describe('tooltip', () => {
|
||||
expect(style.left).to.equal('64px');
|
||||
expect(style.top).to.equal('64px');
|
||||
graph.emit('node:mouseenter', { x: 410, y: 52, item: rt });
|
||||
expect(style.left).to.equal('372.203px');
|
||||
expect(style.left).to.equal('384.203px');
|
||||
expect(style.top).to.equal('64px');
|
||||
graph.emit('node:mouseenter', { x: 410, y: 410, item: rb });
|
||||
expect(style.left).to.equal('372.203px');
|
||||
expect(style.top).to.equal('380px');
|
||||
expect(style.left).to.equal('384.203px');
|
||||
expect(style.top).to.equal('392px');
|
||||
graph.emit('node:mouseenter', { x: 52, y: 410, item: lb });
|
||||
expect(style.left).to.equal('64px');
|
||||
expect(style.top).to.equal('380px');
|
||||
expect(style.top).to.equal('392px');
|
||||
graph.removeBehaviors('tooltip', 'default');
|
||||
div.removeChild(tooltip);
|
||||
});
|
||||
|
@ -1,5 +1,7 @@
|
||||
const expect = require('chai').expect;
|
||||
const G6 = require('../../../src');
|
||||
const Base = require('../../../plugins/base');
|
||||
|
||||
// const Util = require('../../../src/util');
|
||||
|
||||
const div = document.createElement('div');
|
||||
@ -518,6 +520,38 @@ describe('all node link center', () => {
|
||||
expect(model.color).to.equal('#666');
|
||||
graph.destroy();
|
||||
});
|
||||
it('regist plugin', () => {
|
||||
let count = 0;
|
||||
class Plugin extends Base {
|
||||
getDefaultCfg() {
|
||||
return {
|
||||
b: { d: 2 }
|
||||
};
|
||||
}
|
||||
getEvents() {
|
||||
return { event: 'handler' };
|
||||
}
|
||||
handler() {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
const plugin = new Plugin({ a: 1, b: { c: 2 } });
|
||||
expect(plugin.get('a')).to.equal(1);
|
||||
expect(plugin.get('b').c).to.equal(2);
|
||||
expect(plugin.get('b').d).to.equal(2);
|
||||
const graph = new G6.Graph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500,
|
||||
plugins: [ plugin ]
|
||||
});
|
||||
expect(graph.get('plugins').length).to.equal(1);
|
||||
graph.emit('event');
|
||||
expect(count).to.equal(1);
|
||||
plugin.destroy();
|
||||
graph.emit('event');
|
||||
expect(count).to.equal(1);
|
||||
});
|
||||
it('clear', () => {
|
||||
graph.destroy();
|
||||
expect(graph.destroyed).eql(true);
|
||||
|
177
test/unit/plugins/minimap-spec.js
Normal file
177
test/unit/plugins/minimap-spec.js
Normal file
@ -0,0 +1,177 @@
|
||||
const expect = require('chai').expect;
|
||||
const G6 = require('../../../src');
|
||||
const Minimap = require('../../../plugins/minimap');
|
||||
const Simulate = require('event-simulate');
|
||||
const div = document.createElement('div');
|
||||
div.id = 'minimap';
|
||||
document.body.appendChild(div);
|
||||
const container = document.createElement('div');
|
||||
div.appendChild(container);
|
||||
|
||||
describe('minimap', () => {
|
||||
const minimap = new Minimap({ size: [ 200, 200 ] });
|
||||
const graph = new G6.Graph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500,
|
||||
modes: {
|
||||
default: [{
|
||||
type: 'drag-node',
|
||||
delegate: false
|
||||
}, 'zoom-canvas', 'click-select', 'drag-canvas' ]
|
||||
},
|
||||
pixelRatio: 2,
|
||||
plugins: [ minimap ]
|
||||
});
|
||||
graph.addItem('node', { id: 'node1', label: 'text1', x: 50, y: 50 });
|
||||
graph.addItem('node', { id: 'node2', label: 'text2', x: 120, y: 150 });
|
||||
graph.addItem('edge', { id: 'edge1', source: 'node1', target: 'node2' });
|
||||
it('minimap with default settings & destroy', () => {
|
||||
const canvas = minimap.getCanvas();
|
||||
expect(canvas).not.to.be.undefined;
|
||||
expect(canvas.get('width')).to.equal(200);
|
||||
expect(canvas.get('height')).to.equal(200);
|
||||
const viewport = minimap.getViewport();
|
||||
expect(viewport).not.to.be.undefined;
|
||||
expect(viewport.className.indexOf('g6-minimap-viewport') >= 0).to.be.true;
|
||||
expect(viewport.style.left).to.equal('0px');
|
||||
expect(viewport.style.top).to.equal('0px');
|
||||
expect(viewport.style.width).to.equal('200px');
|
||||
expect(viewport.style.height).to.equal('200px');
|
||||
// 缩小的时候,viewport已经最大了,不会更大
|
||||
graph.zoom(0.5, { x: 250, y: 250 });
|
||||
expect(viewport.style.left).to.equal('0px');
|
||||
expect(viewport.style.top).to.equal('0px');
|
||||
expect(viewport.style.width).to.equal('200px');
|
||||
expect(viewport.style.height).to.equal('200px');
|
||||
graph.zoom(2.5, { x: 250, y: 250 });
|
||||
expect(viewport.style.left).to.equal('20px');
|
||||
expect(viewport.style.top).to.equal('20px');
|
||||
expect(viewport.style.width).to.equal('160px');
|
||||
expect(viewport.style.height).to.equal('160px');
|
||||
minimap.destroy();
|
||||
const container = div.childNodes[1];
|
||||
expect(container.innerHTML).to.equal('');
|
||||
graph.zoom(2.5, { x: 250, y: 250 });
|
||||
expect(viewport.style.left).to.equal('20px');
|
||||
expect(viewport.style.top).to.equal('20px');
|
||||
expect(viewport.style.width).to.equal('160px');
|
||||
expect(viewport.style.height).to.equal('160px');
|
||||
});
|
||||
xit('move viewport', done => {
|
||||
const minimap = new Minimap({ size: [ 200, 200 ] });
|
||||
const graph = new G6.Graph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500,
|
||||
plugins: [ minimap ]
|
||||
});
|
||||
const viewport = minimap.getViewport();
|
||||
const canvas = minimap.getCanvas();
|
||||
graph.zoom(2, { x: 250, y: 250 });
|
||||
graph.translate(100, 100);
|
||||
expect(viewport.style.left).to.equal('30px');
|
||||
expect(viewport.style.top).to.equal('30px');
|
||||
expect(viewport.style.width).to.equal('100px');
|
||||
expect(viewport.style.height).to.equal('100px');
|
||||
const container = canvas.get('containerDOM');
|
||||
Simulate.simulate(viewport, 'mousedown', {
|
||||
clientX: 100,
|
||||
clientY: 100,
|
||||
target: viewport
|
||||
});
|
||||
Simulate.simulate(container, 'mousemove', {
|
||||
clientX: 120,
|
||||
clientY: 120
|
||||
});
|
||||
Simulate.simulate(container, 'mouseup', {
|
||||
clientX: 120,
|
||||
clientY: 120
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(viewport.style.left).to.equal('50px');
|
||||
expect(viewport.style.top).to.equal('50px');
|
||||
expect(viewport.style.width).to.equal('100px');
|
||||
expect(viewport.style.height).to.equal('100px');
|
||||
const matrix = graph.get('group').getMatrix();
|
||||
expect(matrix[0]).to.equal(2);
|
||||
expect(matrix[4]).to.equal(2);
|
||||
expect(matrix[6]).to.equal(-250);
|
||||
expect(matrix[7]).to.equal(-250);
|
||||
Simulate.simulate(viewport, 'mousedown', {
|
||||
clientX: 200,
|
||||
clientY: 200,
|
||||
target: viewport
|
||||
});
|
||||
Simulate.simulate(container, 'mousemove', {
|
||||
clientX: 0,
|
||||
clientY: 0
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(viewport.style.left).to.equal('0px');
|
||||
expect(viewport.style.top).to.equal('0px');
|
||||
expect(viewport.style.width).to.equal('100px');
|
||||
expect(viewport.style.height).to.equal('100px');
|
||||
const matrix = graph.get('group').getMatrix();
|
||||
expect(matrix[0]).to.equal(2);
|
||||
expect(matrix[4]).to.equal(2);
|
||||
expect(matrix[6]).to.equal(0);
|
||||
expect(matrix[7]).to.equal(0);
|
||||
graph.destroy();
|
||||
done();
|
||||
}, 50);
|
||||
}, 50);
|
||||
});
|
||||
it('minimap container', () => {
|
||||
const minimap = new Minimap({ container, size: [ 200, 200 ], className: 'test-className' });
|
||||
const graph = new G6.Graph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500,
|
||||
plugins: [ minimap ],
|
||||
modes: {
|
||||
default: [{
|
||||
type: 'drag-node',
|
||||
delegate: false
|
||||
}, 'zoom-canvas', 'click-select', 'drag-canvas' ]
|
||||
},
|
||||
pixelRatio: 2
|
||||
});
|
||||
expect(container.childNodes).not.to.be.undefined;
|
||||
expect(container.className).to.equal('test-className');
|
||||
expect(container.style.width).to.equal('200px');
|
||||
expect(container.style.width).to.equal('200px');
|
||||
expect(container.childNodes[0].tagName).to.equal('DIV');
|
||||
expect(container.childNodes[0].style.position).to.equal('relative');
|
||||
expect(container.childNodes[0].childNodes[0]).to.equal(minimap.getCanvas().get('el'));
|
||||
graph.destroy();
|
||||
});
|
||||
it('keyShapeOnly minimap', () => {
|
||||
const minimap = new Minimap({ size: [ 200, 200 ], keyShapeOnly: true });
|
||||
const graph = new G6.Graph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500,
|
||||
plugins: [ minimap ]
|
||||
});
|
||||
graph.addItem('node', { id: 'node1', label: 'text1', x: 50, y: 50 });
|
||||
graph.addItem('node', { id: 'node2', label: 'text2', x: 120, y: 150 });
|
||||
graph.addItem('edge', { id: 'edge1', source: 'node1', target: 'node2' });
|
||||
const canvas = minimap.getCanvas();
|
||||
const shapeGroup = canvas.get('children')[0].get('children');
|
||||
expect(shapeGroup.length).to.equal(3);
|
||||
expect(shapeGroup[0].type).to.equal('path');
|
||||
expect(shapeGroup[0].attr('path')).not.to.be.undefined;
|
||||
expect(shapeGroup[1].type).to.equal('group');
|
||||
expect(shapeGroup[1].getMatrix()[6]).to.equal(50);
|
||||
expect(shapeGroup[1].getMatrix()[7]).to.equal(50);
|
||||
expect(shapeGroup[1].get('children').length).to.equal(1);
|
||||
expect(shapeGroup[1].get('children')[0].type).to.equal('circle');
|
||||
expect(shapeGroup[2].type).to.equal('group');
|
||||
expect(shapeGroup[2].getMatrix()[6]).to.equal(120);
|
||||
expect(shapeGroup[2].getMatrix()[7]).to.equal(150);
|
||||
expect(shapeGroup[2].get('children').length).to.equal(1);
|
||||
expect(shapeGroup[2].get('children')[0].type).to.equal('circle');
|
||||
graph.destroy();
|
||||
});
|
||||
});
|
@ -1,11 +1,29 @@
|
||||
const webpack = require('webpack');
|
||||
const resolve = require('path').resolve;
|
||||
const shelljs = require('shelljs');
|
||||
const _ = require('lodash');
|
||||
|
||||
const entry = {
|
||||
G6: './src/index.js'
|
||||
G6: './src/index.js',
|
||||
G6Plugins: './plugins/index.js'
|
||||
};
|
||||
|
||||
shelljs.ls(resolve(__dirname, 'plugins')).forEach(pluginPath => {
|
||||
if (pluginPath === 'base.js') return;
|
||||
if (pluginPath !== 'index.js') {
|
||||
const fileDirs = pluginPath.split('-');
|
||||
let moduleName = '';
|
||||
for (let i = 0; i < fileDirs.length; i++) {
|
||||
const segment = fileDirs[i];
|
||||
moduleName += (segment.charAt(0).toUpperCase() + segment.substring(1));
|
||||
}
|
||||
entry[moduleName] = `./plugins/${pluginPath}/index.js`;
|
||||
} else {
|
||||
const moduleName = 'plugins';
|
||||
entry[moduleName] = './plugins/index.js';
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
devtool: 'cheap-source-map',
|
||||
|
Loading…
Reference in New Issue
Block a user