Merge pull request #394 from safwansamsudeen/master

Tooling and Bug Fixes
This commit is contained in:
Safwan Samsudeen 2024-09-11 20:19:52 +05:30 committed by GitHub
commit 22ff6e202e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 17538 additions and 9054 deletions

View File

@ -1,3 +0,0 @@
{
"presets": ["env"]
}

1
.gitignore vendored
View File

@ -25,6 +25,7 @@ build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
.yarn
.DS_Store

1
.yarnrc.yml Normal file
View File

@ -0,0 +1 @@
nodeLinker: node-modules

View File

@ -51,18 +51,18 @@ var gantt = new Gantt("#gantt", tasks);
You can also pass various options to the Gantt constructor:
```js
var gantt = new Gantt("#gantt", tasks, {
var gantt = new Gantt('#gantt', tasks, {
header_height: 50,
column_width: 30,
step: 24,
view_modes: ["Quarter Day", "Half Day", "Day", "Week", "Month"],
view_modes: ['Quarter Day', 'Half Day', 'Day', 'Week', 'Month'],
bar_height: 20,
bar_corner_radius: 3,
arrow_curve: 5,
padding: 18,
view_mode: "Day",
date_format: "YYYY-MM-DD",
language: "en", // or 'es', 'it', 'ru', 'ptBr', 'fr', 'tr', 'zh', 'de', 'hu'
view_mode: 'Day',
date_format: 'YYYY-MM-DD',
language: 'en', // or 'es', 'it', 'ru', 'ptBr', 'fr', 'tr', 'zh', 'de', 'hu'
custom_popup_html: null,
});
```

295
dist/frappe-gantt.css vendored
View File

@ -1,295 +0,0 @@
.dark > .gantt-container .gantt .grid-header {
fill: #252525;
stroke: #616161;
}
.dark > .gantt-container .gantt .grid-row {
fill: #252525;
}
.dark > .gantt-container .gantt .grid-row:nth-child(even) {
fill: #3e3e3e;
}
.dark > .gantt-container .gantt .row-line {
stroke: #3e3e3e;
}
.dark > .gantt-container .gantt .tick {
stroke: #616161;
}
.dark > .gantt-container .gantt .today-highlight {
opacity: 0.2;
}
.dark > .gantt-container .gantt .arrow {
stroke: #eee;
}
.dark > .gantt-container .gantt .bar {
fill: #616161;
stroke: none;
}
.dark > .gantt-container .gantt .bar-progress {
fill: #8a8aff;
}
.dark > .gantt-container .gantt .bar-invalid {
fill: transparent;
stroke: #c6ccd2;
}
.dark > .gantt-container .gantt .bar-invalid ~ .bar-label {
fill: #ececec;
}
.dark > .gantt-container .gantt .bar-label.big {
fill: #ececec;
}
.dark > .gantt-container .gantt .bar-wrapper:hover .bar {
fill: #6e6e6e;
}
.dark > .gantt-container .gantt .bar-wrapper:hover .bar-progress {
fill: #a4a4ff;
}
.dark > .gantt-container .gantt .bar-wrapper.active .bar {
fill: #6e6e6e;
}
.dark > .gantt-container .gantt .bar-wrapper.active .bar-progress {
fill: #a4a4ff;
}
.dark > .gantt-container .gantt .upper-text {
fill: #a2a2a2;
}
.dark > .gantt-container .gantt .lower-text {
fill: #f7f7f7;
}
.dark > .gantt-container .popup-wrapper {
background-color: #333;
}
.dark > .gantt-container .popup-wrapper .title {
border-color: #a4a4ff;
}
.dark > .gantt-container .popup-wrapper .pointer {
border-top-color: #333;
}
.gantt-container {
line-height: 14.5px;
}
.gantt-container .grid-header {
background-color: #ffffff;
position: sticky;
top: 0;
left: 0;
z-index: 10;
}
.gantt-container .lower-text,
.gantt-container .upper-text {
text-anchor: middle;
color: #111;
}
.gantt-container .upper-header {
height: 40px;
}
.gantt-container .lower-header {
height: 30px;
}
.gantt-container .lower-text {
font-size: 14px;
position: absolute;
width: fit-content;
}
.gantt-container .upper-text {
position: absolute;
width: fit-content;
font-weight: 500;
font-size: 16px;
}
.gantt-container .current-upper {
position: fixed;
}
.gantt-container .side-header {
position: fixed;
padding: 0 10px;
margin-right: 10px;
background: white;
line-height: 20px;
font-weight: 400;
}
.gantt-container .today-button,
.gantt-container .viewmode-select {
background: #F4F5F6;
text-align: -webkit-center;
text-align: center;
height: 25px;
border-radius: 8px;
border: none;
color: #111;
padding: 4px 10px;
border-radius: 8px;
height: 25px;
}
.gantt-container .viewmode-select {
outline: none !important;
padding: 4px 8px;
margin-right: 4px;
-webkit-appearance: none;
-moz-appearance: none;
text-indent: 1px;
text-overflow: "";
}
.gantt-container .date-highlight {
background-color: #EBEEF0;
border-radius: 12px;
position: absolute;
display: none;
}
.gantt-container .current-highlight {
position: absolute;
background: #2c94ec;
width: 1px;
}
.gantt-container .current-date-highlight {
background: #2c94ec;
color: #fff;
padding: 4px 8px;
border-radius: 200px;
}
.gantt {
user-select: none;
-webkit-user-select: none;
position: absolute;
}
.gantt .grid-background {
fill: none;
}
.gantt .grid-row {
fill: #ffffff;
}
.gantt .row-line {
stroke: #ebeff2;
}
.gantt .tick {
stroke: #EBEEF0;
stroke-width: 0.4;
}
.gantt .tick.thick {
stroke: #e0e0e0;
stroke-width: 0.7;
}
.gantt .holiday-highlight {
fill: #F9FAFA;
}
.gantt .arrow {
fill: none;
stroke: #9FA9B1;
stroke-width: 1;
}
.gantt .bar-wrapper .bar {
fill: #fff;
stroke: #fff;
stroke-width: 0;
transition: stroke-width 0.3s ease;
}
.gantt .bar-progress {
fill: #EBEEF0;
}
.gantt .bar-expected-progress {
fill: #c4c4e9;
}
.gantt .bar-invalid {
fill: transparent;
stroke: #fff;
stroke-width: 1;
stroke-dasharray: 5;
}
.gantt .bar-invalid ~ .bar-label {
fill: #fff;
}
.gantt .bar-label {
fill: #111;
dominant-baseline: central;
font-family: Helvetica;
font-size: 13px;
font-weight: 400;
}
.gantt .bar-label.big {
fill: #111;
text-anchor: start;
}
.gantt .bar-wrapper.important .bar {
fill: #94c4f4;
}
.gantt .bar-wrapper.important .bar-progress {
fill: #2c94ec;
}
.gantt .bar-wrapper.important .bar-label {
fill: #fff;
}
.gantt .bar-wrapper.important .handle {
fill: #94c4f4;
}
.gantt .bar-wrapper.important .handle.progress {
fill: #fff;
}
.gantt .handle {
fill: #dcdce4;
cursor: ew-resize;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease;
}
.gantt .handle.progress {
fill: #666;
}
.gantt .bar-wrapper {
cursor: pointer;
outline: none;
}
.gantt .bar-wrapper.active .handle {
visibility: visible;
opacity: 1;
}
.gantt .bar-wrapper .bar {
-webkit-filter: drop-shadow(3px 3px 2px rgba(0, 0, 0, 0.7));
filter: drop-shadow(0 0 2px rgba(17, 43, 66, 0.16));
border-radius: 3px;
}
.gantt .bar-wrapper:hover .bar {
transition: transform 0.3s ease;
}
.gantt .bar-wrapper:hover .date-highlight {
display: block;
}
.gantt .hide {
display: none;
}
.gantt-container {
position: relative;
overflow: auto;
font-size: 12px;
height: 500px;
}
.gantt-container .popup-wrapper {
position: absolute;
top: 0;
left: 0;
background: #171B1F;
padding: 10px;
border-radius: 5px;
width: max-content;
}
.gantt-container .popup-wrapper.hidden {
opacity: 0 !important;
}
.gantt-container .popup-wrapper .title {
margin-bottom: 5px;
text-align: -webkit-center;
text-align: center;
color: #fff;
}
.gantt-container .popup-wrapper .subtitle {
color: #98A1A9;
}
.gantt-container .popup-wrapper .pointer {
position: absolute;
height: 5px;
margin: 0 0 0 -5px;
border: 5px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.8);
}

