First commit
This commit is contained in:
commit
77af1bb8fb
4
.babelrc
Executable file
4
.babelrc
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"presets": ["es2015"],
|
||||||
|
"plugins": ["babel-plugin-add-module-exports"]
|
||||||
|
}
|
||||||
182
.eslintrc
Executable file
182
.eslintrc
Executable file
@ -0,0 +1,182 @@
|
|||||||
|
{
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"globalReturn": true,
|
||||||
|
"jsx": true,
|
||||||
|
"modules": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"globals": {
|
||||||
|
"document": false,
|
||||||
|
"escape": false,
|
||||||
|
"navigator": false,
|
||||||
|
"unescape": false,
|
||||||
|
"window": false,
|
||||||
|
"describe": true,
|
||||||
|
"before": true,
|
||||||
|
"it": true,
|
||||||
|
"expect": true,
|
||||||
|
"sinon": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
|
||||||
|
"plugins": [
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
"rules": {
|
||||||
|
"block-scoped-var": 2,
|
||||||
|
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
|
||||||
|
"comma-dangle": [2, "never"],
|
||||||
|
"comma-spacing": [2, { "before": false, "after": true }],
|
||||||
|
"comma-style": [2, "last"],
|
||||||
|
"complexity": 0,
|
||||||
|
"consistent-return": 2,
|
||||||
|
"consistent-this": 0,
|
||||||
|
"curly": [2, "multi-line"],
|
||||||
|
"default-case": 0,
|
||||||
|
"dot-location": [2, "property"],
|
||||||
|
"dot-notation": 0,
|
||||||
|
"eol-last": 2,
|
||||||
|
"eqeqeq": [2, "allow-null"],
|
||||||
|
"func-names": 0,
|
||||||
|
"func-style": 0,
|
||||||
|
"generator-star-spacing": [2, "both"],
|
||||||
|
"guard-for-in": 0,
|
||||||
|
"handle-callback-err": [2, "^(err|error|anySpecificError)$" ],
|
||||||
|
"indent": [2, "tab", { "SwitchCase": 1 }],
|
||||||
|
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
|
||||||
|
"linebreak-style": 0,
|
||||||
|
"max-depth": 0,
|
||||||
|
"max-len": [2, 120, 4],
|
||||||
|
"max-nested-callbacks": 0,
|
||||||
|
"max-params": 0,
|
||||||
|
"max-statements": 0,
|
||||||
|
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
|
||||||
|
"new-parens": 2,
|
||||||
|
"no-alert": 0,
|
||||||
|
"no-array-constructor": 2,
|
||||||
|
"no-bitwise": 0,
|
||||||
|
"no-caller": 2,
|
||||||
|
"no-catch-shadow": 0,
|
||||||
|
"no-cond-assign": 2,
|
||||||
|
"no-console": 0,
|
||||||
|
"no-constant-condition": 0,
|
||||||
|
"no-continue": 0,
|
||||||
|
"no-control-regex": 2,
|
||||||
|
"no-debugger": 2,
|
||||||
|
"no-delete-var": 2,
|
||||||
|
"no-div-regex": 0,
|
||||||
|
"no-dupe-args": 2,
|
||||||
|
"no-dupe-keys": 2,
|
||||||
|
"no-duplicate-case": 2,
|
||||||
|
"no-else-return": 2,
|
||||||
|
"no-empty": 0,
|
||||||
|
"no-empty-character-class": 2,
|
||||||
|
"no-empty-label": 2,
|
||||||
|
"no-eq-null": 0,
|
||||||
|
"no-eval": 2,
|
||||||
|
"no-ex-assign": 2,
|
||||||
|
"no-extend-native": 2,
|
||||||
|
"no-extra-bind": 2,
|
||||||
|
"no-extra-boolean-cast": 2,
|
||||||
|
"no-extra-parens": 0,
|
||||||
|
"no-extra-semi": 0,
|
||||||
|
"no-extra-strict": 0,
|
||||||
|
"no-fallthrough": 2,
|
||||||
|
"no-floating-decimal": 2,
|
||||||
|
"no-func-assign": 2,
|
||||||
|
"no-implied-eval": 2,
|
||||||
|
"no-inline-comments": 0,
|
||||||
|
"no-inner-declarations": [2, "functions"],
|
||||||
|
"no-invalid-regexp": 2,
|
||||||
|
"no-irregular-whitespace": 2,
|
||||||
|
"no-iterator": 2,
|
||||||
|
"no-label-var": 2,
|
||||||
|
"no-labels": 2,
|
||||||
|
"no-lone-blocks": 0,
|
||||||
|
"no-lonely-if": 0,
|
||||||
|
"no-loop-func": 0,
|
||||||
|
"no-mixed-requires": 0,
|
||||||
|
"no-mixed-spaces-and-tabs": [2, false],
|
||||||
|
"no-multi-spaces": 2,
|
||||||
|
"no-multi-str": 2,
|
||||||
|
"no-multiple-empty-lines": [2, { "max": 1 }],
|
||||||
|
"no-native-reassign": 2,
|
||||||
|
"no-negated-in-lhs": 2,
|
||||||
|
"no-nested-ternary": 0,
|
||||||
|
"no-new": 2,
|
||||||
|
"no-new-func": 2,
|
||||||
|
"no-new-object": 2,
|
||||||
|
"no-new-require": 2,
|
||||||
|
"no-new-wrappers": 2,
|
||||||
|
"no-obj-calls": 2,
|
||||||
|
"no-octal": 2,
|
||||||
|
"no-octal-escape": 2,
|
||||||
|
"no-path-concat": 0,
|
||||||
|
"no-plusplus": 0,
|
||||||
|
"no-process-env": 0,
|
||||||
|
"no-process-exit": 0,
|
||||||
|
"no-proto": 2,
|
||||||
|
"no-redeclare": 2,
|
||||||
|
"no-regex-spaces": 2,
|
||||||
|
"no-reserved-keys": 0,
|
||||||
|
"no-restricted-modules": 0,
|
||||||
|
"no-return-assign": 2,
|
||||||
|
"no-script-url": 0,
|
||||||
|
"no-self-compare": 2,
|
||||||
|
"no-sequences": 2,
|
||||||
|
"no-shadow": 0,
|
||||||
|
"no-shadow-restricted-names": 2,
|
||||||
|
"no-spaced-func": 2,
|
||||||
|
"no-sparse-arrays": 2,
|
||||||
|
"no-sync": 0,
|
||||||
|
"no-ternary": 0,
|
||||||
|
"no-throw-literal": 2,
|
||||||
|
"no-trailing-spaces": 2,
|
||||||
|
"no-undef": 2,
|
||||||
|
"no-undef-init": 2,
|
||||||
|
"no-undefined": 0,
|
||||||
|
"no-underscore-dangle": 0,
|
||||||
|
"no-unneeded-ternary": 2,
|
||||||
|
"no-unreachable": 2,
|
||||||
|
"no-unused-expressions": 0,
|
||||||
|
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
|
||||||
|
"no-var": 0,
|
||||||
|
"no-void": 0,
|
||||||
|
"no-warning-comments": 0,
|
||||||
|
"no-with": 2,
|
||||||
|
"one-var": 0,
|
||||||
|
"operator-assignment": 0,
|
||||||
|
"operator-linebreak": [2, "after"],
|
||||||
|
"padded-blocks": 0,
|
||||||
|
"quote-props": 0,
|
||||||
|
"quotes": [2, "single", "avoid-escape"],
|
||||||
|
"radix": 2,
|
||||||
|
"semi": [2, "always"],
|
||||||
|
"semi-spacing": 0,
|
||||||
|
"sort-vars": 0,
|
||||||
|
"space-before-blocks": [2, "always"],
|
||||||
|
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
|
||||||
|
"space-in-brackets": 0,
|
||||||
|
"space-in-parens": [2, "never"],
|
||||||
|
"space-infix-ops": 2,
|
||||||
|
"space-return-throw-case": 2,
|
||||||
|
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||||
|
"spaced-comment": [2, "always"],
|
||||||
|
"strict": 0,
|
||||||
|
"use-isnan": 2,
|
||||||
|
"valid-jsdoc": 0,
|
||||||
|
"valid-typeof": 2,
|
||||||
|
"vars-on-top": 2,
|
||||||
|
"wrap-iife": [2, "any"],
|
||||||
|
"wrap-regex": 0,
|
||||||
|
"yoda": [2, "never"]
|
||||||
|
}
|
||||||
|
}
|
||||||
29
.gitignore
vendored
Executable file
29
.gitignore
vendored
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
67
index.html
Normal file
67
index.html
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Simple Gantt</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.gantt-container {
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="node_modules/moment/min/moment.min.js"></script>
|
||||||
|
<script src="node_modules/snapsvg/dist/snap.svg-min.js"></script>
|
||||||
|
<script src="lib/gantt.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>Interactive Gantt Chart entirely made in SVG!</h2>
|
||||||
|
<div class="gantt-container">
|
||||||
|
<svg id="gantt" width="400" height="600"></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var arr = [1, 2, 3, 4, 5, 6];
|
||||||
|
var tasks = arr.map(function(i){
|
||||||
|
return {
|
||||||
|
start: "2016-10-0"+i,
|
||||||
|
end: "2016-10-2"+i,
|
||||||
|
name: "Task "+i,
|
||||||
|
id: i,
|
||||||
|
progress: i*10
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tasks[0].dependencies = '2, 3';
|
||||||
|
|
||||||
|
var gantt_chart = gantt("#gantt", tasks, {
|
||||||
|
date_format: "YYYY-MM-DD",
|
||||||
|
bar: {
|
||||||
|
height: 20
|
||||||
|
}
|
||||||
|
// events: {
|
||||||
|
// bar_on_click: function (task) {
|
||||||
|
// console.log(task);
|
||||||
|
// },
|
||||||
|
// bar_on_date_change: function(task, start, end) {
|
||||||
|
// console.log(task, start, end);
|
||||||
|
// },
|
||||||
|
// bar_on_progress_change: function(task, progress) {
|
||||||
|
// console.log(task, progress);
|
||||||
|
// },
|
||||||
|
// on_viewmode_change: function(mode) {
|
||||||
|
// console.log(mode);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
console.log(gantt_chart);
|
||||||
|
// gantt.render();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1762
lib/gantt.js
Normal file
1762
lib/gantt.js
Normal file
File diff suppressed because one or more lines are too long
1
lib/gantt.js.map
Normal file
1
lib/gantt.js.map
Normal file
File diff suppressed because one or more lines are too long
2
lib/gantt.min.js
vendored
Normal file
2
lib/gantt.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
lib/gantt.min.js.map
Normal file
1
lib/gantt.min.js.map
Normal file
File diff suppressed because one or more lines are too long
52
package.json
Executable file
52
package.json
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "gantt",
|
||||||
|
"version": "0.1.3",
|
||||||
|
"description": "Visualize tasks on a timeline",
|
||||||
|
"main": "lib/gantt.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --mode=build",
|
||||||
|
"dev": "webpack --progress --colors --watch --mode=dev",
|
||||||
|
"test": "mocha --compilers js:babel-core/register --colors -w ./test/*.spec.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel": "6.3.13",
|
||||||
|
"babel-core": "6.1.18",
|
||||||
|
"babel-eslint": "5.0.0",
|
||||||
|
"babel-loader": "6.1.0",
|
||||||
|
"babel-plugin-add-module-exports": "0.1.2",
|
||||||
|
"babel-preset-es2015": "6.3.13",
|
||||||
|
"chai": "3.4.1",
|
||||||
|
"css-loader": "^0.26.1",
|
||||||
|
"eslint": "1.7.2",
|
||||||
|
"eslint-loader": "1.1.0",
|
||||||
|
"mocha": "2.3.4",
|
||||||
|
"node-sass": "^4.0.0",
|
||||||
|
"sass-loader": "^4.1.0",
|
||||||
|
"style-loader": "^0.13.1",
|
||||||
|
"webpack": "1.12.9",
|
||||||
|
"yargs": "3.32.0"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/krasimir/webpack-library-starter.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"webpack",
|
||||||
|
"es6",
|
||||||
|
"starter",
|
||||||
|
"library",
|
||||||
|
"universal",
|
||||||
|
"umd",
|
||||||
|
"commonjs"
|
||||||
|
],
|
||||||
|
"author": "Krasimir Tsonev",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/krasimir/webpack-library-starter/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/krasimir/webpack-library-starter",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.17.1",
|
||||||
|
"snapsvg": "^0.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
105
src/Arrow.js
Normal file
105
src/Arrow.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/* global Snap */
|
||||||
|
/*
|
||||||
|
Class: Arrow
|
||||||
|
from_task ---> to_task
|
||||||
|
|
||||||
|
Opts:
|
||||||
|
gantt (Gantt object)
|
||||||
|
from_task (Bar object)
|
||||||
|
to_task (Bar object)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function Arrow(gt, from_task, to_task) {
|
||||||
|
|
||||||
|
const self = {};
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
self.from_task = from_task;
|
||||||
|
self.to_task = to_task;
|
||||||
|
prepare();
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare() {
|
||||||
|
|
||||||
|
self.start_x = from_task.$bar.getX() + from_task.$bar.getWidth() / 2;
|
||||||
|
|
||||||
|
const condition = () =>
|
||||||
|
to_task.$bar.getX() < self.start_x + gt.config.padding &&
|
||||||
|
self.start_x > from_task.$bar.getX() + gt.config.padding;
|
||||||
|
|
||||||
|
while(condition()) {
|
||||||
|
self.start_x -= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.start_y = gt.config.header_height + gt.config.bar.height +
|
||||||
|
(gt.config.padding + gt.config.bar.height) * from_task.task._index +
|
||||||
|
gt.config.padding;
|
||||||
|
|
||||||
|
self.end_x = to_task.$bar.getX() - gt.config.padding / 2;
|
||||||
|
self.end_y = gt.config.header_height + gt.config.bar.height / 2 +
|
||||||
|
(gt.config.padding + gt.config.bar.height) * to_task.task._index +
|
||||||
|
gt.config.padding;
|
||||||
|
|
||||||
|
const from_is_below_to = (from_task.task._index > to_task.task._index);
|
||||||
|
self.curve = gt.config.arrow.curve;
|
||||||
|
self.clockwise = from_is_below_to ? 1 : 0;
|
||||||
|
self.curve_y = from_is_below_to ? -self.curve : self.curve;
|
||||||
|
self.offset = from_is_below_to ?
|
||||||
|
self.end_y + gt.config.arrow.curve :
|
||||||
|
self.end_y - gt.config.arrow.curve;
|
||||||
|
|
||||||
|
self.path =
|
||||||
|
Snap.format('M {start_x} {start_y} V {offset} ' +
|
||||||
|
'a {curve} {curve} 0 0 {clockwise} {curve} {curve_y} ' +
|
||||||
|
'L {end_x} {end_y} m -5 -5 l 5 5 l -5 5',
|
||||||
|
{
|
||||||
|
start_x: self.start_x,
|
||||||
|
start_y: self.start_y,
|
||||||
|
end_x: self.end_x,
|
||||||
|
end_y: self.end_y,
|
||||||
|
offset: self.offset,
|
||||||
|
curve: self.curve,
|
||||||
|
clockwise: self.clockwise,
|
||||||
|
curve_y: self.curve_y
|
||||||
|
});
|
||||||
|
|
||||||
|
if(to_task.$bar.getX() < from_task.$bar.getX() + gt.config.padding) {
|
||||||
|
self.path =
|
||||||
|
Snap.format('M {start_x} {start_y} v {down_1} ' +
|
||||||
|
'a {curve} {curve} 0 0 1 -{curve} {curve} H {left} ' +
|
||||||
|
'a {curve} {curve} 0 0 {clockwise} -{curve} {curve_y} V {down_2} ' +
|
||||||
|
'a {curve} {curve} 0 0 {clockwise} {curve} {curve_y} ' +
|
||||||
|
'L {end_x} {end_y} m -5 -5 l 5 5 l -5 5',
|
||||||
|
{
|
||||||
|
start_x: self.start_x,
|
||||||
|
start_y: self.start_y,
|
||||||
|
end_x: self.end_x,
|
||||||
|
end_y: self.end_y,
|
||||||
|
down_1: gt.config.padding / 2 - self.curve,
|
||||||
|
down_2: to_task.$bar.getY() + to_task.$bar.get('height') / 2 - self.curve_y,
|
||||||
|
left: to_task.$bar.getX() - gt.config.padding,
|
||||||
|
offset: self.offset,
|
||||||
|
curve: self.curve,
|
||||||
|
clockwise: self.clockwise,
|
||||||
|
curve_y: self.curve_y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
self.element = self.gantt.canvas.path(self.path)
|
||||||
|
.attr('data-from', self.from_task.task.id)
|
||||||
|
.attr('data-to', self.to_task.task.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() { // eslint-disable-line
|
||||||
|
self.prepare();
|
||||||
|
self.element.attr('d', self.path);
|
||||||
|
}
|
||||||
|
self.update = update;
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
528
src/Bar.js
Normal file
528
src/Bar.js
Normal file
@ -0,0 +1,528 @@
|
|||||||
|
/* global Snap */
|
||||||
|
/*
|
||||||
|
Class: Bar
|
||||||
|
|
||||||
|
Opts:
|
||||||
|
canvas [reqd]
|
||||||
|
task [reqd]
|
||||||
|
column_width [reqd]
|
||||||
|
x
|
||||||
|
y
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function Bar(gt, task) {
|
||||||
|
|
||||||
|
const self = {};
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
set_defaults();
|
||||||
|
prepare();
|
||||||
|
draw();
|
||||||
|
bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_defaults() {
|
||||||
|
self.action_completed = false;
|
||||||
|
self.task = task;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare() {
|
||||||
|
prepare_values();
|
||||||
|
prepare_plugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare_values() {
|
||||||
|
self.invalid = self.task.invalid;
|
||||||
|
self.height = gt.config.bar.height;
|
||||||
|
self.x = compute_x();
|
||||||
|
self.y = compute_y();
|
||||||
|
self.corner_radius = 3;
|
||||||
|
self.duration = (self.task._end.diff(self.task._start, 'hours') + 24) / gt.config.step;
|
||||||
|
self.width = gt.config.column_width * self.duration;
|
||||||
|
self.progress_width = gt.config.column_width * self.duration * (self.task.progress / 100) || 0;
|
||||||
|
self.group = gt.canvas.group().addClass('bar-wrapper');
|
||||||
|
self.bar_group = gt.canvas.group().addClass('bar-group').appendTo(self.group);
|
||||||
|
self.handle_group = gt.canvas.group().addClass('handle-group').appendTo(self.group);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare_plugins() {
|
||||||
|
Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
|
||||||
|
Element.prototype.getX = function () {
|
||||||
|
return +this.attr('x');
|
||||||
|
};
|
||||||
|
Element.prototype.getY = function () {
|
||||||
|
return +this.attr('y');
|
||||||
|
};
|
||||||
|
Element.prototype.getWidth = function () {
|
||||||
|
return +this.attr('width');
|
||||||
|
};
|
||||||
|
Element.prototype.getHeight = function () {
|
||||||
|
return +this.attr('height');
|
||||||
|
};
|
||||||
|
Element.prototype.getEndX = function () {
|
||||||
|
return this.getX() + this.getWidth();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
draw_bar();
|
||||||
|
draw_progress_bar();
|
||||||
|
draw_label();
|
||||||
|
draw_resize_handles();
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw_bar() {
|
||||||
|
self.$bar = gt.canvas.rect(self.x, self.y,
|
||||||
|
self.width, self.height,
|
||||||
|
self.corner_radius, self.corner_radius)
|
||||||
|
.addClass('bar')
|
||||||
|
.appendTo(self.bar_group);
|
||||||
|
if (self.invalid) {
|
||||||
|
self.$bar.addClass('bar-invalid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw_progress_bar() {
|
||||||
|
if (self.invalid) return;
|
||||||
|
self.$bar_progress = gt.canvas.rect(self.x, self.y,
|
||||||
|
self.progress_width, self.height,
|
||||||
|
self.corner_radius, self.corner_radius)
|
||||||
|
.addClass('bar-progress')
|
||||||
|
.appendTo(self.bar_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw_label() {
|
||||||
|
gt.canvas.text(self.x + self.width / 2,
|
||||||
|
self.y + self.height / 2,
|
||||||
|
self.task.name)
|
||||||
|
.addClass('bar-label')
|
||||||
|
.appendTo(self.bar_group);
|
||||||
|
update_label_position();
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw_resize_handles() {
|
||||||
|
if (self.invalid) return;
|
||||||
|
|
||||||
|
const bar = self.$bar,
|
||||||
|
bar_progress = self.$bar_progress,
|
||||||
|
handle_width = 8;
|
||||||
|
|
||||||
|
gt.canvas.rect(bar.getX() + bar.getWidth() - 9, bar.getY() + 1,
|
||||||
|
handle_width, self.height - 2, self.corner_radius, self.corner_radius)
|
||||||
|
.addClass('handle right')
|
||||||
|
.appendTo(self.handle_group);
|
||||||
|
gt.canvas.rect(bar.getX() + 1, bar.getY() + 1,
|
||||||
|
handle_width, self.height - 2, self.corner_radius, self.corner_radius)
|
||||||
|
.addClass('handle left')
|
||||||
|
.appendTo(self.handle_group);
|
||||||
|
|
||||||
|
if (self.task.progress && self.task.progress < 100) {
|
||||||
|
gt.canvas.polygon(
|
||||||
|
bar_progress.getEndX() - 5, bar_progress.getY() + bar_progress.getHeight(),
|
||||||
|
bar_progress.getEndX() + 5, bar_progress.getY() + bar_progress.getHeight(),
|
||||||
|
bar_progress.getEndX(), bar_progress.getY() + bar_progress.getHeight() - 8.66
|
||||||
|
)
|
||||||
|
.addClass('handle progress')
|
||||||
|
.appendTo(self.handle_group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function draw_invalid_bar() {
|
||||||
|
// const x = moment().startOf('day').diff(gt.gantt_start, 'hours') /
|
||||||
|
// gt.config.step * gt.config.column_width;
|
||||||
|
|
||||||
|
// gt.canvas.rect(x, self.y,
|
||||||
|
// gt.config.column_width * 2, self.height,
|
||||||
|
// self.corner_radius, self.corner_radius)
|
||||||
|
// .addClass('bar-invalid')
|
||||||
|
// .appendTo(self.bar_group);
|
||||||
|
|
||||||
|
// gt.canvas.text(
|
||||||
|
// x + gt.config.column_width,
|
||||||
|
// self.y + self.height / 2,
|
||||||
|
// 'Dates not set')
|
||||||
|
// .addClass('bar-label big')
|
||||||
|
// .appendTo(self.bar_group);
|
||||||
|
// }
|
||||||
|
|
||||||
|
function bind() {
|
||||||
|
if (self.invalid) return;
|
||||||
|
show_details();
|
||||||
|
bind_resize();
|
||||||
|
bind_drag();
|
||||||
|
bind_resize_progress();
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_details() {
|
||||||
|
const popover_group = gt.element_groups.details;
|
||||||
|
let details_box = popover_group.select('.details-wrapper');
|
||||||
|
|
||||||
|
if (!details_box) {
|
||||||
|
details_box = gt.canvas.group()
|
||||||
|
.addClass('details-wrapper')
|
||||||
|
.appendTo(popover_group);
|
||||||
|
gt.canvas.rect(0, 0, 0, 110, 2, 2)
|
||||||
|
.addClass('details-container')
|
||||||
|
.appendTo(details_box);
|
||||||
|
gt.canvas.text(0, 0, '')
|
||||||
|
.attr({ dx: 10, dy: 30 })
|
||||||
|
.addClass('details-heading')
|
||||||
|
.appendTo(details_box);
|
||||||
|
gt.canvas.text(0, 0, '')
|
||||||
|
.attr({ dx: 10, dy: 65 })
|
||||||
|
.addClass('details-body')
|
||||||
|
.appendTo(details_box);
|
||||||
|
gt.canvas.text(0, 0, '')
|
||||||
|
.attr({ dx: 10, dy: 90 })
|
||||||
|
.addClass('details-body')
|
||||||
|
.appendTo(details_box);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.group.mouseover((e, x, y) => {
|
||||||
|
popover_group.removeClass('hide');
|
||||||
|
|
||||||
|
const pos = get_details_position();
|
||||||
|
details_box.transform(`t${pos.x},${pos.y}`);
|
||||||
|
|
||||||
|
const start_date = self.task._start.format('MMM D'),
|
||||||
|
end_date = self.task._end.format('MMM D'),
|
||||||
|
heading = `${self.task.name}: ${start_date} - ${end_date}`;
|
||||||
|
|
||||||
|
const $heading = popover_group
|
||||||
|
.select('.details-heading')
|
||||||
|
.attr('text', heading);
|
||||||
|
|
||||||
|
const bbox = $heading.getBBox();
|
||||||
|
details_box.select('.details-container')
|
||||||
|
.attr({ width: bbox.width + 20 });
|
||||||
|
|
||||||
|
const duration = self.task._end.diff(self.task._start, 'days'),
|
||||||
|
body1 = `Duration: ${duration} days`,
|
||||||
|
body2 = self.task.progress ?
|
||||||
|
`Progress: ${self.task.progress}` : '';
|
||||||
|
|
||||||
|
const $body = popover_group.selectAll('.details-body');
|
||||||
|
$body[0].attr('text', body1);
|
||||||
|
$body[1].attr('text', body2);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.group.mouseout(() => {
|
||||||
|
setTimeout(() => popover_group.addClass('hide'), 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_details_position() {
|
||||||
|
return {
|
||||||
|
x: self.$bar.getEndX() + 2,
|
||||||
|
y: self.$bar.getY() - 10
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind_resize() {
|
||||||
|
const { left, right } = get_handles();
|
||||||
|
|
||||||
|
left.drag(onmove_left, onstart, onstop_left);
|
||||||
|
right.drag(onmove_right, onstart, onstop_right);
|
||||||
|
|
||||||
|
function onstart() {
|
||||||
|
onstart();
|
||||||
|
this.ox = this.getX();
|
||||||
|
this.oy = this.getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmove_right(dx, dy) {
|
||||||
|
onmove_handle_right(dx, dy);
|
||||||
|
}
|
||||||
|
function onstop_right() {
|
||||||
|
onstop_handle_right();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmove_left(dx, dy) {
|
||||||
|
onmove_handle_left(dx, dy);
|
||||||
|
}
|
||||||
|
function onstop_left() {
|
||||||
|
onstop_handle_left();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_handles() {
|
||||||
|
return {
|
||||||
|
left: self.handle_group.select('.handle.left'),
|
||||||
|
right: self.handle_group.select('.handle.right')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind_drag() {
|
||||||
|
self.bar_group.drag(onmove, onstart, onstop);
|
||||||
|
|
||||||
|
function onmove(dx, dy) {
|
||||||
|
onmove(dx, dy);
|
||||||
|
}
|
||||||
|
function onstop() {
|
||||||
|
onstop();
|
||||||
|
}
|
||||||
|
function onstart() {
|
||||||
|
onstart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind_resize_progress() {
|
||||||
|
const bar = self.$bar,
|
||||||
|
bar_progress = self.$bar_progress,
|
||||||
|
handle = self.group.select('.handle.progress');
|
||||||
|
handle && handle.drag(onmove, onstart, onstop);
|
||||||
|
|
||||||
|
function onmove(dx, dy) {
|
||||||
|
if (dx > bar_progress.max_dx) {
|
||||||
|
dx = bar_progress.max_dx;
|
||||||
|
}
|
||||||
|
if (dx < bar_progress.min_dx) {
|
||||||
|
dx = bar_progress.min_dx;
|
||||||
|
}
|
||||||
|
|
||||||
|
bar_progress.attr('width', bar_progress.owidth + dx);
|
||||||
|
handle.transform(`t{dx},0`);
|
||||||
|
bar_progress.finaldx = dx;
|
||||||
|
}
|
||||||
|
function onstop() {
|
||||||
|
if (!bar_progress.finaldx) return;
|
||||||
|
progress_changed();
|
||||||
|
set_action_completed();
|
||||||
|
}
|
||||||
|
function onstart() {
|
||||||
|
bar_progress.finaldx = 0;
|
||||||
|
bar_progress.owidth = bar_progress.getWidth();
|
||||||
|
bar_progress.min_dx = -bar_progress.getWidth();
|
||||||
|
bar_progress.max_dx = bar.getWidth() - bar_progress.getWidth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onstart() {
|
||||||
|
const bar = self.$bar;
|
||||||
|
bar.ox = bar.getX();
|
||||||
|
bar.oy = bar.getY();
|
||||||
|
bar.owidth = bar.getWidth();
|
||||||
|
bar.finaldx = 0;
|
||||||
|
run_method_for_dependencies('onstart');
|
||||||
|
}
|
||||||
|
self.onstart = onstart;
|
||||||
|
|
||||||
|
function onmove(dx, dy) {
|
||||||
|
const bar = self.$bar;
|
||||||
|
bar.finaldx = get_snap_position(dx);
|
||||||
|
update_bar_position(bar.ox + bar.finaldx);
|
||||||
|
run_method_for_dependencies('onmove', [dx, dy]);
|
||||||
|
}
|
||||||
|
self.onmove = onmove;
|
||||||
|
|
||||||
|
function onstop() {
|
||||||
|
const bar = self.$bar;
|
||||||
|
if (!bar.finaldx) return;
|
||||||
|
date_changed();
|
||||||
|
set_action_completed();
|
||||||
|
run_method_for_dependencies('onstop');
|
||||||
|
}
|
||||||
|
self.onstop = onstop;
|
||||||
|
|
||||||
|
function onmove_handle_left(dx, dy) {
|
||||||
|
const bar = self.$bar;
|
||||||
|
bar.finaldx = get_snap_position(dx);
|
||||||
|
update_bar_position(bar.ox + bar.finaldx, bar.owidth - bar.finaldx);
|
||||||
|
run_method_for_dependencies('onmove_handle_left', [dx, dy]);
|
||||||
|
}
|
||||||
|
self.onmove_handle_left = onmove_handle_left;
|
||||||
|
|
||||||
|
function onstop_handle_left() {
|
||||||
|
const bar = self.$bar;
|
||||||
|
if (bar.finaldx) date_changed();
|
||||||
|
set_action_completed();
|
||||||
|
run_method_for_dependencies('onstop_handle_left');
|
||||||
|
}
|
||||||
|
self.onstop_handle_left = onstop_handle_left;
|
||||||
|
|
||||||
|
function run_method_for_dependencies(fn, args) {
|
||||||
|
const dm = gt.dependency_map;
|
||||||
|
if (dm[self.task.id]) {
|
||||||
|
for (let deptask of dm[self.task.id]) {
|
||||||
|
const dt = gt.get_bar(deptask);
|
||||||
|
dt[fn].apply(dt, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmove_handle_right(dx, dy) {
|
||||||
|
const bar = self.$bar;
|
||||||
|
bar.finaldx = get_snap_position(dx);
|
||||||
|
update_bar_position(null, bar.owidth + bar.finaldx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onstop_handle_right() {
|
||||||
|
const bar = self.$bar;
|
||||||
|
if (bar.finaldx) date_changed();
|
||||||
|
set_action_completed();
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_bar_position(x, width) {
|
||||||
|
const bar = self.$bar;
|
||||||
|
if (x) update_attr(bar, 'x', x);
|
||||||
|
if (width) update_attr(bar, 'width', width);
|
||||||
|
update_label_position();
|
||||||
|
update_handle_position();
|
||||||
|
update_progressbar_position();
|
||||||
|
update_arrow_position();
|
||||||
|
update_details_position();
|
||||||
|
}
|
||||||
|
|
||||||
|
// function click(callback) {
|
||||||
|
// self.group.click(function () {
|
||||||
|
// if (self.action_completed) {
|
||||||
|
// // just finished a move action, wait for a few seconds
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (self.group.hasClass('active')) {
|
||||||
|
// callback(self.task);
|
||||||
|
// }
|
||||||
|
// unselect_all();
|
||||||
|
// self.group.toggleClass('active');
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
function date_changed() {
|
||||||
|
self.events.on_date_change &&
|
||||||
|
self.events.on_date_change(
|
||||||
|
self.task,
|
||||||
|
compute_start_date(),
|
||||||
|
compute_end_date()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function progress_changed() {
|
||||||
|
self.events.on_progress_change &&
|
||||||
|
self.events.on_progress_change(
|
||||||
|
self.task,
|
||||||
|
compute_progress()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_action_completed() {
|
||||||
|
self.action_completed = true;
|
||||||
|
setTimeout(() => self.action_completed = false, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function compute_date(x) {
|
||||||
|
// const shift = (x - compute_x()) / gt.config.column_width;
|
||||||
|
// const date = self.task._start.clone().add(gt.config.step * shift, 'hours');
|
||||||
|
// return date;
|
||||||
|
// }
|
||||||
|
|
||||||
|
function compute_start_date() {
|
||||||
|
const bar = self.$bar,
|
||||||
|
shift = (bar.getX() - compute_x()) / gt.config.column_width,
|
||||||
|
new_start_date = self.task._start.clone().add(gt.config.step * shift, 'hours');
|
||||||
|
return new_start_date;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compute_end_date() {
|
||||||
|
const bar = self.$bar,
|
||||||
|
og_x = compute_x() + self.duration * gt.config.column_width,
|
||||||
|
final_x = bar.getEndX(),
|
||||||
|
shift = (final_x - og_x) / gt.config.column_width,
|
||||||
|
new_end_date = self.task._end.clone().add(gt.config.step * shift, 'hours');
|
||||||
|
return new_end_date;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compute_progress() {
|
||||||
|
return self.$bar_progress.getWidth() / self.$bar.getWidth() * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compute_x() {
|
||||||
|
let x = self.task._start.diff(gt.gantt_start, 'hours') /
|
||||||
|
gt.config.step * gt.config.column_width;
|
||||||
|
|
||||||
|
if (gt.view_is('Month')) {
|
||||||
|
x = self.task._start.diff(gt.config.start, 'days') *
|
||||||
|
gt.config.column_width / 30;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compute_y() {
|
||||||
|
return gt.config.header_height + gt.config.padding +
|
||||||
|
self.task._index * (self.height + gt.config.padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_snap_position(dx) {
|
||||||
|
let odx = dx, rem, position;
|
||||||
|
|
||||||
|
if (gt.view_is('Week')) {
|
||||||
|
rem = dx % (gt.config.column_width / 7);
|
||||||
|
position = odx - rem +
|
||||||
|
((rem < gt.config.column_width / 14) ? 0 : gt.config.column_width / 7);
|
||||||
|
} else if (gt.view_is('Month')) {
|
||||||
|
rem = dx % (gt.config.column_width / 30);
|
||||||
|
position = odx - rem +
|
||||||
|
((rem < gt.config.column_width / 60) ? 0 : gt.config.column_width / 30);
|
||||||
|
} else {
|
||||||
|
rem = dx % gt.config.column_width;
|
||||||
|
position = odx - rem +
|
||||||
|
((rem < gt.config.column_width / 2) ? 0 : gt.config.column_width);
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_attr(element, attr, value) {
|
||||||
|
value = +value;
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
element.attr(attr, value);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_progressbar_position() {
|
||||||
|
self.$bar_progress.attr('x', self.$bar.getX());
|
||||||
|
self.$bar_progress.attr('width', self.$bar.getWidth() * (self.task.progress / 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_label_position() {
|
||||||
|
const bar = self.$bar,
|
||||||
|
label = self.group.select('.bar-label');
|
||||||
|
if (label.getBBox().width > bar.getWidth()) {
|
||||||
|
label.addClass('big').attr('x', bar.getX() + bar.getWidth() + 5);
|
||||||
|
} else {
|
||||||
|
label.removeClass('big').attr('x', bar.getX() + bar.getWidth() / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_handle_position() {
|
||||||
|
const bar = self.$bar;
|
||||||
|
self.handle_group.select('.handle.left').attr({
|
||||||
|
'x': bar.getX() + 1
|
||||||
|
});
|
||||||
|
self.handle_group.select('.handle.right').attr({
|
||||||
|
'x': bar.getEndX() - 9
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_arrow_position() {
|
||||||
|
for (let arrow of self.arrows) {
|
||||||
|
arrow.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_details_position() {
|
||||||
|
const details_box = gt.element_groups.details.select('.details-wrapper');
|
||||||
|
const pos = get_details_position();
|
||||||
|
details_box && details_box.transform(`t${pos.x},${pos.y}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function unselect_all() {
|
||||||
|
// gt.canvas.selectAll('.bar-wrapper').forEach(function (el) {
|
||||||
|
// el.removeClass('active');
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
516
src/Gantt.js
Normal file
516
src/Gantt.js
Normal file
@ -0,0 +1,516 @@
|
|||||||
|
/* global moment, Snap */
|
||||||
|
/**
|
||||||
|
* Gantt:
|
||||||
|
* element: querySelector string, required
|
||||||
|
* tasks: array of tasks, required
|
||||||
|
* task: { id, name, start, end, progress, dependencies }
|
||||||
|
* config: configuration options, optional
|
||||||
|
*/
|
||||||
|
import './gantt.scss';
|
||||||
|
|
||||||
|
import Bar from './Bar';
|
||||||
|
import Arrow from './Arrow';
|
||||||
|
|
||||||
|
export default function Gantt(element, tasks, config) {
|
||||||
|
|
||||||
|
const self = {};
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
set_defaults();
|
||||||
|
// initialize with default view mode
|
||||||
|
change_view_mode(self.config.view_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_defaults() {
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
header_height: 50,
|
||||||
|
column_width: 30,
|
||||||
|
step: 24,
|
||||||
|
view_modes: [
|
||||||
|
'Quarter Day',
|
||||||
|
'Half Day',
|
||||||
|
'Day',
|
||||||
|
'Week',
|
||||||
|
'Month'
|
||||||
|
],
|
||||||
|
bar: {
|
||||||
|
height: 20
|
||||||
|
},
|
||||||
|
arrow: {
|
||||||
|
curve: 5
|
||||||
|
},
|
||||||
|
padding: 18,
|
||||||
|
view_mode: 'Day',
|
||||||
|
date_format: 'YYYY-MM-DD'
|
||||||
|
};
|
||||||
|
|
||||||
|
self.element = element;
|
||||||
|
self._tasks = tasks;
|
||||||
|
self.config = Object.assign({}, defaults, config);
|
||||||
|
|
||||||
|
self._bars = [];
|
||||||
|
self._arrows = [];
|
||||||
|
self.element_groups = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function change_view_mode(mode) {
|
||||||
|
set_scale(mode);
|
||||||
|
prepare();
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare() {
|
||||||
|
prepare_tasks();
|
||||||
|
prepare_dependencies();
|
||||||
|
prepare_dates();
|
||||||
|
prepare_canvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare_tasks() {
|
||||||
|
|
||||||
|
// prepare tasks
|
||||||
|
self.tasks = self._tasks.map((task, i) => {
|
||||||
|
|
||||||
|
// momentify
|
||||||
|
task._start = moment(task.start, self.config.date_format);
|
||||||
|
task._end = moment(task.end, self.config.date_format);
|
||||||
|
|
||||||
|
// cache index
|
||||||
|
task._index = i;
|
||||||
|
|
||||||
|
// invalid dates
|
||||||
|
if(!task.start && !task.end) {
|
||||||
|
task._start = moment().startOf('day');
|
||||||
|
task._end = moment().startOf('day').add(2, 'days');
|
||||||
|
} else if(!task.start) {
|
||||||
|
task._start = task._end.clone().add(-2, 'days');
|
||||||
|
} else {
|
||||||
|
task._end = task._start.clone().add(2, 'days');
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid flag
|
||||||
|
if(!task.start || !task.end) {
|
||||||
|
task.invalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dependencies
|
||||||
|
let deps;
|
||||||
|
|
||||||
|
if(task.dependencies) {
|
||||||
|
deps = task.dependencies
|
||||||
|
.split(',')
|
||||||
|
.map(d => d.trim())
|
||||||
|
.filter((d) => d);
|
||||||
|
} else {
|
||||||
|
deps = [];
|
||||||
|
}
|
||||||
|
task.dependencies = deps;
|
||||||
|
|
||||||
|
return task;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function prepare_dependencies() {
|
||||||
|
|
||||||
|
self.dependency_map = {};
|
||||||
|
for(let t of self.tasks) {
|
||||||
|
for(let d of t.dependencies) {
|
||||||
|
self.dependency_map[d] = self.dependency_map[d] || [];
|
||||||
|
self.dependency_map[d].push(t.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare_dates() {
|
||||||
|
|
||||||
|
for(let task of self.tasks) {
|
||||||
|
// set global start and end date
|
||||||
|
if(!self.gantt_start || task._start < self.gantt_start) {
|
||||||
|
self.gantt_start = task._start;
|
||||||
|
}
|
||||||
|
if(!self.gantt_end || task._end > self.gantt_end) {
|
||||||
|
self.gantt_end = task._end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_gantt_dates();
|
||||||
|
setup_dates();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare_canvas() {
|
||||||
|
self.canvas = Snap(self.element).addClass('gantt');
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
clear();
|
||||||
|
setup_groups();
|
||||||
|
make_grid();
|
||||||
|
make_dates();
|
||||||
|
make_bars();
|
||||||
|
make_arrows();
|
||||||
|
map_arrows_on_bars();
|
||||||
|
setup_events();
|
||||||
|
set_width();
|
||||||
|
set_scroll_position();
|
||||||
|
bind_grid_click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
self.canvas.clear();
|
||||||
|
self._bars = [];
|
||||||
|
self._arrows = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_gantt_dates() {
|
||||||
|
|
||||||
|
if(view_is(['Quarter Day', 'Half Day'])) {
|
||||||
|
self.gantt_start = self.gantt_start.clone().subtract(7, 'day');
|
||||||
|
self.gantt_end = self.gantt_end.clone().add(7, 'day');
|
||||||
|
} else if(view_is('Month')) {
|
||||||
|
self.gantt_start = self.gantt_start.clone().startOf('year');
|
||||||
|
self.gantt_end = self.gantt_end.clone().endOf('month').add(1, 'year');
|
||||||
|
} else {
|
||||||
|
self.gantt_start = self.gantt_start.clone().startOf('month').subtract(1, 'month');
|
||||||
|
self.gantt_end = self.gantt_end.clone().endOf('month').add(1, 'month');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup_dates() {
|
||||||
|
|
||||||
|
self.dates = [];
|
||||||
|
let cur_date = null;
|
||||||
|
|
||||||
|
while(cur_date === null || cur_date < self.gantt_end) {
|
||||||
|
if(!cur_date) {
|
||||||
|
cur_date = self.gantt_start.clone();
|
||||||
|
} else {
|
||||||
|
cur_date = view_is('Month') ?
|
||||||
|
cur_date.clone().add(1, 'month') :
|
||||||
|
cur_date.clone().add(self.config.step, 'hours');
|
||||||
|
}
|
||||||
|
self.dates.push(cur_date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup_groups() {
|
||||||
|
|
||||||
|
const groups = ['grid', 'date', 'arrow', 'progress', 'bar', 'details'];
|
||||||
|
// make group layers
|
||||||
|
for(let group of groups) {
|
||||||
|
self.element_groups[group] = self.canvas.group().attr({'id': group});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_scale(scale) {
|
||||||
|
self.config.view_mode = scale;
|
||||||
|
|
||||||
|
// fire viewmode_change event
|
||||||
|
// self.events.on_viewmode_change(scale);
|
||||||
|
// trigger("view_mode_change");
|
||||||
|
|
||||||
|
if(scale === 'Day') {
|
||||||
|
self.config.step = 24;
|
||||||
|
self.config.column_width = 38;
|
||||||
|
} else if(scale === 'Half Day') {
|
||||||
|
self.config.step = 24 / 2;
|
||||||
|
self.config.column_width = 38;
|
||||||
|
} else if(scale === 'Quarter Day') {
|
||||||
|
self.config.step = 24 / 4;
|
||||||
|
self.config.column_width = 38;
|
||||||
|
} else if(scale === 'Week') {
|
||||||
|
self.config.step = 24 * 7;
|
||||||
|
self.config.column_width = 140;
|
||||||
|
} else if(scale === 'Month') {
|
||||||
|
self.config.step = 24 * 30;
|
||||||
|
self.config.column_width = 120;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_width() {
|
||||||
|
const cur_width = self.canvas.node.getBoundingClientRect().width;
|
||||||
|
const actual_width = self.canvas.getBBox().width;
|
||||||
|
if(cur_width < actual_width) {
|
||||||
|
self.canvas.attr('width', actual_width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_scroll_position() {
|
||||||
|
const parent_element = document.querySelector(self.element).parentElement;
|
||||||
|
if(!parent_element) return;
|
||||||
|
|
||||||
|
const scroll_pos = get_min_date().diff(self.gantt_start, 'hours') /
|
||||||
|
self.config.step * self.config.column_width;
|
||||||
|
parent_element.scrollLeft = scroll_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_min_date() {
|
||||||
|
const task = self.tasks.reduce((acc, curr) => {
|
||||||
|
return curr._start.isSameOrBefore(acc._start) ? curr : acc;
|
||||||
|
});
|
||||||
|
return task._start;
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_grid() {
|
||||||
|
make_grid_background();
|
||||||
|
make_grid_rows();
|
||||||
|
make_grid_header();
|
||||||
|
make_grid_ticks();
|
||||||
|
make_grid_highlights();
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_grid_background() {
|
||||||
|
|
||||||
|
const grid_width = self.dates.length * self.config.column_width,
|
||||||
|
grid_height = self.config.header_height + self.config.padding +
|
||||||
|
(self.config.bar.height + self.config.padding) * self.tasks.length;
|
||||||
|
|
||||||
|
self.canvas.rect(0, 0, grid_width, grid_height)
|
||||||
|
.addClass('grid-background')
|
||||||
|
.appendTo(self.element_groups.grid);
|
||||||
|
|
||||||
|
self.canvas.attr({
|
||||||
|
height: grid_height + self.config.padding,
|
||||||
|
width: '100%'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_grid_header() {
|
||||||
|
const header_width = self.dates.length * self.config.column_width,
|
||||||
|
header_height = self.config.header_height + 10;
|
||||||
|
self.canvas.rect(0, 0, header_width, header_height)
|
||||||
|
.addClass('grid-header')
|
||||||
|
.appendTo(self.element_groups.grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_grid_rows() {
|
||||||
|
|
||||||
|
const rows = self.canvas.group().appendTo(self.element_groups.grid),
|
||||||
|
lines = self.canvas.group().appendTo(self.element_groups.grid),
|
||||||
|
row_width = self.dates.length * self.config.column_width,
|
||||||
|
row_height = self.config.bar.height + self.config.padding;
|
||||||
|
|
||||||
|
let row_y = self.config.header_height + self.config.padding / 2;
|
||||||
|
|
||||||
|
for(let task of self.tasks) { // eslint-disable-line
|
||||||
|
self.canvas.rect(0, row_y, row_width, row_height)
|
||||||
|
.addClass('grid-row')
|
||||||
|
.appendTo(rows);
|
||||||
|
|
||||||
|
self.canvas.line(0, row_y + row_height, row_width, row_y + row_height)
|
||||||
|
.addClass('row-line')
|
||||||
|
.appendTo(lines);
|
||||||
|
|
||||||
|
row_y += self.config.bar.height + self.config.padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_grid_ticks() {
|
||||||
|
let tick_x = 0,
|
||||||
|
tick_y = self.config.header_height + self.config.padding / 2,
|
||||||
|
tick_height = (self.config.bar.height + self.config.padding) * self.tasks.length;
|
||||||
|
|
||||||
|
for(let date of self.dates) {
|
||||||
|
let tick_class = 'tick';
|
||||||
|
// thick tick for monday
|
||||||
|
if(view_is('Day') && date.day() === 1) {
|
||||||
|
tick_class += ' thick';
|
||||||
|
}
|
||||||
|
// thick tick for first week
|
||||||
|
if(view_is('Week') && date.date() >= 1 && date.date() < 8) {
|
||||||
|
tick_class += ' thick';
|
||||||
|
}
|
||||||
|
// thick ticks for quarters
|
||||||
|
if(view_is('Month') && date.month() % 3 === 0) {
|
||||||
|
tick_class += ' thick';
|
||||||
|
}
|
||||||
|
|
||||||
|
self.canvas.path(Snap.format('M {x} {y} v {height}', {
|
||||||
|
x: tick_x,
|
||||||
|
y: tick_y,
|
||||||
|
height: tick_height
|
||||||
|
}))
|
||||||
|
.addClass(tick_class)
|
||||||
|
.appendTo(self.element_groups.grid);
|
||||||
|
|
||||||
|
if(view_is('Month')) {
|
||||||
|
tick_x += date.daysInMonth() * self.config.column_width / 30;
|
||||||
|
} else {
|
||||||
|
tick_x += self.config.column_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_grid_highlights() {
|
||||||
|
|
||||||
|
// highlight today's date
|
||||||
|
if(view_is('Day')) {
|
||||||
|
const x = moment().startOf('day').diff(self.gantt_start, 'hours') /
|
||||||
|
self.config.step * self.config.column_width;
|
||||||
|
const y = 0;
|
||||||
|
const width = self.config.column_width;
|
||||||
|
const height = (self.config.bar.height + self.config.padding) * self.tasks.length +
|
||||||
|
self.config.header_height + self.config.padding / 2;
|
||||||
|
|
||||||
|
self.canvas.rect(x, y, width, height)
|
||||||
|
.addClass('today-highlight')
|
||||||
|
.appendTo(self.element_groups.grid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_dates() {
|
||||||
|
|
||||||
|
for(let date of get_dates_to_draw()) {
|
||||||
|
self.canvas.text(date.lower_x, date.lower_y, date.lower_text)
|
||||||
|
.addClass('lower-text')
|
||||||
|
.appendTo(self.element_groups.date);
|
||||||
|
|
||||||
|
if(date.upper_text) {
|
||||||
|
const $upper_text = self.canvas.text(date.upper_x, date.upper_y, date.upper_text)
|
||||||
|
.addClass('upper-text')
|
||||||
|
.appendTo(self.element_groups.date);
|
||||||
|
|
||||||
|
// remove out-of-bound dates
|
||||||
|
if($upper_text.getBBox().x2 > self.element_groups.grid.getBBox().width) {
|
||||||
|
$upper_text.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_dates_to_draw() {
|
||||||
|
let last_date = null;
|
||||||
|
const dates = self.dates.map((date, i) => {
|
||||||
|
const d = get_date_info(date, last_date, i);
|
||||||
|
last_date = date;
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
return dates;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_date_info(date, last_date, i) {
|
||||||
|
if(!last_date) {
|
||||||
|
last_date = date.clone().add(1, 'year');
|
||||||
|
}
|
||||||
|
const date_text = {
|
||||||
|
'Quarter Day_lower': date.format('HH'),
|
||||||
|
'Half Day_lower': date.format('HH'),
|
||||||
|
'Day_lower': date.date() !== last_date.date() ? date.format('D') : '',
|
||||||
|
'Week_lower': 'Week ' + date.format('W'),
|
||||||
|
'Month_lower': date.format('MMMM'),
|
||||||
|
'Quarter Day_upper': date.date() !== last_date.date() ? date.format('D MMM') : '',
|
||||||
|
'Half Day_upper': date.date() !== last_date.date() ? date.format('D MMM') : '',
|
||||||
|
'Day_upper': date.month() !== last_date.month() ? date.format('MMMM') : '',
|
||||||
|
'Week_upper': date.month() !== last_date.month() ? date.format('MMMM') : '',
|
||||||
|
'Month_upper': date.year() !== last_date.year() ? date.format('YYYY') : ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const base_pos = {
|
||||||
|
x: i * self.config.column_width,
|
||||||
|
lower_y: self.config.header_height,
|
||||||
|
upper_y: self.config.header_height - 25
|
||||||
|
};
|
||||||
|
|
||||||
|
const x_pos = {
|
||||||
|
'Quarter Day_lower': (self.config.column_width * 4) / 2,
|
||||||
|
'Quarter Day_upper': 0,
|
||||||
|
'Half Day_lower': (self.config.column_width * 2) / 2,
|
||||||
|
'Half Day_upper': 0,
|
||||||
|
'Day_lower': self.config.column_width / 2,
|
||||||
|
'Day_upper': (self.config.column_width * 30) / 2,
|
||||||
|
'Week_lower': self.config.column_width / 2,
|
||||||
|
'Week_upper': (self.config.column_width * 4) / 2,
|
||||||
|
'Month_lower': (date.daysInMonth() * self.config.column_width / 30) / 2,
|
||||||
|
'Month_upper': (self.config.column_width * 12) / 2
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
upper_text: date_text[`${self.config.view_mode}_upper`],
|
||||||
|
lower_text: date_text[`${self.config.view_mode}_lower`],
|
||||||
|
upper_x: base_pos.x + x_pos[`${self.config.view_mode}_upper`],
|
||||||
|
upper_y: base_pos.upper_y,
|
||||||
|
lower_x: base_pos.x + x_pos[`${self.config.view_mode}_lower`],
|
||||||
|
lower_y: base_pos.lower_y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_arrows() {
|
||||||
|
|
||||||
|
for(let task of self.tasks) {
|
||||||
|
self._arrows = task.dependencies.map(dep => {
|
||||||
|
const dependency = get_task(dep);
|
||||||
|
if(!dependency) return;
|
||||||
|
|
||||||
|
const arrow = Arrow(
|
||||||
|
self, // gt
|
||||||
|
self._bars[dependency._index], // from_task
|
||||||
|
self._bars[task._index] // to_task
|
||||||
|
);
|
||||||
|
self.element_groups.arrow.add(arrow.element);
|
||||||
|
return arrow; // eslint-disable-line
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_bars() {
|
||||||
|
|
||||||
|
self._bars = self.tasks.map((task) => {
|
||||||
|
const bar = Bar(self, task);
|
||||||
|
self.element_groups.bar.add(bar.group);
|
||||||
|
return bar;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function map_arrows_on_bars() {
|
||||||
|
|
||||||
|
for(let bar of self._bars) {
|
||||||
|
bar.arrows = self._arrows.filter(arrow => {
|
||||||
|
return arrow.from_task.task.id === bar.task.id ||
|
||||||
|
arrow.to_task.task.id === bar.task.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup_events() {
|
||||||
|
// this._bars.forEach(function(bar) {
|
||||||
|
// bar.events.on_date_change = me.events.bar_on_date_change;
|
||||||
|
// bar.events.on_progress_change = me.events.bar_on_progress_change;
|
||||||
|
// bar.click(me.events.bar_on_click);
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind_grid_click() {
|
||||||
|
self.element_groups.grid.click(() => {
|
||||||
|
self.canvas.selectAll('.bar-wrapper').forEach(el => {
|
||||||
|
el.removeClass('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function view_is(modes) {
|
||||||
|
if (typeof modes === 'string') {
|
||||||
|
return self.config.view_mode === modes;
|
||||||
|
} else if(Array.isArray(modes)) {
|
||||||
|
for (let mode of modes) {
|
||||||
|
if(self.config.view_mode === mode) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.view_is = view_is;
|
||||||
|
|
||||||
|
function get_task(id) {
|
||||||
|
self.tasks.find((task) => {
|
||||||
|
return task.id === id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_bar(id) {
|
||||||
|
self._bars.find((bar) => {
|
||||||
|
return bar.task.id === id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.get_bar = get_bar; // required in Bar
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
144
src/gantt.scss
Normal file
144
src/gantt.scss
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
$bar-color: #b8c2cc;
|
||||||
|
$bar-stroke: #8D99A6;
|
||||||
|
$border-color: #e0e0e0;
|
||||||
|
$light-bg: #f5f5f5;
|
||||||
|
$light-border-color: #ebeff2;
|
||||||
|
$light-yellow: #fcf8e3;
|
||||||
|
$text-muted: #666;
|
||||||
|
$text-light: #555;
|
||||||
|
$text-color: #333;
|
||||||
|
$blue: #a3a3ff;
|
||||||
|
$handle-color: #ddd;
|
||||||
|
|
||||||
|
.gantt {
|
||||||
|
#grid {
|
||||||
|
.grid-background {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
.grid-header {
|
||||||
|
fill: #ffffff;
|
||||||
|
stroke: $border-color;
|
||||||
|
stroke-width: 1.4;
|
||||||
|
}
|
||||||
|
.grid-row {
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
.grid-row:nth-child(even) {
|
||||||
|
fill: $light-bg;
|
||||||
|
}
|
||||||
|
.row-line {
|
||||||
|
stroke: $light-border-color;
|
||||||
|
}
|
||||||
|
.tick {
|
||||||
|
stroke: $border-color;
|
||||||
|
stroke-width: 0.2;
|
||||||
|
&.thick {
|
||||||
|
stroke-width: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.today-highlight {
|
||||||
|
fill: $light-yellow;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#arrow {
|
||||||
|
fill: none;
|
||||||
|
stroke: $text-muted;
|
||||||
|
stroke-width: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
fill: $bar-color;
|
||||||
|
stroke: $bar-stroke;
|
||||||
|
stroke-width: 0;
|
||||||
|
transition: stroke-width .3s ease;
|
||||||
|
}
|
||||||
|
.bar-progress {
|
||||||
|
fill: $blue;
|
||||||
|
}
|
||||||
|
.bar-invalid {
|
||||||
|
fill: transparent;
|
||||||
|
stroke: $bar-stroke;
|
||||||
|
stroke-width: 1;
|
||||||
|
stroke-dasharray: 5;
|
||||||
|
|
||||||
|
&~.bar-label {
|
||||||
|
fill: $text-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bar-label {
|
||||||
|
fill: #fff;
|
||||||
|
dominant-baseline: central;
|
||||||
|
text-anchor: middle;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: lighter;
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
|
||||||
|
&.big {
|
||||||
|
fill: $text-light;
|
||||||
|
text-anchor: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle {
|
||||||
|
fill: $handle-color;
|
||||||
|
cursor: ew-resize;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity .3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-wrapper {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.bar {
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
.bar {
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-text, .secondary-text {
|
||||||
|
font-size: 12px;
|
||||||
|
text-anchor: middle;
|
||||||
|
}
|
||||||
|
.primary-text {
|
||||||
|
fill: $text-light;
|
||||||
|
}
|
||||||
|
.secondary-text {
|
||||||
|
fill: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
#details {
|
||||||
|
font-size: 14;
|
||||||
|
|
||||||
|
.details-container {
|
||||||
|
stroke: $border-color;
|
||||||
|
stroke-width: 1.1;
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
.details-heading {
|
||||||
|
fill: $text-color;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.details-body {
|
||||||
|
fill: $text-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
test/library.spec.js
Executable file
19
test/library.spec.js
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
import chai from 'chai';
|
||||||
|
import Library from '../lib/library.js';
|
||||||
|
|
||||||
|
chai.expect();
|
||||||
|
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
let lib;
|
||||||
|
|
||||||
|
describe('Given an instance of my library', function () {
|
||||||
|
before(function () {
|
||||||
|
lib = new Library();
|
||||||
|
});
|
||||||
|
describe('when I need the name', function () {
|
||||||
|
it('should return the name', () => {
|
||||||
|
expect(lib.name).to.be.equal('Library');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
52
webpack.config.js
Executable file
52
webpack.config.js
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
var webpack = require('webpack');
|
||||||
|
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
|
||||||
|
var path = require('path');
|
||||||
|
var env = require('yargs').argv.mode;
|
||||||
|
|
||||||
|
var libraryName = 'gantt';
|
||||||
|
|
||||||
|
var plugins = [], outputFile;
|
||||||
|
|
||||||
|
if (env === 'build') {
|
||||||
|
plugins.push(new UglifyJsPlugin({ minimize: true }));
|
||||||
|
outputFile = libraryName + '.min.js';
|
||||||
|
} else {
|
||||||
|
outputFile = libraryName + '.js';
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = {
|
||||||
|
entry: __dirname + '/src/Gantt.js',
|
||||||
|
devtool: 'source-map',
|
||||||
|
output: {
|
||||||
|
path: __dirname + '/lib',
|
||||||
|
filename: outputFile,
|
||||||
|
library: libraryName,
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
umdNamedDefine: true
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /(\.jsx|\.js)$/,
|
||||||
|
loader: 'babel',
|
||||||
|
exclude: /(node_modules|bower_components)/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /(\.jsx|\.js)$/,
|
||||||
|
loader: 'eslint-loader',
|
||||||
|
exclude: /node_modules/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
loaders: [ 'style', 'css?sourceMap', 'sass?sourceMap' ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
root: path.resolve('./src'),
|
||||||
|
extensions: ['', '.js']
|
||||||
|
},
|
||||||
|
plugins: plugins
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
Loading…
x
Reference in New Issue
Block a user