2824
dist/frappe-gantt.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.dark>.gantt-container .gantt .grid-header{fill:#252525;stroke:#616161}.dark>.gantt-container .gantt .grid-row{fill:#252525}.dark>.gantt-container .gantt .grid-row:nth-child(even){fill:#3e3e3e}.dark>.gantt-container .gantt .row-line{stroke:#3e3e3e}.dark>.gantt-container .gantt .tick{stroke:#616161}.dark>.gantt-container .gantt .today-highlight{opacity:.2}.dark>.gantt-container .gantt .arrow{stroke:#eee}.dark>.gantt-container .gantt .bar{fill:#616161;stroke:none}.dark>.gantt-container .gantt .bar-progress{fill:#8a8aff}.dark>.gantt-container .gantt .bar-invalid{fill:rgba(0,0,0,0);stroke:#c6ccd2}.dark>.gantt-container .gantt .bar-invalid~.bar-label{fill:#ececec}.dark>.gantt-container .gantt .bar-label.big{fill:#ececec}.dark>.gantt-container .gantt .bar-wrapper:hover .bar{fill:#6e6e6e}.dark>.gantt-container .gantt .bar-wrapper:hover .bar-progress{fill:#a4a4ff}.dark>.gantt-container .gantt .bar-wrapper.active .bar{fill:#6e6e6e}.dark>.gantt-container .gantt .bar-wrapper.active .bar-progress{fill:#a4a4ff}.dark>.gantt-container .gantt .upper-text{fill:#a2a2a2}.dark>.gantt-container .gantt .lower-text{fill:#f7f7f7}.dark>.gantt-container .popup-wrapper{background-color:#333}.dark>.gantt-container .popup-wrapper .title{border-color:#a4a4ff}.dark>.gantt-container .popup-wrapper .pointer{border-top-color:#333}.gantt-container{line-height:14.5px}.gantt-container .grid-header{background-color:#fff;position:sticky;top:0;left:0;z-index:10}.gantt-container .lower-text,.gantt-container .upper-text{text-anchor:middle;color:#111}.gantt-container .upper-header{height:40px}.gantt-container .lower-header{height:30px}.gantt-container .lower-text{font-size:14px;position:absolute;width:fit-content}.gantt-container .upper-text{position:absolute;width:fit-content;font-weight:500;font-size:16px}.gantt-container .current-upper{position:fixed}.gantt-container .side-header{position:fixed;padding:0 10px;margin-right:10px;background:#fff;line-height:20px;font-weight:400}.gantt-container .today-button,.gantt-container .viewmode-select{background:#f4f5f6;text-align:-webkit-center;text-align:center;height:25px;border-radius:8px;border:none;color:#111;padding:4px 10px;border-radius:8px;height:25px}.gantt-container .viewmode-select{outline:none !important;padding:4px 8px;margin-right:4px;-webkit-appearance:none;-moz-appearance:none;text-indent:1px;text-overflow:""}.gantt-container .date-highlight{background-color:#ebeef0;border-radius:12px;position:absolute;display:none}.gantt-container .current-highlight{position:absolute;background:#2c94ec;width:1px}.gantt-container .current-date-highlight{background:#2c94ec;color:#fff;padding:4px 8px;border-radius:200px}.gantt{user-select:none;-webkit-user-select:none;position:absolute}.gantt .grid-background{fill:none}.gantt .grid-row{fill:#fff}.gantt .row-line{stroke:#ebeff2}.gantt .tick{stroke:#ebeef0;stroke-width:.4}.gantt .tick.thick{stroke:#e0e0e0;stroke-width:.7}.gantt .holiday-highlight{fill:#f9fafa}.gantt .arrow{fill:none;stroke:#9fa9b1;stroke-width:1}.gantt .bar-wrapper .bar{fill:#fff;stroke:#fff;stroke-width:0;transition:stroke-width .3s ease}.gantt .bar-progress{fill:#ebeef0}.gantt .bar-expected-progress{fill:#c4c4e9}.gantt .bar-invalid{fill:rgba(0,0,0,0);stroke:#fff;stroke-width:1;stroke-dasharray:5}.gantt .bar-invalid~.bar-label{fill:#fff}.gantt .bar-label{fill:#111;dominant-baseline:central;font-family:Helvetica;font-size:13px;font-weight:400}.gantt .bar-label.big{fill:#111;text-anchor:start}.gantt .bar-wrapper.important .bar{fill:#94c4f4}.gantt .bar-wrapper.important .bar-progress{fill:#2c94ec}.gantt .bar-wrapper.important .bar-label{fill:#fff}.gantt .bar-wrapper.important .handle{fill:#94c4f4}.gantt .bar-wrapper.important .handle.progress{fill:#fff}.gantt .handle{fill:#dcdce4;cursor:ew-resize;opacity:0;visibility:hidden;transition:opacity .3s ease}.gantt .handle.progress{fill:#666}.gantt .bar-wrapper{cursor:pointer;outline:none}.gantt .bar-wrapper.active .handle{visibility:visible;opacity:1}.gantt .bar-wrapper .bar{-webkit-filter:drop-shadow(3px 3px 2px rgba(0, 0, 0, 0.7));filter:drop-shadow(0 0 2px rgba(17, 43, 66, 0.16));border-radius:3px}.gantt .bar-wrapper:hover .bar{transition:transform .3s ease}.gantt .bar-wrapper:hover .date-highlight{display:block}.gantt .hide{display:none}.gantt-container{position:relative;overflow:auto;font-size:12px;height:500px}.gantt-container .popup-wrapper{position:absolute;top:0;left:0;background:#171b1f;padding:10px;border-radius:5px;width:max-content}.gantt-container .popup-wrapper.hidden{opacity:0 !important}.gantt-container .popup-wrapper .title{margin-bottom:5px;text-align:-webkit-center;text-align:center;color:#fff}.gantt-container .popup-wrapper .subtitle{color:#98a1a9}.gantt-container .popup-wrapper .pointer{position:absolute;height:5px;margin:0 0 0 -5px;border:5px solid rgba(0,0,0,0);border-bottom-color:rgba(0,0,0,.8)}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

23
dist/frappe-gantt.umd.cjs vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/style.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -23,53 +23,57 @@
background-color: #252525;
}
</style>
<link rel="stylesheet" href="dist/frappe-gantt.css" />
<script src="dist/frappe-gantt.js"></script>
<link rel="stylesheet" href="dist/style.css" />
<script src="dist/frappe-gantt.umd.cjs"></script>
</head>
<body>
<div class="container">
<h2 class="heading">Interactive Gantt Chart entirely made in SVG!</h2>
<h2 class="heading">
Interactive Gantt Chart entirely made in SVG!
</h2>
<div class="gantt-target"></div>
</div>
<script>
<script type="module">
// import Gantt from "./dist/frappe-gantt.js";
let tasks = [
{
start: '2024-04-01',
end: '2024-04-01',
name: 'Redesign website',
id: "Task 0",
progress: 30
id: 'Task 0',
progress: 30,
},
{
start: '2024-03-26',
// Utilizes duration
duration: '6d',
name: 'Write new content',
id: "Task 1",
id: 'Task 1',
progress: 5,
important: true
important: true,
},
{
start: '2024-04-04',
end: '2024-04-08',
name: 'Apply new styles',
id: "Task 2",
id: 'Task 2',
progress: 80,
dependencies: 'Task 1'
dependencies: 'Task 1',
},
{
start: '2024-04-08',
end: '2024-04-09',
name: 'Review',
id: "Task 3",
id: 'Task 3',
progress: 5,
dependencies: 'Task 2'
dependencies: 'Task 2',
},
{
start: '2024-04-08',
end: '2024-04-10',
name: 'Deploy',
id: "Task 4",
id: 'Task 4',
progress: 0,
// dependencies: 'Task 2'
},
@ -77,10 +81,10 @@
start: '2024-04-21',
end: '2024-04-29',
name: 'Go Live!',
id: "Task 5",
id: 'Task 5',
progress: 0,
dependencies: 'Task 2',
custom_class: 'bar-milestone'
custom_class: 'bar-milestone',
},
// {
// start: '2014-01-05',
@ -92,11 +96,17 @@
];
// Uncomment to test fixed header
tasks = [...tasks, ...Array.from({length: tasks.length * 3}, (_, i) => ({...tasks[i % 3], id: i}))]
// tasks = [
// ...tasks,
// ...Array.from({ length: tasks.length * 3 }, (_, i) => ({
// ...tasks[i % 3],
// id: i,
// })),
// ];
let gantt_chart = new Gantt(".gantt-target", tasks, {
let gantt_chart = new Gantt('.gantt-target', tasks, {
on_click: (task) => {
console.log("Click", task);
console.log('Click', task);
},
// on_double_click: (task) => {
// console.log("Double Click", task);
@ -113,18 +123,18 @@
// on_hover: (task, x, y) => {
// console.log("Hover", x, y);
// },
view_mode: "Day",
view_mode_padding: { DAY: "3d" },
popup: false,
view_mode: 'Day',
view_mode_padding: { DAY: '3d' },
// popup: false,
// scroll_to: 'today',
// view_mode_select: true,
// dates_readonly: true,
// today_button: false,
// readonly: true,
// lines: 'vertical',
// lower_text: (date) => date.getDay(),
// upper_text: (date, view_mode, def) => def,
});
console.log(gantt_chart);
</script>
</body>
</html>

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016 Frappe Technologies Pvt. Ltd.
Copyright (c) 2024 Frappe Technologies Pvt. Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

6265
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,16 @@
{
"name": "frappe-gantt",
"version": "0.6.1",
"version": "0.8",
"description": "A simple, modern, interactive gantt library for the web",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "yarn run dev",
"build": "rollup -c",
"dev": "rollup -c -w",
"dev": "vite",
"build-dev": "vite build --watch",
"build": "vite build",
"test": "jest",
"lint": "eslint src/**/*.js",
"test:watch": "jest --watch",
"prettier": "prettier --write \"{src/*,tests/*,rollup.config}.js\"",
"prettier-check": "prettier --check \"{src/*,tests/*,rollup.config}.js\""
@ -21,6 +24,12 @@
"dist",
"README.md"
],
"exports": {
".": {
"import": "./dist/frappe-gantt.es.js",
"require": "./dist/frappe-gantt.umd.js"
}
},
"keywords": [
"gantt",
"svg",
@ -36,17 +45,16 @@
},
"homepage": "https://github.com/frappe/gantt",
"devDependencies": {
"babel-preset-env": "^1.6.1",
"eslint": "^4.17.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-prettier": "^2.6.0",
"jest": "^22.2.1",
"postcss-nesting": "^12.1.2",
"prettier": "3.2.5",
"rollup": "^2.70.2",
"rollup-plugin-sass": "^1.2.12",
"rollup-plugin-terser": "^7.0.2"
"vite": "^5.2.10"
},
"eslintIgnore": [
"dist"
]
],
"packageManager": "yarn@3.2.3"
}

4
postcss.config.cjs Normal file
View File

@ -0,0 +1,4 @@
/* eslint-disable */
module.exports = {
plugins: [require('postcss-nesting')],
};

View File

@ -1,37 +0,0 @@
import sass from "rollup-plugin-sass";
import { terser } from "rollup-plugin-terser";
const dev = {
input: "src/index.js",
output: {
name: "Gantt",
file: "dist/frappe-gantt.js",
sourcemap: true,
format: "iife",
},
plugins: [
sass({
output: true,
}),
],
};
const prod = {
input: "src/index.js",
output: {
name: "Gantt",
file: "dist/frappe-gantt.min.js",
sourcemap: true,
format: "iife",
},
plugins: [
sass({
output: true,
options: {
outputStyle: "compressed",
},
}),
terser(),
],
};
export default [dev, prod];

View File

@ -1,4 +1,4 @@
import { createSVG } from "./svg_utils";
import { createSVG } from './svg_utils';
export default class Arrow {
constructor(gantt, from_task, to_task) {
@ -29,7 +29,8 @@ export default class Arrow {
this.from_task.task._index +
this.gantt.options.padding;
const end_x = this.to_task.$bar.getX() - this.gantt.options.padding / 2 - 7;
const end_x =
this.to_task.$bar.getX() - this.gantt.options.padding / 2 - 7;
const end_y =
this.gantt.options.header_height +
this.gantt.options.bar_height / 2 +
@ -61,7 +62,9 @@ export default class Arrow {
) {
const down_1 = this.gantt.options.padding / 2 - curve;
const down_2 =
this.to_task.$bar.getY() + this.to_task.$bar.getHeight() / 2 - curve_y;
this.to_task.$bar.getY() +
this.to_task.$bar.getHeight() / 2 -
curve_y;
const left = this.to_task.$bar.getX() - this.gantt.options.padding;
this.path = `
@ -80,15 +83,15 @@ export default class Arrow {
}
draw() {
this.element = createSVG("path", {
this.element = createSVG('path', {
d: this.path,
"data-from": this.from_task.task.id,
"data-to": this.to_task.task.id,
'data-from': this.from_task.task.id,
'data-to': this.to_task.task.id,
});
}
update() {
this.calculate_path();
this.element.setAttribute("d", this.path);
this.element.setAttribute('d', this.path);
}
}

View File

@ -1,5 +1,5 @@
import date_utils from "./date_utils";
import { $, createSVG, animateSVG } from "./svg_utils";
import date_utils from './date_utils';
import { $, createSVG, animateSVG } from './svg_utils';
export default class Bar {
constructor(gantt, task) {
@ -33,32 +33,35 @@ export default class Bar {
this.gantt.options.column_width *
this.duration *
(this.task.progress / 100) || 0;
this.group = createSVG("g", {
class: "bar-wrapper" + (this.task.custom_class ? " " + this.task.custom_class : "") + (this.task.important ? ' important' : ''),
"data-id": this.task.id,
this.group = createSVG('g', {
class:
'bar-wrapper' +
(this.task.custom_class ? ' ' + this.task.custom_class : '') +
(this.task.important ? ' important' : ''),
'data-id': this.task.id,
});
this.bar_group = createSVG("g", {
class: "bar-group",
this.bar_group = createSVG('g', {
class: 'bar-group',
append_to: this.group,
});
this.handle_group = createSVG("g", {
class: "handle-group",
this.handle_group = createSVG('g', {
class: 'handle-group',
append_to: this.group,
});
}
prepare_helpers() {
SVGElement.prototype.getX = function () {
return +this.getAttribute("x");
return +this.getAttribute('x');
};
SVGElement.prototype.getY = function () {
return +this.getAttribute("y");
return +this.getAttribute('y');
};
SVGElement.prototype.getWidth = function () {
return +this.getAttribute("width");
return +this.getAttribute('width');
};
SVGElement.prototype.getHeight = function () {
return +this.getAttribute("height");
return +this.getAttribute('height');
};
SVGElement.prototype.getEndX = function () {
return this.getX() + this.getWidth();
@ -89,40 +92,45 @@ export default class Bar {
}
draw_bar() {
this.$bar = createSVG("rect", {
this.$bar = createSVG('rect', {
x: this.x,
y: this.y,
width: this.width,
height: this.height,
rx: this.corner_radius,
ry: this.corner_radius,
class: "bar",
class:
'bar' +
(/^((?!chrome|android).)*safari/i.test(navigator.userAgent) &&
!this.task.important
? ' safari'
: ''),
append_to: this.bar_group,
});
animateSVG(this.$bar, "width", 0, this.width);
animateSVG(this.$bar, 'width', 0, this.width);
if (this.invalid) {
this.$bar.classList.add("bar-invalid");
this.$bar.classList.add('bar-invalid');
}
}
draw_expected_progress_bar() {
if (this.invalid) return;
this.$expected_bar_progress = createSVG("rect", {
this.$expected_bar_progress = createSVG('rect', {
x: this.x,
y: this.y,
width: this.expected_progress_width,
height: this.height,
rx: this.corner_radius,
ry: this.corner_radius,
class: "bar-expected-progress",
class: 'bar-expected-progress',
append_to: this.bar_group,
});
animateSVG(
this.$expected_bar_progress,
"width",
'width',
0,
this.expected_progress_width,
);
@ -130,33 +138,33 @@ export default class Bar {
draw_progress_bar() {
if (this.invalid) return;
this.$bar_progress = createSVG("rect", {
this.$bar_progress = createSVG('rect', {
x: this.x,
y: this.y,
width: this.progress_width,
height: this.height,
rx: this.corner_radius,
ry: this.corner_radius,
class: "bar-progress",
class: 'bar-progress',
append_to: this.bar_group,
});
const x = (date_utils.diff(this.task._start, this.gantt.gantt_start, 'hour') /
const x =
(date_utils.diff(this.task._start, this.gantt.gantt_start, 'hour') /
this.gantt.options.step) *
this.gantt.options.column_width;
let $date_highlight = document.createElement("div");
$date_highlight.id = `${this.task.id}-highlight`
$date_highlight.classList.add('date-highlight')
$date_highlight.style.height = this.height * 0.8 + 'px'
$date_highlight.style.width = this.width + 'px'
$date_highlight.style.top = this.gantt.options.header_height - 25 + 'px'
$date_highlight.style.left = x + 'px'
this.$date_highlight = $date_highlight
this.gantt.$lower_header.prepend($date_highlight)
let $date_highlight = document.createElement('div');
$date_highlight.id = `${this.task.id}-highlight`;
$date_highlight.classList.add('date-highlight');
$date_highlight.style.height = this.height * 0.8 + 'px';
$date_highlight.style.width = this.width + 'px';
$date_highlight.style.top =
this.gantt.options.header_height - 25 + 'px';
$date_highlight.style.left = x + 'px';
this.$date_highlight = $date_highlight;
this.gantt.$lower_header.prepend($date_highlight);
animateSVG(this.$bar_progress, "width", 0, this.progress_width);
animateSVG(this.$bar_progress, 'width', 0, this.progress_width);
}
draw_label() {
@ -166,22 +174,23 @@ export default class Bar {
x_coord = this.x + this.image_size + 5;
}
createSVG("text", {
createSVG('text', {
x: x_coord,
y: this.y + this.height / 2,
innerHTML: this.task.name,
class: "bar-label",
class: 'bar-label',
append_to: this.bar_group,
});
// labels get BBox in the next tick
requestAnimationFrame(() => this.update_label_position());
}
draw_thumbnail() {
let x_offset = 10, y_offset = 2;
let x_offset = 10,
y_offset = 2;
let defs, clipPath;
defs = createSVG('defs', {
append_to: this.bar_group
append_to: this.bar_group,
});
createSVG('rect', {
@ -192,17 +201,17 @@ export default class Bar {
height: this.image_size,
rx: '15',
class: 'img_mask',
append_to: defs
append_to: defs,
});
clipPath = createSVG('clipPath', {
id: 'clip_' + this.task.id,
append_to: defs
append_to: defs,
});
createSVG('use', {
href: '#rect_' + this.task.id,
append_to: clipPath
append_to: clipPath,
});
createSVG('image', {
@ -213,7 +222,7 @@ export default class Bar {
class: 'bar-img',
href: this.task.thumbnail,
clipPath: 'clip_' + this.task.id,
append_to: this.bar_group
append_to: this.bar_group,
});
}
@ -222,57 +231,39 @@ export default class Bar {
const bar = this.$bar;
const handle_width = 8;
createSVG("rect", {
if (!this.gantt.options.dates_readonly) {
createSVG('rect', {
x: bar.getX() + bar.getWidth() + handle_width - 4,
y: bar.getY() + 1,
width: handle_width,
height: this.height - 2,
rx: this.corner_radius,
ry: this.corner_radius,
class: "handle right",
class: 'handle right',
append_to: this.handle_group,
});
createSVG("rect", {
createSVG('rect', {
x: bar.getX() - handle_width - 4,
y: bar.getY() + 1,
width: handle_width,
height: this.height - 2,
rx: this.corner_radius,
ry: this.corner_radius,
class: "handle left",
append_to: this.handle_group,
});
this.$handle_progress = createSVG("polygon", {
points: this.get_progress_polygon_points().join(","),
class: "handle progress",
class: 'handle left',
append_to: this.handle_group,
});
}
get_progress_polygon_points() {
if (!this.gantt.options.progress_readonly) {
const bar_progress = this.$bar_progress;
let icon_width = 10;
let icon_height = 15;
return [
bar_progress.getEndX() - icon_width / 2,
bar_progress.getY() + bar_progress.getHeight() / 2,
bar_progress.getEndX(),
bar_progress.getY() + bar_progress.getHeight() / 2 - icon_height / 2,
bar_progress.getEndX() + icon_width / 2,
bar_progress.getY() + bar_progress.getHeight() / 2,
bar_progress.getEndX(),
bar_progress.getY() + bar_progress.getHeight() / 2 + icon_height / 2,
bar_progress.getEndX() - icon_width / 2,
bar_progress.getY() + bar_progress.getHeight() / 2,
];
this.$handle_progress = createSVG('circle', {
cx: bar_progress.getEndX(),
cy: bar_progress.getY() + bar_progress.getHeight() / 2,
r: 5,
class: 'handle progress',
append_to: this.handle_group,
});
}
}
bind() {
@ -282,34 +273,49 @@ export default class Bar {
setup_click_event() {
let task_id = this.task.id;
$.on(this.group, "mouseover", (e) => {
this.gantt.trigger_event("hover", [this.task, e.screenX, e.screenY, e])
})
let timeout;
$.on(this.group, "mouseenter", (e) => timeout = setTimeout(() => {
this.show_popup(e.offsetX)
document.querySelector(`#${task_id}-highlight`).style.display = 'block'
}, 200))
$.on(this.group, "mouseleave", () => {
clearTimeout(timeout)
this.gantt.popup?.hide?.()
document.querySelector(`#${task_id}-highlight`).style.display = 'none'
})
$.on(this.group, this.gantt.options.popup_trigger, () => {
this.gantt.trigger_event("click", [this.task]);
$.on(this.group, 'mouseover', (e) => {
this.gantt.trigger_event('hover', [
this.task,
e.screenX,
e.screenY,
e,
]);
});
$.on(this.group, "dblclick", (e) => {
let timeout;
$.on(
this.group,
'mouseenter',
(e) =>
(timeout = setTimeout(() => {
this.show_popup(e.offsetX || e.layerX);
document.getElementById(
`${task_id}-highlight`,
).style.display = 'block';
}, 200)),
);
$.on(this.group, 'mouseleave', () => {
clearTimeout(timeout);
this.gantt.popup?.hide?.();
document.getElementById(`${task_id}-highlight`).style.display =
'none';
});
$.on(this.group, 'click', () => {
this.gantt.trigger_event('click', [this.task]);
});
$.on(this.group, 'dblclick', (e) => {
if (this.action_completed) {
// just finished a move action, wait for a few seconds
return;
}
this.group.classList.remove('active');
if (this.gantt.popup)
this.gantt.popup.parent.classList.remove('hidden');
this.gantt.trigger_event("double_click", [this.task]);
this.gantt.trigger_event('double_click', [this.task]);
});
}
@ -318,12 +324,12 @@ export default class Bar {
const start_date = date_utils.format(
this.task._start,
"MMM D",
'MMM D',
this.gantt.options.language,
);
const end_date = date_utils.format(
date_utils.add(this.task._end, -1, "second"),
"MMM D",
date_utils.add(this.task._end, -1, 'second'),
'MMM D',
this.gantt.options.language,
);
const subtitle = `${start_date} - ${end_date}<br/>Progress: ${this.task.progress}`;
@ -352,12 +358,12 @@ export default class Bar {
width = null;
return;
}
this.update_attr(bar, "x", x);
this.$date_highlight.style.left = x + 'px'
this.update_attr(bar, 'x', x);
this.$date_highlight.style.left = x + 'px';
}
if (width) {
this.update_attr(bar, "width", width);
this.$date_highlight.style.width = width + 'px'
this.update_attr(bar, 'width', width);
this.$date_highlight.style.width = width + 'px';
}
this.update_label_position();
this.update_handle_position();
@ -378,8 +384,8 @@ export default class Bar {
let barWidthLimit = this.$bar.getX() + this.$bar.getWidth();
let newLabelX = label.getX() + x;
let newImgX = img && img.getX() + x || 0;
let imgWidth = img && img.getBBox().width + 7 || 7;
let newImgX = (img && img.getX() + x) || 0;
let imgWidth = (img && img.getBBox().width + 7) || 7;
let labelEndX = newLabelX + label.getBBox().width + 7;
let viewportCentral = sx + container.clientWidth / 2;
@ -391,13 +397,16 @@ export default class Bar {
img.setAttribute('x', newImgX);
img_mask.setAttribute('x', newImgX);
}
} else if ((newLabelX - imgWidth) > this.$bar.getX() && x < 0 && labelEndX > viewportCentral) {
} else if (
newLabelX - imgWidth > this.$bar.getX() &&
x < 0 &&
labelEndX > viewportCentral
) {
label.setAttribute('x', newLabelX);
if (img) {
img.setAttribute('x', newImgX);
img_mask.setAttribute('x', newImgX);
}
}
}
@ -417,17 +426,17 @@ export default class Bar {
if (!changed) return;
this.gantt.trigger_event("date_change", [
this.gantt.trigger_event('date_change', [
this.task,
new_start_date,
date_utils.add(new_end_date, -1, "second"),
date_utils.add(new_end_date, -1, 'second'),
]);
}
progress_changed() {
const new_progress = this.compute_progress();
this.task.progress = new_progress;
this.gantt.trigger_event("progress_change", [this.task, new_progress]);
this.gantt.trigger_event('progress_change', [this.task, new_progress]);
}
set_action_completed() {
@ -438,16 +447,27 @@ export default class Bar {
compute_start_end_date() {
const bar = this.$bar;
const x_in_units = bar.getX() / this.gantt.options.column_width;
const new_start_date = date_utils.add(
let new_start_date = date_utils.add(
this.gantt.gantt_start,
x_in_units * this.gantt.options.step,
"hour",
'hour',
);
const start_offset =
this.gantt.gantt_start.getTimezoneOffset() -
new_start_date.getTimezoneOffset();
if (start_offset) {
new_start_date = date_utils.add(
new_start_date,
start_offset,
'minute',
);
}
const width_in_units = bar.getWidth() / this.gantt.options.column_width;
const new_end_date = date_utils.add(
new_start_date,
width_in_units * this.gantt.options.step,
"hour",
'hour',
);
return { new_start_date, new_end_date };
@ -461,7 +481,7 @@ export default class Bar {
compute_expected_progress() {
this.expected_progress =
date_utils.diff(date_utils.today(), this.task._start, "hour") /
date_utils.diff(date_utils.today(), this.task._start, 'hour') /
this.gantt.options.step;
this.expected_progress =
((this.expected_progress < this.duration
@ -476,11 +496,11 @@ export default class Bar {
const task_start = this.task._start;
const gantt_start = this.gantt.gantt_start;
const diff = date_utils.diff(task_start, gantt_start, "hour");
const diff = date_utils.diff(task_start, gantt_start, 'hour');
let x = (diff / step) * column_width;
if (this.gantt.view_is("Month")) {
const diff = date_utils.diff(task_start, gantt_start, "day");
if (this.gantt.view_is('Month')) {
const diff = date_utils.diff(task_start, gantt_start, 'day');
x = (diff * column_width) / 30;
}
this.x = x;
@ -495,7 +515,7 @@ export default class Bar {
compute_duration() {
this.duration =
date_utils.diff(this.task._end, this.task._start, "hour") /
date_utils.diff(this.task._end, this.task._start, 'hour') /
this.gantt.options.step;
}
@ -504,7 +524,7 @@ export default class Bar {
rem,
position;
if (this.gantt.view_is("Week")) {
if (this.gantt.view_is('Week')) {
rem = dx % (this.gantt.options.column_width / 7);
position =
odx -
@ -512,7 +532,7 @@ export default class Bar {
(rem < this.gantt.options.column_width / 14
? 0
: this.gantt.options.column_width / 7);
} else if (this.gantt.view_is("Month")) {
} else if (this.gantt.view_is('Month')) {
rem = dx % (this.gantt.options.column_width / 30);
position =
odx -
@ -542,10 +562,10 @@ export default class Bar {
update_expected_progressbar_position() {
if (this.invalid) return;
this.$expected_bar_progress.setAttribute("x", this.$bar.getX());
this.$expected_bar_progress.setAttribute('x', this.$bar.getX());
this.compute_expected_progress();
this.$expected_bar_progress.setAttribute(
"width",
'width',
this.gantt.options.column_width *
this.duration *
(this.expected_progress / 100) || 0,
@ -554,9 +574,9 @@ export default class Bar {
update_progressbar_position() {
if (this.invalid || this.gantt.options.readonly) return;
this.$bar_progress.setAttribute("x", this.$bar.getX());
this.$bar_progress.setAttribute('x', this.$bar.getX());
this.$bar_progress.setAttribute(
"width",
'width',
this.$bar.getWidth() * (this.task.progress / 100),
);
}
@ -564,31 +584,42 @@ export default class Bar {
update_label_position() {
const img_mask = this.bar_group.querySelector('.img_mask') || '';
const bar = this.$bar,
label = this.group.querySelector(".bar-label"),
label = this.group.querySelector('.bar-label'),
img = this.group.querySelector('.bar-img');
let padding = 5;
let x_offset_label_img = this.image_size + 10;
const labelWidth = label.getBBox().width
const barWidth = bar.getWidth()
const labelWidth = label.getBBox().width;
const barWidth = bar.getWidth();
if (labelWidth > barWidth) {
label.classList.add("big");
label.classList.add('big');
if (img) {
img.setAttribute('x', bar.getX() + bar.getWidth() + padding);
img_mask.setAttribute('x', bar.getX() + bar.getWidth() + padding);
label.setAttribute('x', bar.getX() + bar.getWidth() + x_offset_label_img);
img_mask.setAttribute(
'x',
bar.getX() + bar.getWidth() + padding,
);
label.setAttribute(
'x',
bar.getX() + bar.getWidth() + x_offset_label_img,
);
} else {
label.setAttribute('x', bar.getX() + bar.getWidth() + padding);
}
} else {
label.classList.remove("big");
label.classList.remove('big');
if (img) {
img.setAttribute('x', bar.getX() + padding);
img_mask.setAttribute('x', bar.getX() + padding);
label.setAttribute('x', bar.getX() + barWidth / 2 + x_offset_label_img);
label.setAttribute(
'x',
bar.getX() + barWidth / 2 + x_offset_label_img,
);
} else {
label.setAttribute('x', bar.getX() + barWidth / 2 - labelWidth / 2);
label.setAttribute(
'x',
bar.getX() + barWidth / 2 - labelWidth / 2,
);
}
}
}
@ -597,13 +628,13 @@ export default class Bar {
if (this.invalid || this.gantt.options.readonly) return;
const bar = this.$bar;
this.handle_group
.querySelector(".handle.left")
.setAttribute("x", bar.getX() - 12);
.querySelector('.handle.left')
.setAttribute('x', bar.getX() - 12);
this.handle_group
.querySelector(".handle.right")
.setAttribute("x", bar.getEndX() + 4);
const handle = this.group.querySelector(".handle.progress");
handle && handle.setAttribute("points", this.get_progress_polygon_points());
.querySelector('.handle.right')
.setAttribute('x', bar.getEndX() + 4);
const handle = this.group.querySelector('.handle.progress');
handle && handle.setAttribute('cx', this.$bar_progress.getEndX());
}
update_arrow_position() {
@ -618,6 +649,6 @@ function isFunction(functionToCheck) {
let getType = {};
return (
functionToCheck &&
getType.toString.call(functionToCheck) === "[object Function]"
getType.toString.call(functionToCheck) === '[object Function]'
);
}

99
src/dark.css Normal file
View File

@ -0,0 +1,99 @@
:root {
--bar-color-dark: #616161;
--bar-stroke-dark: #c6ccd2;
--border-color-dark: #616161;
--light-bg-dark: #3e3e3e;
--light-border-color-dark: #3e3e3e;
--text-muted-dark: #eee;
--text-light-dark: #ececec;
--text-color-dark: #f7f7f7;
--blue-dark: #8a8aff;
}
.dark>.gantt-container .gantt {
& .grid-row {
fill: #252525;
}
/* & .grid-row:nth-child(even) {
fill: var(--light-bg-dark);
} */
& .row-line {
stroke: var(--light-border-color-dark);
}
& .tick {
stroke: var(--border-color-dark);
}
& .holiday-highlight {
fill: var(--light-bg-dark);
}
& .arrow {
stroke: var(--text-muted-dark);
}
& .bar {
fill: var(--bar-color-dark);
stroke: none;
}
& .bar-progress {
fill: var(--blue-dark);
}
& .bar-invalid {
fill: transparent;
stroke: var(--bar-stroke-dark);
&~.bar-label {
fill: var(--text-light-dark);
}
}
& .bar-label.big {
fill: var(--text-light-dark);
}
& .bar-wrapper {
&:hover {
.bar {
fill: lighten(var(--bar-color-dark, 5));
}
& .bar-progress {
fill: lighten(var(--blue-dark, 5));
}
}
&.active {
.bar {
fill: lighten(var(--bar-color-dark, 5));
}
& .bar-progress {
fill: lighten(var(--blue-dark, 5));
}
}
}
}
.dark>.gantt-container {
& .grid-header {
background-color: #252525;
}
& .popup-wrapper {
background-color: #333;
& .title {
border-color: lighten(var(--blue-dark, 5));
}
& .pointer {
border-top-color: #333;
}
}
}

View File

@ -1,97 +0,0 @@
$bar-color-dark: #616161;
$bar-stroke-dark: #c6ccd2;
$border-color-dark: #616161;
$light-bg-dark: #3e3e3e;
$light-border-color-dark: #3e3e3e;
$text-muted-dark: #eee;
$text-light-dark: #ececec;
$text-color-dark: #f7f7f7;
$blue-dark: #8a8aff;
.dark > .gantt-container .gantt {
.grid-header {
fill: #252525;
stroke: $border-color-dark;
}
.grid-row {
fill: #252525;
}
.grid-row:nth-child(even) {
fill: $light-bg-dark;
}
.row-line {
stroke: $light-border-color-dark;
}
.tick {
stroke: $border-color-dark;
}
.today-highlight {
opacity: 0.2;
}
.arrow {
stroke: $text-muted-dark;
}
.bar {
fill: $bar-color-dark;
stroke: none;
}
.bar-progress {
fill: $blue-dark;
}
.bar-invalid {
fill: transparent;
stroke: $bar-stroke-dark;
& ~ .bar-label {
fill: $text-light-dark;
}
}
.bar-label.big {
fill: $text-light-dark;
}
.bar-wrapper {
&:hover {
.bar {
fill: lighten($bar-color-dark, 5);
}
.bar-progress {
fill: lighten($blue-dark, 5);
}
}
&.active {
.bar {
fill: lighten($bar-color-dark, 5);
}
.bar-progress {
fill: lighten($blue-dark, 5);
}
}
}
.upper-text {
fill: #a2a2a2;
}
.lower-text {
fill: $text-color-dark;
}
}
.dark > .gantt-container {
.popup-wrapper {
background-color: #333;
.title {
border-color: lighten($blue-dark, 5);
}
.pointer {
border-top-color: #333;
}
}
}

View File

@ -1,56 +1,56 @@
const YEAR = "year";
const MONTH = "month";
const DAY = "day";
const HOUR = "hour";
const MINUTE = "minute";
const SECOND = "second";
const MILLISECOND = "millisecond";
const YEAR = 'year';
const MONTH = 'month';
const DAY = 'day';
const HOUR = 'hour';
const MINUTE = 'minute';
const SECOND = 'second';
const MILLISECOND = 'millisecond';
const SHORTENED = {
January: "Jan",
February: "Feb",
March: "Mar",
April: "Apr",
May: "May",
June: "Jun",
July: "Jul",
August: "Aug",
September: "Sep",
October: "Oct",
November: "Nov",
December: "Dec"
January: 'Jan',
February: 'Feb',
March: 'Mar',
April: 'Apr',
May: 'May',
June: 'Jun',
July: 'Jul',
August: 'Aug',
September: 'Sep',
October: 'Oct',
November: 'Nov',
December: 'Dec',
};
export default {
parse_duration(duration) {
const regex = /([0-9])+(y|m|d|h|min|s|ms)/gm;
const regex = /([0-9]+)(y|m|d|h|min|s|ms)/gm;
const matches = regex.exec(duration);
console.log(matches);
if (matches !== null) {
if (matches[2] === "y") {
if (matches[2] === 'y') {
return { duration: parseInt(matches[1]), scale: `year` };
} else if (matches[2] === "m") {
} else if (matches[2] === 'm') {
return { duration: parseInt(matches[1]), scale: `month` };
} else if (matches[2] === "d") {
} else if (matches[2] === 'd') {
return { duration: parseInt(matches[1]), scale: `day` };
} else if (matches[2] === "h") {
} else if (matches[2] === 'h') {
return { duration: parseInt(matches[1]), scale: `hour` };
} else if (matches[2] === "min") {
} else if (matches[2] === 'min') {
return { duration: parseInt(matches[1]), scale: `minute` };
} else if (matches[2] === "s") {
} else if (matches[2] === 's') {
return { duration: parseInt(matches[1]), scale: `second` };
} else if (matches[2] === "ms") {
} else if (matches[2] === 'ms') {
return { duration: parseInt(matches[1]), scale: `millisecond` };
}
}
},
parse(date, date_separator = "-", time_separator = /[.:]/) {
parse(date, date_separator = '-', time_separator = /[.:]/) {
if (date instanceof Date) {
return date;
}
if (typeof date === "string") {
if (typeof date === 'string') {
let date_parts, time_parts;
const parts = date.split(" ");
const parts = date.split(' ');
date_parts = parts[0]
.split(date_separator)
.map((val) => parseInt(val, 10));
@ -63,7 +63,7 @@ export default {
if (time_parts && time_parts.length) {
if (time_parts.length === 4) {
time_parts[3] = "0." + time_parts[3];
time_parts[3] = '0.' + time_parts[3];
time_parts[3] = parseFloat(time_parts[3]) * 1000;
}
vals = vals.concat(time_parts);
@ -74,7 +74,7 @@ export default {
to_string(date, with_time = false) {
if (!(date instanceof Date)) {
throw new TypeError("Invalid argument type");
throw new TypeError('Invalid argument type');
}
const vals = this.get_date_values(date).map((val, i) => {
if (i === 1) {
@ -83,20 +83,20 @@ export default {
}
if (i === 6) {
return padStart(val + "", 3, "0");
return padStart(val + '', 3, '0');
}
return padStart(val + "", 2, "0");
return padStart(val + '', 2, '0');
});
const date_string = `${vals[0]}-${vals[1]}-${vals[2]}`;
const time_string = `${vals[3]}:${vals[4]}:${vals[5]}.${vals[6]}`;
return date_string + (with_time ? " " + time_string : "");
return date_string + (with_time ? ' ' + time_string : '');
},
format(date, format_string = "YYYY-MM-DD HH:mm:ss.SSS", lang = "en") {
format(date, format_string = 'YYYY-MM-DD HH:mm:ss.SSS', lang = 'en') {
const dateTimeFormat = new Intl.DateTimeFormat(lang, {
month: "long",
month: 'long',
});
const month_name = dateTimeFormat.format(date);
const month_name_capitalized =
@ -146,8 +146,8 @@ export default {
months = days / 30;
years = months / 12;
if (!scale.endsWith("s")) {
scale += "s";
if (!scale.endsWith('s')) {
scale += 's';
}
return Math.floor(
@ -251,9 +251,9 @@ export default {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
function padStart(str, targetLength, padString) {
str = str + "";
str = str + '';
targetLength = targetLength >> 0;
padString = String(typeof padString !== "undefined" ? padString : " ");
padString = String(typeof padString !== 'undefined' ? padString : ' ');
if (str.length > targetLength) {
return String(str);
} else {

306
src/gantt.css Normal file
View File

@ -0,0 +1,306 @@
@import './dark.css';
:root {
--bar-color: #fff;
--bar-color-important: #94c4f4;
--bar-stroke: #fff;
--dark-stroke-color: #e0e0e0;
--stroke-color: #ebeef0;
--light-bg: #f5f5f5;
--light-border-color: #ebeff2;
--light-yellow: #f6e796;
--holiday-color: #f9fafa;
--text-muted: #666;
--text-grey: #98a1a9;
--text-light: #fff;
--text-dark: #111;
--progress: #ebeef0;
--handle-color: #dcdce4;
--handle-color-important: #94c4f4;
--light-blue: #c4c4e9;
--middle-blue: #62b2f9;
--dark-blue: #2c94ec;
}
.gantt-container {
line-height: 14.5px;
position: relative;
overflow: auto;
font-size: 12px;
height: 500px;
& .popup-wrapper {
position: absolute;
top: 0;
left: 0;
background: #171b1f;
padding: 10px;
border-radius: 5px;
width: max-content;
&.hidden {
opacity: 0 !important;
}
& .title {
margin-bottom: 5px;
text-align: -webkit-center;
text-align: center;
color: var(--text-light);
}
& .subtitle {
color: var(--text-grey);
}
& .pointer {
position: absolute;
height: 5px;
margin: 0 0 0 -5px;
border: 5px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.8);
}
}
& .grid-header {
background-color: #ffffff;
position: sticky;
top: 0;
left: 0;
z-index: 10;
}
& .lower-text,
& .upper-text {
text-anchor: middle;
color: var(--text-dark);
}
& .upper-header {
height: 40px;
}
& .lower-header {
height: 30px;
}
& .lower-text {
font-size: 14px;
position: absolute;
width: fit-content;
}
& .upper-text {
position: absolute;
width: fit-content;
font-weight: 500;
font-size: 16px;
}
& .current-upper {
position: fixed;
}
& .side-header {
position: fixed;
padding: 0 10px;
margin-right: 10px;
background: white;
line-height: 20px;
font-weight: 400;
}
& .today-button,
& .viewmode-select {
background: #f4f5f6;
text-align: -webkit-center;
text-align: center;
height: 25px;
border-radius: 8px;
border: none;
color: var(--text-dark);
padding: 4px 10px;
border-radius: 8px;
height: 25px;
}
& .viewmode-select {
outline: none !important;
padding: 4px 8px;
margin-right: 4px;
/* -webkit-appearance: none; */
/* -moz-appearance: none; */
text-indent: 1px;
text-overflow: '';
}
& .date-highlight {
background-color: var(--progress);
border-radius: 12px;
position: absolute;
display: none;
}
& .current-highlight {
position: absolute;
background: var(--dark-blue);
width: 1px;
}
& .current-date-highlight {
background: var(--dark-blue);
color: var(--text-light);
padding: 4px 8px;
border-radius: 200px;
}
}
.gantt {
user-select: none;
-webkit-user-select: none;
position: absolute;
& .grid-background {
fill: none;
}
& .grid-row {
fill: #ffffff;
}
& .row-line {
stroke: var(--light-border-color);
}
& .tick {
stroke: var(--stroke-color);
stroke-width: 0.4;
&.thick {
stroke: var(--dark-stroke-color);
stroke-width: 0.7;
}
}
& .holiday-highlight {
fill: var(--holiday-color);
}
& .arrow {
fill: none;
stroke: #9fa9b1;
stroke-width: 1;
}
& .bar-wrapper .bar {
fill: var(--bar-color);
stroke: var(--bar-stroke);
stroke-width: 0;
transition: stroke-width 0.3s ease;
}
& .bar-progress {
fill: var(--progress);
}
& .bar-expected-progress {
fill: var(--light-blue);
}
& .bar-invalid {
fill: transparent;
stroke: var(--bar-stroke);
stroke-width: 1;
stroke-dasharray: 5;
&~.bar-label {
fill: var(--text-light);
}
}
& .bar-label {
fill: var(--text-dark);
dominant-baseline: central;
font-family: Helvetica;
font-size: 13px;
font-weight: 400;
&.big {
fill: var(--text-dark);
text-anchor: start;
}
}
& .bar-wrapper.important {
& .bar {
fill: var(--bar-color-important);
}
& .bar-progress {
fill: var(--dark-blue);
}
& .bar-label {
fill: var(--text-light);
}
& .handle {
fill: var(--handle-color-important);
}
& .handle.progress {
fill: var(--text-light);
}
}
& .handle {
fill: var(--handle-color);
cursor: ew-resize;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease;
}
& .handle.progress {
fill: var(--text-muted);
}
& .bar-wrapper {
cursor: pointer;
&.active {
& .handle {
visibility: visible;
opacity: 1;
}
}
& .bar {
-webkit-filter: drop-shadow(3px 3px 2px rgba(0, 0, 0, 0.7));
filter: drop-shadow(0 0 2px rgba(17, 43, 66, 0.16));
border-radius: 3px;
}
& .bar.safari {
outline: 1px solid black;
}
&:hover {
.bar {
transition: transform 0.3s ease;
}
.date-highlight {
display: block;
}
}
}
& .hide {
display: none;
}
}

View File

@ -1,316 +0,0 @@
@import "./dark.scss";
$bar-color: #fff !default;
$bar-color-important: #94c4f4 !default;
$bar-stroke: #fff !default;
$dark-stroke-color: #e0e0e0 !default;
$stroke-color: #EBEEF0 !default;
$light-bg: #f5f5f5 !default;
$light-border-color: #ebeff2 !default;
$light-yellow: #f6e796 !default;
$holiday-color: #F9FAFA !default;
$text-muted: #666 !default;
$text-grey: #98A1A9;
$text-light: #fff !default;
$text-dark: #111 !default;
$progress: #EBEEF0 !default;
$handle-color: #dcdce4 !default;
$handle-color-important: #94c4f4 !default;
$light-blue: #c4c4e9 !default;
$middle-blue: #62B2F9 !default;
$dark-blue: #2c94ec !default;
.gantt-container {
line-height: 14.5px;
.grid-header {
background-color: #ffffff;
position: sticky;
top: 0;
left: 0;
z-index: 10;
}
.lower-text,
.upper-text {
text-anchor: middle;
color: $text-dark;
}
.upper-header {
height: 40px;
}
.lower-header {
height: 30px;
}
.lower-text {
font-size: 14px;
position: absolute;
width: fit-content;
}
.upper-text {
position: absolute;
width: fit-content;
font-weight: 500;
font-size: 16px;
}
.current-upper {
position: fixed;
}
.side-header {
position: fixed;
padding: 0 10px;
margin-right: 10px;
background: white;
line-height: 20px;
font-weight: 400;
}
.today-button,
.viewmode-select {
background: #F4F5F6;
text-align: -webkit-center;
text-align: center;
height: 25px;
border-radius: 8px;
border: none;
color: $text-dark;
padding: 4px 10px;
border-radius: 8px;
height: 25px;
}
.viewmode-select {
outline: none !important;
padding: 4px 8px;
margin-right: 4px;
// Hide select icon
-webkit-appearance: none;
-moz-appearance: none;
text-indent: 1px;
text-overflow: '';
}
.date-highlight {
background-color: $progress;
border-radius: 12px;
position: absolute;
display: none;
}
.current-highlight {
position: absolute;
background: $dark-blue;
width: 1px;
}
.current-date-highlight {
background: $dark-blue;
color: $text-light;
padding: 4px 8px;
border-radius: 200px;
}
}
.gantt {
user-select: none;
-webkit-user-select: none;
position: absolute;
.grid-background {
fill: none;
}
.grid-row {
fill: #ffffff;
}
// .grid-row:nth-child(even) {
// fill: $light-bg;
// }
.row-line {
stroke: $light-border-color;
}
.tick {
stroke: $stroke-color;
stroke-width: 0.4;
&.thick {
stroke: $dark-stroke-color;
stroke-width: 0.7;
}
}
.holiday-highlight {
fill: $holiday-color;
}
.arrow {
fill: none;
stroke: #9FA9B1;
stroke-width: 1;
}
.bar-wrapper .bar {
fill: $bar-color;
stroke: $bar-stroke;
stroke-width: 0;
transition: stroke-width 0.3s ease;
}
.bar-progress {
fill: $progress;
}
.bar-expected-progress {
fill: $light-blue;
}
.bar-invalid {
fill: transparent;
stroke: $bar-stroke;
stroke-width: 1;
stroke-dasharray: 5;
&~.bar-label {
fill: $text-light;
}
}
.bar-label {
fill: $text-dark;
dominant-baseline: central;
// text-anchor: middle;
font-family: Helvetica;
font-size: 13px;
font-weight: 400;
&.big {
fill: $text-dark;
text-anchor: start;
}
}
.bar-wrapper.important {
.bar {
fill: $bar-color-important;
}
.bar-progress {
fill: $dark-blue;
}
.bar-label {
fill: $text-light;
}
.handle {
fill: $handle-color-important;
}
.handle.progress {
fill: $text-light;
}
}
.handle {
fill: $handle-color;
cursor: ew-resize;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease;
}
.handle.progress {
fill: $text-muted;
}
.bar-wrapper {
cursor: pointer;
outline: none;
&.active {
& .handle {
visibility: visible;
opacity: 1;
}
}
.bar {
-webkit-filter: drop-shadow(3px 3px 2px rgba(0, 0, 0, .7));
filter: drop-shadow(0 0 2px rgba(17, 43, 66, .16));
border-radius: 3px;
}
&:hover {
.bar {
transition: transform 0.3s ease;
}
.date-highlight {
display: block;
}
}
}
.hide {
display: none;
}
}
.gantt-container {
position: relative;
overflow: auto;
font-size: 12px;
height: 500px;
.popup-wrapper {
position: absolute;
top: 0;
left: 0;
background: #171B1F;
padding: 10px;
border-radius: 5px;
width: max-content;
&.hidden {
opacity: 0 !important;
}
.title {
margin-bottom: 5px;
text-align: -webkit-center;
text-align: center;
color: $text-light;
}
.subtitle {
color: $text-grey;
}
.pointer {
position: absolute;
height: 5px;
margin: 0 0 0 -5px;
border: 5px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.8);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -14,14 +14,14 @@ export default class Popup {
this.hide();
this.title = this.parent.querySelector(".title");
this.subtitle = this.parent.querySelector(".subtitle");
this.pointer = this.parent.querySelector(".pointer");
this.title = this.parent.querySelector('.title');
this.subtitle = this.parent.querySelector('.subtitle');
this.pointer = this.parent.querySelector('.pointer');
}
show(options) {
if (!options.target_element) {
throw new Error("target_element is required to show popup");
throw new Error('target_element is required to show popup');
}
const target_element = options.target_element;
@ -29,7 +29,7 @@ export default class Popup {
let html = this.custom_html(options.task);
html += '<div class="pointer"></div>';
this.parent.innerHTML = html;
this.pointer = this.parent.querySelector(".pointer");
this.pointer = this.parent.querySelector('.pointer');
} else {
// set data
this.title.innerHTML = options.title;
@ -44,11 +44,12 @@ export default class Popup {
position_meta = options.target_element.getBBox();
}
this.parent.style.left = options.x - this.parent.clientWidth / 2 + "px";
this.parent.style.top = position_meta.y + position_meta.height + 10 + "px";
this.parent.style.left = options.x - this.parent.clientWidth / 2 + 'px';
this.parent.style.top =
position_meta.y + position_meta.height + 10 + 'px';
this.pointer.style.left = this.parent.clientWidth / 2 + "px";
this.pointer.style.top = "-15px";
this.pointer.style.left = this.parent.clientWidth / 2 + 'px';
this.pointer.style.top = '-15px';
// show
this.parent.style.opacity = 1;

View File

@ -1,16 +1,16 @@
export function $(expr, con) {
return typeof expr === "string"
return typeof expr === 'string'
? (con || document).querySelector(expr)
: expr || null;
}
export function createSVG(tag, attrs) {
const elem = document.createElementNS("http://www.w3.org/2000/svg", tag);
const elem = document.createElementNS('http://www.w3.org/2000/svg', tag);
for (let attr in attrs) {
if (attr === "append_to") {
if (attr === 'append_to') {
const parent = attrs.append_to;
parent.appendChild(elem);
} else if (attr === "innerHTML") {
} else if (attr === 'innerHTML') {
elem.innerHTML = attrs.innerHTML;
} else if (attr === 'clipPath') {
elem.setAttribute('clip-path', 'url(#' + attrs[attr] + ')');
@ -27,9 +27,9 @@ export function animateSVG(svgElement, attr, from, to) {
if (animatedSvgElement === svgElement) {
// triggered 2nd time programmatically
// trigger artificial click event
const event = document.createEvent("HTMLEvents");
event.initEvent("click", true, true);
event.eventName = "click";
const event = document.createEvent('HTMLEvents');
event.initEvent('click', true, true);
event.eventName = 'click';
animatedSvgElement.dispatchEvent(event);
}
}
@ -39,31 +39,31 @@ function getAnimationElement(
attr,
from,
to,
dur = "0.4s",
begin = "0.1s",
dur = '0.4s',
begin = '0.1s',
) {
const animEl = svgElement.querySelector("animate");
const animEl = svgElement.querySelector('animate');
if (animEl) {
$.attr(animEl, {
attributeName: attr,
from,
to,
dur,
begin: "click + " + begin, // artificial click
begin: 'click + ' + begin, // artificial click
});
return svgElement;
}
const animateElement = createSVG("animate", {
const animateElement = createSVG('animate', {
attributeName: attr,
from,
to,
dur,
begin,
calcMode: "spline",
values: from + ";" + to,
keyTimes: "0; 1",
keySplines: cubic_bezier("ease-out"),
calcMode: 'spline',
values: from + ';' + to,
keyTimes: '0; 1',
keySplines: cubic_bezier('ease-out'),
});
svgElement.appendChild(animateElement);
@ -72,11 +72,11 @@ function getAnimationElement(
function cubic_bezier(name) {
return {
ease: ".25 .1 .25 1",
linear: "0 0 1 1",
"ease-in": ".42 0 1 1",
"ease-out": "0 0 .58 1",
"ease-in-out": ".42 0 .58 1",
ease: '.25 .1 .25 1',
linear: '0 0 1 1',
'ease-in': '.42 0 1 1',
'ease-out': '0 0 .58 1',
'ease-in-out': '.42 0 .58 1',
}[name];
}
@ -120,11 +120,11 @@ $.closest = (selector, element) => {
};
$.attr = (element, attr, value) => {
if (!value && typeof attr === "string") {
if (!value && typeof attr === 'string') {
return element.getAttribute(attr);
}
if (typeof attr === "object") {
if (typeof attr === 'object') {
for (let key in attr) {
$.attr(element, key, attr[key]);
}

View File

@ -1,15 +1,15 @@
import date_utils from "../src/date_utils";
import date_utils from '../src/date_utils';
test("Parse: parses string date", () => {
const date = date_utils.parse("2017-09-09");
test('Parse: parses string date', () => {
const date = date_utils.parse('2017-09-09');
expect(date.getDate()).toBe(9);
expect(date.getMonth()).toBe(8);
expect(date.getFullYear()).toBe(2017);
});
test("Parse: parses string datetime", () => {
const date = date_utils.parse("2017-08-27 16:08:34");
test('Parse: parses string datetime', () => {
const date = date_utils.parse('2017-08-27 16:08:34');
expect(date.getFullYear()).toBe(2017);
expect(date.getMonth()).toBe(7);
@ -19,8 +19,8 @@ test("Parse: parses string datetime", () => {
expect(date.getSeconds()).toBe(34);
});
test("Parse: parses string datetime", () => {
const date = date_utils.parse("2016-02-29 16:08:34.3");
test('Parse: parses string datetime', () => {
const date = date_utils.parse('2016-02-29 16:08:34.3');
expect(date.getFullYear()).toBe(2016);
expect(date.getMonth()).toBe(1);
@ -31,8 +31,8 @@ test("Parse: parses string datetime", () => {
expect(date.getMilliseconds()).toBe(300);
});
test("Parse: parses string datetime", () => {
const date = date_utils.parse("2015-07-01 00:00:59.200");
test('Parse: parses string datetime', () => {
const date = date_utils.parse('2015-07-01 00:00:59.200');
expect(date.getFullYear()).toBe(2015);
expect(date.getMonth()).toBe(6);
@ -43,82 +43,82 @@ test("Parse: parses string datetime", () => {
expect(date.getMilliseconds()).toBe(200);
});
test("Format: converts date object to string", () => {
const date = new Date("2017-09-18");
expect(date_utils.to_string(date)).toBe("2017-09-18");
test('Format: converts date object to string', () => {
const date = new Date('2017-09-18');
expect(date_utils.to_string(date)).toBe('2017-09-18');
});
test("Format: converts date object to string", () => {
const date = new Date("2016-02-29 16:08:34.3");
expect(date_utils.to_string(date, true)).toBe("2016-02-29 16:08:34.300");
test('Format: converts date object to string', () => {
const date = new Date('2016-02-29 16:08:34.3');
expect(date_utils.to_string(date, true)).toBe('2016-02-29 16:08:34.300');
});
test("Format: converts date object to string", () => {
const date = new Date("2016-02-29 16:08:34.3");
expect(date_utils.to_string(date, true)).toBe("2016-02-29 16:08:34.300");
test('Format: converts date object to string', () => {
const date = new Date('2016-02-29 16:08:34.3');
expect(date_utils.to_string(date, true)).toBe('2016-02-29 16:08:34.300');
});
test("Parse: returns Date Object as is", () => {
test('Parse: returns Date Object as is', () => {
const d = new Date();
const date = date_utils.parse(d);
expect(d).toBe(date);
});
test("Diff: returns diff between 2 date objects", () => {
const a = date_utils.parse("2017-09-08");
const b = date_utils.parse("2017-06-07");
test('Diff: returns diff between 2 date objects', () => {
const a = date_utils.parse('2017-09-08');
const b = date_utils.parse('2017-06-07');
expect(date_utils.diff(a, b, "day")).toBe(93);
expect(date_utils.diff(a, b, "month")).toBe(3);
expect(date_utils.diff(a, b, "year")).toBe(0);
expect(date_utils.diff(a, b, 'day')).toBe(93);
expect(date_utils.diff(a, b, 'month')).toBe(3);
expect(date_utils.diff(a, b, 'year')).toBe(0);
});
test("StartOf", () => {
const date = date_utils.parse("2017-08-12 15:07:34.012");
test('StartOf', () => {
const date = date_utils.parse('2017-08-12 15:07:34.012');
const start_of_millisecond = date_utils.start_of(date, "millisecond");
const start_of_millisecond = date_utils.start_of(date, 'millisecond');
expect(date_utils.to_string(start_of_millisecond, true)).toBe(
"2017-08-12 15:07:34.012",
'2017-08-12 15:07:34.012',
);
const start_of_second = date_utils.start_of(date, "second");
const start_of_second = date_utils.start_of(date, 'second');
expect(date_utils.to_string(start_of_second, true)).toBe(
"2017-08-12 15:07:34.000",
'2017-08-12 15:07:34.000',
);
const start_of_minute = date_utils.start_of(date, "minute");
const start_of_minute = date_utils.start_of(date, 'minute');
expect(date_utils.to_string(start_of_minute, true)).toBe(
"2017-08-12 15:07:00.000",
'2017-08-12 15:07:00.000',
);
const start_of_hour = date_utils.start_of(date, "hour");
const start_of_hour = date_utils.start_of(date, 'hour');
expect(date_utils.to_string(start_of_hour, true)).toBe(
"2017-08-12 15:00:00.000",
'2017-08-12 15:00:00.000',
);
const start_of_day = date_utils.start_of(date, "day");
const start_of_day = date_utils.start_of(date, 'day');
expect(date_utils.to_string(start_of_day, true)).toBe(
"2017-08-12 00:00:00.000",
'2017-08-12 00:00:00.000',
);
const start_of_month = date_utils.start_of(date, "month");
const start_of_month = date_utils.start_of(date, 'month');
expect(date_utils.to_string(start_of_month, true)).toBe(
"2017-08-01 00:00:00.000",
'2017-08-01 00:00:00.000',
);
const start_of_year = date_utils.start_of(date, "year");
const start_of_year = date_utils.start_of(date, 'year');
expect(date_utils.to_string(start_of_year, true)).toBe(
"2017-01-01 00:00:00.000",
'2017-01-01 00:00:00.000',
);
});
test("format", () => {
const date = date_utils.parse("2017-08-12 15:07:23");
expect(date_utils.format(date, "YYYY-MM-DD")).toBe("2017-08-12");
test('format', () => {
const date = date_utils.parse('2017-08-12 15:07:23');
expect(date_utils.format(date, 'YYYY-MM-DD')).toBe('2017-08-12');
});
test("format", () => {
const date = date_utils.parse("2016-02-29 16:08:34.3");
expect(date_utils.format(date)).toBe("2016-02-29 16:08:34.300");
test('format', () => {
const date = date_utils.parse('2016-02-29 16:08:34.3');
expect(date_utils.format(date)).toBe('2016-02-29 16:08:34.300');
});

22
vite.config.js Normal file
View File

@ -0,0 +1,22 @@
import { resolve } from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.js'),
name: 'Gantt',
fileName: 'frappe-gantt',
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue'
}
},
},
},
output: { interop: 'auto' },
server: { watch: { include: ['dist/*', 'src/*'] } }
});

10471
yarn.lock

File diff suppressed because it is too large Load Diff