chore: format (add prettier), fix minor bug, build

This commit is contained in:
Safwan Samsudeen 2024-04-05 15:39:04 +05:30
parent a0c7cd36bd
commit f9da6ad42f
25 changed files with 4248 additions and 4115 deletions

View File

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

View File

@ -1,7 +1,7 @@
name: Publish on NPM name: Publish on NPM
on: on:
push: push:
branches: [ master ] branches: [master]
jobs: jobs:
publish: publish:

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
dist

1
.prettierrc Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -1,4 +1,4 @@
{ {
"tabWidth": 4, "tabWidth": 4,
"singleQuote": true "singleQuote": true
} }

View File

@ -16,18 +16,22 @@
</p> </p>
### Install ### Install
``` ```
npm install frappe-gantt npm install frappe-gantt
``` ```
### Usage ### Usage
Include it in your HTML: Include it in your HTML:
``` ```
<script src="frappe-gantt.min.js"></script> <script src="frappe-gantt.min.js"></script>
<link rel="stylesheet" href="frappe-gantt.css"> <link rel="stylesheet" href="frappe-gantt.css">
``` ```
And start hacking: And start hacking:
```js ```js
var tasks = [ var tasks = [
{ {
@ -45,20 +49,21 @@ var gantt = new Gantt("#gantt", tasks);
``` ```
You can also pass various options to the Gantt constructor: You can also pass various options to the Gantt constructor:
```js ```js
var gantt = new Gantt("#gantt", tasks, { var gantt = new Gantt("#gantt", tasks, {
header_height: 50, header_height: 50,
column_width: 30, column_width: 30,
step: 24, step: 24,
view_modes: ['Quarter Day', 'Half Day', 'Day', 'Week', 'Month'], view_modes: ["Quarter Day", "Half Day", "Day", "Week", "Month"],
bar_height: 20, bar_height: 20,
bar_corner_radius: 3, bar_corner_radius: 3,
arrow_curve: 5, arrow_curve: 5,
padding: 18, padding: 18,
view_mode: 'Day', view_mode: "Day",
date_format: 'YYYY-MM-DD', date_format: "YYYY-MM-DD",
language: 'en', // or 'es', 'it', 'ru', 'ptBr', 'fr', 'tr', 'zh', 'de', 'hu' language: "en", // or 'es', 'it', 'ru', 'ptBr', 'fr', 'tr', 'zh', 'de', 'hu'
custom_popup_html: null custom_popup_html: null,
}); });
``` ```
@ -69,6 +74,7 @@ You can add `dark` class to the container element to apply dark theme.
``` ```
### Contributing ### Contributing
If you want to contribute enhancements or fixes: If you want to contribute enhancements or fixes:
1. Clone this repo. 1. Clone this repo.
@ -78,6 +84,7 @@ If you want to contribute enhancements or fixes:
5. Open `index.html` in your browser, make your code changes and test them. 5. Open `index.html` in your browser, make your code changes and test them.
### Publishing ### Publishing
If you have publishing rights (Frappe Team), follow these steps to publish a new version. If you have publishing rights (Frappe Team), follow these steps to publish a new version.
Assuming the last commit (or a couple of commits) were enhancements or fixes, Assuming the last commit (or a couple of commits) were enhancements or fixes,
@ -85,15 +92,18 @@ Assuming the last commit (or a couple of commits) were enhancements or fixes,
1. Run `yarn build` 1. Run `yarn build`
This will generate files in the `dist/` folder. These files need to be committed. This will generate files in the `dist/` folder. These files need to be committed.
1. Run `yarn publish` 1. Run `yarn publish`
1. Type the new version at the prompt 1. Type the new version at the prompt
Depending on the type of change, you can either bump the patch version or the minor version. Depending on the type of change, you can either bump the patch version or the minor version.
For e.g., For e.g.,
``` ```
0.5.0 -> 0.6.0 (minor version bump) 0.5.0 -> 0.6.0 (minor version bump)
0.5.0 -> 0.5.1 (patch version bump) 0.5.0 -> 0.5.1 (patch version bump)
``` ```
1. Now, there will be a commit named after the version you just entered. Include the generated files in `dist/` folder as part of this commit by running the command: 1. Now, there will be a commit named after the version you just entered. Include the generated files in `dist/` folder as part of this commit by running the command:
``` ```
git add dist git add dist
@ -103,5 +113,6 @@ Assuming the last commit (or a couple of commits) were enhancements or fixes,
License: MIT License: MIT
------------------ ---
Project maintained by [frappe](https://github.com/frappe) Project maintained by [frappe](https://github.com/frappe)

24
dist/frappe-gantt.css vendored
View File

@ -66,6 +66,10 @@
border-top-color: #333; border-top-color: #333;
} }
.gantt {
user-select: none;
-webkit-user-select: none;
}
.gantt .grid-background { .gantt .grid-background {
fill: none; fill: none;
} }
@ -94,6 +98,18 @@
fill: #fcf8e3; fill: #fcf8e3;
opacity: 0.5; opacity: 0.5;
} }
.gantt .week-highlight {
fill: #fcf8e3;
opacity: 0.5;
}
.gantt .month-highlight {
fill: #fcf8e3;
opacity: 0.5;
}
.gantt .year-highlight {
fill: #fcf8e3;
opacity: 0.5;
}
.gantt .arrow { .gantt .arrow {
fill: none; fill: none;
stroke: #666; stroke: #666;
@ -101,10 +117,9 @@
} }
.gantt .bar { .gantt .bar {
fill: #b8c2cc; fill: #b8c2cc;
stroke: #8D99A6; stroke: #8d99a6;
stroke-width: 0; stroke-width: 0;
transition: stroke-width 0.3s ease; transition: stroke-width 0.3s ease;
user-select: none;
} }
.gantt .bar-progress { .gantt .bar-progress {
fill: #acacfa; fill: #acacfa;
@ -114,7 +129,7 @@
} }
.gantt .bar-invalid { .gantt .bar-invalid {
fill: transparent; fill: transparent;
stroke: #8D99A6; stroke: #8d99a6;
stroke-width: 1; stroke-width: 1;
stroke-dasharray: 5; stroke-dasharray: 5;
} }
@ -159,7 +174,8 @@
.gantt .bar-wrapper.active .bar-progress { .gantt .bar-wrapper.active .bar-progress {
fill: #9494f9; fill: #9494f9;
} }
.gantt .lower-text, .gantt .upper-text { .gantt .lower-text,
.gantt .upper-text {
font-size: 12px; font-size: 12px;
text-anchor: middle; text-anchor: middle;
} }

3684
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 +1 @@
.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 .grid-background{fill:none}.gantt .grid-header{fill:#fff;stroke:#e0e0e0;stroke-width:1.4}.gantt .grid-row{fill:#fff}.gantt .grid-row:nth-child(even){fill:#f5f5f5}.gantt .row-line{stroke:#ebeff2}.gantt .tick{stroke:#e0e0e0;stroke-width:.2}.gantt .tick.thick{stroke-width:.4}.gantt .today-highlight{fill:#fcf8e3;opacity:.5}.gantt .arrow{fill:none;stroke:#666;stroke-width:1.4}.gantt .bar{fill:#b8c2cc;stroke:#8d99a6;stroke-width:0;transition:stroke-width .3s ease;user-select:none}.gantt .bar-progress{fill:#a3a3ff}.gantt .bar-invalid{fill:rgba(0,0,0,0);stroke:#8d99a6;stroke-width:1;stroke-dasharray:5}.gantt .bar-invalid~.bar-label{fill:#555}.gantt .bar-label{fill:#fff;dominant-baseline:central;text-anchor:middle;font-size:12px;font-weight:lighter}.gantt .bar-label.big{fill:#555;text-anchor:start}.gantt .handle{fill:#ddd;cursor:ew-resize;opacity:0;visibility:hidden;transition:opacity .3s ease}.gantt .bar-wrapper{cursor:pointer;outline:none}.gantt .bar-wrapper:hover .bar{fill:#a9b5c1}.gantt .bar-wrapper:hover .bar-progress{fill:#8a8aff}.gantt .bar-wrapper:hover .handle{visibility:visible;opacity:1}.gantt .bar-wrapper.active .bar{fill:#a9b5c1}.gantt .bar-wrapper.active .bar-progress{fill:#8a8aff}.gantt .lower-text,.gantt .upper-text{font-size:12px;text-anchor:middle}.gantt .upper-text{fill:#555}.gantt .lower-text{fill:#333}.gantt .hide{display:none}.gantt-container{position:relative;overflow:auto;font-size:12px}.gantt-container .popup-wrapper{position:absolute;top:0;left:0;background:rgba(0,0,0,.8);padding:0;color:#959da5;border-radius:3px}.gantt-container .popup-wrapper .title{border-bottom:3px solid #a3a3ff;padding:10px}.gantt-container .popup-wrapper .subtitle{padding:10px;color:#dfe2e5}.gantt-container .popup-wrapper .pointer{position:absolute;height:5px;margin:0 0 0 -5px;border:5px solid rgba(0,0,0,0);border-top-color:rgba(0,0,0,.8)} .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{user-select:none;-webkit-user-select:none}.gantt .grid-background{fill:none}.gantt .grid-header{fill:#fff;stroke:#e0e0e0;stroke-width:1.4}.gantt .grid-row{fill:#fff}.gantt .grid-row:nth-child(even){fill:#f5f5f5}.gantt .row-line{stroke:#ebeff2}.gantt .tick{stroke:#e0e0e0;stroke-width:.2}.gantt .tick.thick{stroke-width:.4}.gantt .today-highlight{fill:#fcf8e3;opacity:.5}.gantt .week-highlight{fill:#fcf8e3;opacity:.5}.gantt .month-highlight{fill:#fcf8e3;opacity:.5}.gantt .year-highlight{fill:#fcf8e3;opacity:.5}.gantt .arrow{fill:none;stroke:#666;stroke-width:1.4}.gantt .bar{fill:#b8c2cc;stroke:#8d99a6;stroke-width:0;transition:stroke-width .3s ease}.gantt .bar-progress{fill:#acacfa}.gantt .bar-expected-progress{fill:#c4c4e9}.gantt .bar-invalid{fill:rgba(0,0,0,0);stroke:#8d99a6;stroke-width:1;stroke-dasharray:5}.gantt .bar-invalid~.bar-label{fill:#555}.gantt .bar-label{fill:#fff;dominant-baseline:central;text-anchor:middle;font-size:12px;font-weight:lighter}.gantt .bar-label.big{fill:#555;text-anchor:start}.gantt .handle{fill:#ddd;cursor:ew-resize;opacity:0;visibility:hidden;transition:opacity .3s ease}.gantt .bar-wrapper{cursor:pointer;outline:none}.gantt .bar-wrapper:hover .bar{fill:#a9b5c1}.gantt .bar-wrapper:hover .bar-progress{fill:#9494f9}.gantt .bar-wrapper:hover .handle{visibility:visible;opacity:1}.gantt .bar-wrapper.active .bar{fill:#a9b5c1}.gantt .bar-wrapper.active .bar-progress{fill:#9494f9}.gantt .lower-text,.gantt .upper-text{font-size:12px;text-anchor:middle}.gantt .upper-text{fill:#555}.gantt .lower-text{fill:#333}.gantt .hide{display:none}.gantt-container{position:relative;overflow:auto;font-size:12px}.gantt-container .popup-wrapper{position:absolute;top:0;left:0;background:rgba(0,0,0,.8);padding:0;color:#959da5;border-radius:3px}.gantt-container .popup-wrapper .title{border-bottom:3px solid #acacfa;padding:10px}.gantt-container .popup-wrapper .subtitle{padding:10px;color:#dfe2e5}.gantt-container .popup-wrapper .pointer{position:absolute;height:5px;margin:0 0 0 -5px;border:5px solid rgba(0,0,0,0);border-top-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

View File

@ -1,111 +1,112 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>Simple Gantt</title> <title>Simple Gantt</title>
<style> <style>
body { body {
font-family: sans-serif; font-family: sans-serif;
background: #ccc; background: #ccc;
} }
.container { .container {
width: 80%; width: 80%;
margin: 0 auto; margin: 0 auto;
} }
/* custom class */ /* custom class */
.gantt .bar-milestone .bar { .gantt .bar-milestone .bar {
fill: tomato; fill: tomato;
} }
.heading { .heading {
text-align: center; text-align: center;
} }
.gantt-target.dark { .gantt-target.dark {
background-color: #252525; background-color: #252525;
} }
</style> </style>
<link rel="stylesheet" href="dist/frappe-gantt.css" /> <link rel="stylesheet" href="dist/frappe-gantt.css" />
<script src="dist/frappe-gantt.js"></script> <script src="dist/frappe-gantt.js"></script>
</head> </head>
<body> <body>
<div class="container"> <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 class="gantt-target"></div>
</div> </div>
<script> <script>
var tasks = [ var tasks = [
{ {
start: '2018-10-01', start: '2018-10-01',
end: '2018-10-08', end: '2018-10-08',
name: 'Redesign website', name: 'Redesign website',
id: "Task 0", id: "Task 0",
progress: 20 progress: 100
}, },
{ // {
start: '2018-10-03', // start: '2018-10-03',
duration: '1m 4d', // duration: '1m 4d',
name: 'Write new content', // name: 'Write new content',
id: "Task 1", // id: "Task 1",
progress: 5, // progress: 5,
dependencies: 'Task 0' // dependencies: 'Task 0'
}, // },
{ // {
start: '2018-10-04', // start: '2018-10-04',
end: '2018-10-08', // end: '2018-10-08',
name: 'Apply new styles', // name: 'Apply new styles',
id: "Task 2", // id: "Task 2",
progress: 10, // progress: 10,
dependencies: 'Task 1' // dependencies: 'Task 1'
}, // },
{ // {
start: '2018-10-08', // start: '2018-10-08',
end: '2018-10-09', // end: '2018-10-09',
name: 'Review', // name: 'Review',
id: "Task 3", // id: "Task 3",
progress: 5, // progress: 5,
dependencies: 'Task 2' // dependencies: 'Task 2'
}, // },
{ // {
start: '2018-10-08', // start: '2018-10-08',
end: '2018-10-10', // end: '2018-10-10',
name: 'Deploy', // name: 'Deploy',
id: "Task 4", // id: "Task 4",
progress: 0, // progress: 0,
dependencies: 'Task 2' // dependencies: 'Task 2'
}, // },
{ // {
start: '2018-10-11', // start: '2018-10-11',
end: '2018-10-11', // end: '2018-10-11',
name: 'Go Live!', // name: 'Go Live!',
id: "Task 5", // id: "Task 5",
progress: 0, // progress: 0,
dependencies: 'Task 4', // dependencies: 'Task 4',
custom_class: 'bar-milestone' // custom_class: 'bar-milestone'
}, // },
{ // {
start: '2014-01-05', // start: '2014-01-05',
end: '2019-10-12', // end: '2019-10-12',
name: 'Long term task', // name: 'Long term task',
id: "Task 6", // id: "Task 6",
progress: 0 // progress: 0
} // }
] ];
var gantt_chart = new Gantt(".gantt-target", tasks, { var gantt_chart = new Gantt(".gantt-target", tasks, {
on_click: task => { on_click: (task) => {
console.log(task); console.log(task);
}, },
on_date_change: (task, start, end) => { on_date_change: (task, start, end) => {
console.log(task, start, end); console.log(task, start, end);
}, },
on_progress_change: (task, progress) => { on_progress_change: (task, progress) => {
console.log(task, progress); console.log(task, progress);
}, },
on_view_change: (mode) => { on_view_change: (mode) => {
console.log(mode); console.log(mode);
}, },
view_mode: 'Month', view_mode: "Half Day",
language: 'en' view_mode_padding: { DAY: "1d" },
}); language: "en",
console.log(gantt_chart); });
</script> console.log(gantt_chart);
</body> </script>
</html> </body>
</html>

View File

@ -41,7 +41,7 @@
"eslint-config-prettier": "^2.9.0", "eslint-config-prettier": "^2.9.0",
"eslint-plugin-prettier": "^2.6.0", "eslint-plugin-prettier": "^2.6.0",
"jest": "^22.2.1", "jest": "^22.2.1",
"prettier": "^2.6.2", "prettier": "3.2.5",
"rollup": "^2.70.2", "rollup": "^2.70.2",
"rollup-plugin-sass": "^1.2.12", "rollup-plugin-sass": "^1.2.12",
"rollup-plugin-terser": "^7.0.2" "rollup-plugin-terser": "^7.0.2"

View File

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

View File

@ -1,52 +1,52 @@
import { createSVG } from './svg_utils'; import { createSVG } from "./svg_utils";
export default class Arrow { export default class Arrow {
constructor(gantt, from_task, to_task) { constructor(gantt, from_task, to_task) {
this.gantt = gantt; this.gantt = gantt;
this.from_task = from_task; this.from_task = from_task;
this.to_task = to_task; this.to_task = to_task;
this.calculate_path(); this.calculate_path();
this.draw(); this.draw();
}
calculate_path() {
let start_x =
this.from_task.$bar.getX() + this.from_task.$bar.getWidth() / 2;
const condition = () =>
this.to_task.$bar.getX() < start_x + this.gantt.options.padding &&
start_x > this.from_task.$bar.getX() + this.gantt.options.padding;
while (condition()) {
start_x -= 10;
} }
calculate_path() { const start_y =
let start_x = this.gantt.options.header_height +
this.from_task.$bar.getX() + this.from_task.$bar.getWidth() / 2; this.gantt.options.bar_height +
(this.gantt.options.padding + this.gantt.options.bar_height) *
this.from_task.task._index +
this.gantt.options.padding;
const condition = () => const end_x = this.to_task.$bar.getX() - this.gantt.options.padding / 2;
this.to_task.$bar.getX() < start_x + this.gantt.options.padding && const end_y =
start_x > this.from_task.$bar.getX() + this.gantt.options.padding; this.gantt.options.header_height +
this.gantt.options.bar_height / 2 +
(this.gantt.options.padding + this.gantt.options.bar_height) *
this.to_task.task._index +
this.gantt.options.padding;
while (condition()) { const from_is_below_to =
start_x -= 10; this.from_task.task._index > this.to_task.task._index;
} const curve = this.gantt.options.arrow_curve;
const clockwise = from_is_below_to ? 1 : 0;
const curve_y = from_is_below_to ? -curve : curve;
const offset = from_is_below_to
? end_y + this.gantt.options.arrow_curve
: end_y - this.gantt.options.arrow_curve;
const start_y = this.path = `
this.gantt.options.header_height +
this.gantt.options.bar_height +
(this.gantt.options.padding + this.gantt.options.bar_height) *
this.from_task.task._index +
this.gantt.options.padding;
const end_x = this.to_task.$bar.getX() - this.gantt.options.padding / 2;
const end_y =
this.gantt.options.header_height +
this.gantt.options.bar_height / 2 +
(this.gantt.options.padding + this.gantt.options.bar_height) *
this.to_task.task._index +
this.gantt.options.padding;
const from_is_below_to =
this.from_task.task._index > this.to_task.task._index;
const curve = this.gantt.options.arrow_curve;
const clockwise = from_is_below_to ? 1 : 0;
const curve_y = from_is_below_to ? -curve : curve;
const offset = from_is_below_to
? end_y + this.gantt.options.arrow_curve
: end_y - this.gantt.options.arrow_curve;
this.path = `
M ${start_x} ${start_y} M ${start_x} ${start_y}
V ${offset} V ${offset}
a ${curve} ${curve} 0 0 ${clockwise} ${curve} ${curve_y} a ${curve} ${curve} 0 0 ${clockwise} ${curve} ${curve_y}
@ -55,18 +55,16 @@ export default class Arrow {
l 5 5 l 5 5
l -5 5`; l -5 5`;
if ( if (
this.to_task.$bar.getX() < this.to_task.$bar.getX() <
this.from_task.$bar.getX() + this.gantt.options.padding this.from_task.$bar.getX() + this.gantt.options.padding
) { ) {
const down_1 = this.gantt.options.padding / 2 - curve; const down_1 = this.gantt.options.padding / 2 - curve;
const down_2 = const down_2 =
this.to_task.$bar.getY() + this.to_task.$bar.getY() + this.to_task.$bar.getHeight() / 2 - curve_y;
this.to_task.$bar.getHeight() / 2 - const left = this.to_task.$bar.getX() - this.gantt.options.padding;
curve_y;
const left = this.to_task.$bar.getX() - this.gantt.options.padding;
this.path = ` this.path = `
M ${start_x} ${start_y} M ${start_x} ${start_y}
v ${down_1} v ${down_1}
a ${curve} ${curve} 0 0 1 -${curve} ${curve} a ${curve} ${curve} 0 0 1 -${curve} ${curve}
@ -78,19 +76,19 @@ export default class Arrow {
m -5 -5 m -5 -5
l 5 5 l 5 5
l -5 5`; l -5 5`;
}
} }
}
draw() { draw() {
this.element = createSVG('path', { this.element = createSVG("path", {
d: this.path, d: this.path,
'data-from': this.from_task.task.id, "data-from": this.from_task.task.id,
'data-to': this.to_task.task.id, "data-to": this.to_task.task.id,
}); });
} }
update() { update() {
this.calculate_path(); this.calculate_path();
this.element.setAttribute('d', this.path); this.element.setAttribute("d", this.path);
} }
} }

View File

@ -1,471 +1,482 @@
import date_utils from './date_utils'; import date_utils from "./date_utils";
import { $, createSVG, animateSVG } from './svg_utils'; import { $, createSVG, animateSVG } from "./svg_utils";
export default class Bar { export default class Bar {
constructor(gantt, task) { constructor(gantt, task) {
this.set_defaults(gantt, task); this.set_defaults(gantt, task);
this.prepare(); this.prepare();
this.draw(); this.draw();
this.bind(); this.bind();
}
set_defaults(gantt, task) {
this.action_completed = false;
this.gantt = gantt;
this.task = task;
}
prepare() {
this.prepare_values();
this.prepare_helpers();
}
prepare_values() {
this.invalid = this.task.invalid;
this.height = this.gantt.options.bar_height;
this.compute_x();
this.compute_y();
this.compute_duration();
this.corner_radius = this.gantt.options.bar_corner_radius;
this.width = this.gantt.options.column_width * this.duration;
this.progress_width =
this.gantt.options.column_width *
this.duration *
(this.task.progress / 100) || 0;
this.group = createSVG("g", {
class: "bar-wrapper " + (this.task.custom_class || ""),
"data-id": this.task.id,
});
this.bar_group = createSVG("g", {
class: "bar-group",
append_to: this.group,
});
this.handle_group = createSVG("g", {
class: "handle-group",
append_to: this.group,
});
}
prepare_helpers() {
SVGElement.prototype.getX = function () {
return +this.getAttribute("x");
};
SVGElement.prototype.getY = function () {
return +this.getAttribute("y");
};
SVGElement.prototype.getWidth = function () {
return +this.getAttribute("width");
};
SVGElement.prototype.getHeight = function () {
return +this.getAttribute("height");
};
SVGElement.prototype.getEndX = function () {
return this.getX() + this.getWidth();
};
}
prepare_expected_progress_values() {
this.compute_expected_progress();
this.expected_progress_width =
this.gantt.options.column_width *
this.duration *
(this.expected_progress / 100) || 0;
}
draw() {
this.draw_bar();
if (this.gantt.options.show_expected_progress) {
this.prepare_expected_progress_values();
this.draw_expected_progress_bar();
}
this.draw_progress_bar();
this.draw_label();
this.draw_resize_handles();
}
draw_bar() {
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",
append_to: this.bar_group,
});
animateSVG(this.$bar, "width", 0, this.width);
if (this.invalid) {
this.$bar.classList.add("bar-invalid");
}
}
draw_expected_progress_bar() {
if (this.invalid) return;
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",
append_to: this.bar_group,
});
animateSVG(
this.$expected_bar_progress,
"width",
0,
this.expected_progress_width,
);
}
draw_progress_bar() {
if (this.invalid) return;
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",
append_to: this.bar_group,
});
animateSVG(this.$bar_progress, "width", 0, this.progress_width);
}
draw_label() {
createSVG("text", {
x: this.x + this.width / 2,
y: this.y + this.height / 2,
innerHTML: this.task.name,
class: "bar-label",
append_to: this.bar_group,
});
// labels get BBox in the next tick
requestAnimationFrame(() => this.update_label_position());
}
draw_resize_handles() {
if (this.invalid) return;
const bar = this.$bar;
const handle_width = 8;
createSVG("rect", {
x: bar.getX() + bar.getWidth() - 9,
y: bar.getY() + 1,
width: handle_width,
height: this.height - 2,
rx: this.corner_radius,
ry: this.corner_radius,
class: "handle right",
append_to: this.handle_group,
});
createSVG("rect", {
x: bar.getX() + 1,
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",
append_to: this.handle_group,
});
}
get_progress_polygon_points() {
const bar_progress = this.$bar_progress;
return [
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,
];
}
bind() {
if (this.invalid) return;
this.setup_click_event();
}
setup_click_event() {
$.on(this.group, "focus " + this.gantt.options.popup_trigger, (e) => {
if (this.action_completed) {
// just finished a move action, wait for a few seconds
return;
}
this.show_popup();
this.gantt.unselect_all();
this.group.classList.add("active");
});
$.on(this.group, "dblclick", (e) => {
if (this.action_completed) {
// just finished a move action, wait for a few seconds
return;
}
this.gantt.trigger_event("click", [this.task]);
});
}
show_popup() {
if (this.gantt.bar_being_dragged) return;
const start_date = date_utils.format(
this.task._start,
"MMM D",
this.gantt.options.language,
);
const end_date = date_utils.format(
date_utils.add(this.task._end, -1, "second"),
"MMM D",
this.gantt.options.language,
);
const subtitle = start_date + " - " + end_date;
this.gantt.show_popup({
target_element: this.$bar,
title: this.task.name,
subtitle: subtitle,
task: this.task,
});
}
update_bar_position({ x = null, width = null }) {
const bar = this.$bar;
if (x) {
// get all x values of parent task
const xs = this.task.dependencies.map((dep) => {
return this.gantt.get_bar(dep).$bar.getX();
});
// child task must not go before parent
const valid_x = xs.reduce((prev, curr) => {
return x >= curr;
}, x);
if (!valid_x) {
width = null;
return;
}
this.update_attr(bar, "x", x);
}
if (width) {
this.update_attr(bar, "width", width);
}
this.update_label_position();
this.update_handle_position();
if (this.gantt.options.show_expected_progress) {
this.date_changed();
this.compute_duration();
this.update_expected_progressbar_position();
}
this.update_progressbar_position();
this.update_arrow_position();
}
date_changed() {
let changed = false;
const { new_start_date, new_end_date } = this.compute_start_end_date();
if (Number(this.task._start) !== Number(new_start_date)) {
changed = true;
this.task._start = new_start_date;
} }
set_defaults(gantt, task) { if (Number(this.task._end) !== Number(new_end_date)) {
this.action_completed = false; changed = true;
this.gantt = gantt; this.task._end = new_end_date;
this.task = task;
} }
prepare() { if (!changed) return;
this.prepare_values();
this.prepare_helpers(); this.gantt.trigger_event("date_change", [
this.task,
new_start_date,
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]);
}
set_action_completed() {
this.action_completed = true;
setTimeout(() => (this.action_completed = false), 1000);
}
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(
this.gantt.gantt_start,
x_in_units * this.gantt.options.step,
"hour",
);
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",
);
return { new_start_date, new_end_date };
}
compute_progress() {
const progress =
(this.$bar_progress.getWidth() / this.$bar.getWidth()) * 100;
return parseInt(progress, 10);
}
compute_expected_progress() {
this.expected_progress =
date_utils.diff(date_utils.today(), this.task._start, "hour") /
this.gantt.options.step;
this.expected_progress =
((this.expected_progress < this.duration
? this.expected_progress
: this.duration) *
100) /
this.duration;
}
compute_x() {
const { step, column_width } = this.gantt.options;
const task_start = this.task._start;
const gantt_start = this.gantt.gantt_start;
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");
x = (diff * column_width) / 30;
} }
this.x = x;
}
prepare_values() { compute_y() {
this.invalid = this.task.invalid; this.y =
this.height = this.gantt.options.bar_height; this.gantt.options.header_height +
this.compute_x(); this.gantt.options.padding +
this.compute_y(); this.task._index * (this.height + this.gantt.options.padding);
this.compute_duration(); }
this.corner_radius = this.gantt.options.bar_corner_radius;
this.width = this.gantt.options.column_width * this.duration; compute_duration() {
this.progress_width = this.duration =
this.gantt.options.column_width * date_utils.diff(this.task._end, this.task._start, "hour") /
this.duration * this.gantt.options.step;
(this.task.progress / 100) || 0; }
this.group = createSVG('g', {
class: 'bar-wrapper ' + (this.task.custom_class || ''), get_snap_position(dx) {
'data-id': this.task.id, let odx = dx,
}); rem,
this.bar_group = createSVG('g', { position;
class: 'bar-group',
append_to: this.group, if (this.gantt.view_is("Week")) {
}); rem = dx % (this.gantt.options.column_width / 7);
this.handle_group = createSVG('g', { position =
class: 'handle-group', odx -
append_to: this.group, rem +
}); (rem < this.gantt.options.column_width / 14
? 0
: this.gantt.options.column_width / 7);
} else if (this.gantt.view_is("Month")) {
rem = dx % (this.gantt.options.column_width / 30);
position =
odx -
rem +
(rem < this.gantt.options.column_width / 60
? 0
: this.gantt.options.column_width / 30);
} else {
rem = dx % this.gantt.options.column_width;
position =
odx -
rem +
(rem < this.gantt.options.column_width / 2
? 0
: this.gantt.options.column_width);
} }
return position;
}
prepare_helpers() { update_attr(element, attr, value) {
SVGElement.prototype.getX = function () { value = +value;
return +this.getAttribute('x'); if (!isNaN(value)) {
}; element.setAttribute(attr, value);
SVGElement.prototype.getY = function () {
return +this.getAttribute('y');
};
SVGElement.prototype.getWidth = function () {
return +this.getAttribute('width');
};
SVGElement.prototype.getHeight = function () {
return +this.getAttribute('height');
};
SVGElement.prototype.getEndX = function () {
return this.getX() + this.getWidth();
};
} }
return element;
}
prepare_expected_progress_values() { update_expected_progressbar_position() {
this.compute_expected_progress(); if (this.invalid) return;
this.expected_progress_width = this.$expected_bar_progress.setAttribute("x", this.$bar.getX());
this.gantt.options.column_width * this.compute_expected_progress();
this.duration * this.$expected_bar_progress.setAttribute(
(this.expected_progress / 100) || 0; "width",
this.gantt.options.column_width *
this.duration *
(this.expected_progress / 100) || 0,
);
}
update_progressbar_position() {
if (this.invalid) return;
this.$bar_progress.setAttribute("x", this.$bar.getX());
this.$bar_progress.setAttribute(
"width",
this.$bar.getWidth() * (this.task.progress / 100),
);
}
update_label_position() {
const bar = this.$bar,
label = this.group.querySelector(".bar-label");
if (label.getBBox().width > bar.getWidth()) {
label.classList.add("big");
label.setAttribute("x", bar.getX() + bar.getWidth() + 5);
} else {
label.classList.remove("big");
label.setAttribute("x", bar.getX() + bar.getWidth() / 2);
} }
}
draw() { update_handle_position() {
this.draw_bar(); if (this.invalid) return;
if (this.gantt.options.show_expected_progress) { const bar = this.$bar;
this.prepare_expected_progress_values(); this.handle_group
this.draw_expected_progress_bar(); .querySelector(".handle.left")
} .setAttribute("x", bar.getX() + 1);
this.draw_progress_bar(); this.handle_group
this.draw_label(); .querySelector(".handle.right")
this.draw_resize_handles(); .setAttribute("x", bar.getEndX() - 9);
} const handle = this.group.querySelector(".handle.progress");
handle && handle.setAttribute("points", this.get_progress_polygon_points());
draw_bar() { }
this.$bar = createSVG('rect', {
x: this.x, update_arrow_position() {
y: this.y, this.arrows = this.arrows || [];
width: this.width, for (let arrow of this.arrows) {
height: this.height, arrow.update();
rx: this.corner_radius,
ry: this.corner_radius,
class: 'bar',
append_to: this.bar_group,
});
animateSVG(this.$bar, 'width', 0, this.width);
if (this.invalid) {
this.$bar.classList.add('bar-invalid');
}
}
draw_expected_progress_bar() {
if (this.invalid) return;
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',
append_to: this.bar_group,
});
animateSVG(this.$expected_bar_progress, 'width', 0, this.expected_progress_width);
}
draw_progress_bar() {
if (this.invalid) return;
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',
append_to: this.bar_group,
});
animateSVG(this.$bar_progress, 'width', 0, this.progress_width);
}
draw_label() {
createSVG('text', {
x: this.x + this.width / 2,
y: this.y + this.height / 2,
innerHTML: this.task.name,
class: 'bar-label',
append_to: this.bar_group,
});
// labels get BBox in the next tick
requestAnimationFrame(() => this.update_label_position());
}
draw_resize_handles() {
if (this.invalid) return;
const bar = this.$bar;
const handle_width = 8;
createSVG('rect', {
x: bar.getX() + bar.getWidth() - 9,
y: bar.getY() + 1,
width: handle_width,
height: this.height - 2,
rx: this.corner_radius,
ry: this.corner_radius,
class: 'handle right',
append_to: this.handle_group,
});
createSVG('rect', {
x: bar.getX() + 1,
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,
});
if (this.task.progress < 100) {
this.$handle_progress = createSVG('polygon', {
points: this.get_progress_polygon_points().join(','),
class: 'handle progress',
append_to: this.handle_group,
});
}
}
get_progress_polygon_points() {
const bar_progress = this.$bar_progress;
return [
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,
];
}
bind() {
if (this.invalid) return;
this.setup_click_event();
}
setup_click_event() {
$.on(this.group, 'focus ' + this.gantt.options.popup_trigger, (e) => {
if (this.action_completed) {
// just finished a move action, wait for a few seconds
return;
}
this.show_popup();
this.gantt.unselect_all();
this.group.classList.add('active');
});
$.on(this.group, 'dblclick', (e) => {
if (this.action_completed) {
// just finished a move action, wait for a few seconds
return;
}
this.gantt.trigger_event('click', [this.task]);
});
}
show_popup() {
if (this.gantt.bar_being_dragged) return;
const start_date = date_utils.format(
this.task._start,
'MMM D',
this.gantt.options.language
);
const end_date = date_utils.format(
date_utils.add(this.task._end, -1, 'second'),
'MMM D',
this.gantt.options.language
);
const subtitle = start_date + ' - ' + end_date;
this.gantt.show_popup({
target_element: this.$bar,
title: this.task.name,
subtitle: subtitle,
task: this.task,
});
}
update_bar_position({ x = null, width = null }) {
const bar = this.$bar;
if (x) {
// get all x values of parent task
const xs = this.task.dependencies.map((dep) => {
return this.gantt.get_bar(dep).$bar.getX();
});
// child task must not go before parent
const valid_x = xs.reduce((prev, curr) => {
return x >= curr;
}, x);
if (!valid_x) {
width = null;
return;
}
this.update_attr(bar, 'x', x);
}
if (width) {
this.update_attr(bar, 'width', width);
}
this.update_label_position();
this.update_handle_position();
if (this.gantt.options.show_expected_progress){
this.date_changed();
this.compute_duration();
this.update_expected_progressbar_position();
}
this.update_progressbar_position();
this.update_arrow_position();
}
date_changed() {
let changed = false;
const { new_start_date, new_end_date } = this.compute_start_end_date();
if (Number(this.task._start) !== Number(new_start_date)) {
changed = true;
this.task._start = new_start_date;
}
if (Number(this.task._end) !== Number(new_end_date)) {
changed = true;
this.task._end = new_end_date;
}
if (!changed) return;
this.gantt.trigger_event('date_change', [
this.task,
new_start_date,
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]);
}
set_action_completed() {
this.action_completed = true;
setTimeout(() => (this.action_completed = false), 1000);
}
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(
this.gantt.gantt_start,
x_in_units * this.gantt.options.step,
'hour'
);
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'
);
return { new_start_date, new_end_date };
}
compute_progress() {
const progress =
(this.$bar_progress.getWidth() / this.$bar.getWidth()) * 100;
return parseInt(progress, 10);
}
compute_expected_progress() {
this.expected_progress = date_utils.diff(date_utils.today(), this.task._start, 'hour') / this.gantt.options.step;
this.expected_progress = ((this.expected_progress < this.duration) ? this.expected_progress : this.duration) * 100 / this.duration;
}
compute_x() {
const { step, column_width } = this.gantt.options;
const task_start = this.task._start;
const gantt_start = this.gantt.gantt_start;
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');
x = (diff * column_width) / 30;
}
this.x = x;
}
compute_y() {
this.y = (
this.gantt.options.header_height +
this.gantt.options.padding +
this.task._index * (this.height + this.gantt.options.padding)
);
}
compute_duration() {
this.duration = date_utils.diff(this.task._end, this.task._start, 'hour') /
this.gantt.options.step;
}
get_snap_position(dx) {
let odx = dx,
rem,
position;
if (this.gantt.view_is('Week')) {
rem = dx % (this.gantt.options.column_width / 7);
position =
odx -
rem +
(rem < this.gantt.options.column_width / 14
? 0
: this.gantt.options.column_width / 7);
} else if (this.gantt.view_is('Month')) {
rem = dx % (this.gantt.options.column_width / 30);
position =
odx -
rem +
(rem < this.gantt.options.column_width / 60
? 0
: this.gantt.options.column_width / 30);
} else {
rem = dx % this.gantt.options.column_width;
position =
odx -
rem +
(rem < this.gantt.options.column_width / 2
? 0
: this.gantt.options.column_width);
}
return position;
}
update_attr(element, attr, value) {
value = +value;
if (!isNaN(value)) {
element.setAttribute(attr, value);
}
return element;
}
update_expected_progressbar_position() {
if (this.invalid) return;
this.$expected_bar_progress.setAttribute('x', this.$bar.getX());
this.compute_expected_progress();
this.$expected_bar_progress.setAttribute(
'width',
this.gantt.options.column_width * this.duration * (this.expected_progress / 100) || 0
);
}
update_progressbar_position() {
if (this.invalid) return;
this.$bar_progress.setAttribute('x', this.$bar.getX());
this.$bar_progress.setAttribute(
'width',
this.$bar.getWidth() * (this.task.progress / 100)
);
}
update_label_position() {
const bar = this.$bar,
label = this.group.querySelector('.bar-label');
if (label.getBBox().width > bar.getWidth()) {
label.classList.add('big');
label.setAttribute('x', bar.getX() + bar.getWidth() + 5);
} else {
label.classList.remove('big');
label.setAttribute('x', bar.getX() + bar.getWidth() / 2);
}
}
update_handle_position() {
if (this.invalid) return;
const bar = this.$bar;
this.handle_group
.querySelector('.handle.left')
.setAttribute('x', bar.getX() + 1);
this.handle_group
.querySelector('.handle.right')
.setAttribute('x', bar.getEndX() - 9);
const handle = this.group.querySelector('.handle.progress');
handle &&
handle.setAttribute('points', this.get_progress_polygon_points());
}
update_arrow_position() {
this.arrows = this.arrows || [];
for (let arrow of this.arrows) {
arrow.update();
}
} }
}
} }
function isFunction(functionToCheck) { function isFunction(functionToCheck) {
var getType = {}; var getType = {};
return ( return (
functionToCheck && functionToCheck &&
getType.toString.call(functionToCheck) === '[object Function]' getType.toString.call(functionToCheck) === "[object Function]"
); );
} }

View File

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

View File

@ -1,253 +1,253 @@
const YEAR = 'year'; const YEAR = "year";
const MONTH = 'month'; const MONTH = "month";
const DAY = 'day'; const DAY = "day";
const HOUR = 'hour'; const HOUR = "hour";
const MINUTE = 'minute'; const MINUTE = "minute";
const SECOND = 'second'; const SECOND = "second";
const MILLISECOND = 'millisecond'; const MILLISECOND = "millisecond";
export default { export default {
parse_duration(duration) { 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); const matches = regex.exec(duration);
if (matches !== null) { if (matches !== null) {
if (matches[2] === "y") { if (matches[2] === "y") {
return { duration: parseInt(matches[1]), scale: `year` }; return { duration: parseInt(matches[1]), scale: `year` };
} else if (matches[2] === "m") { } else if (matches[2] === "m") {
return { duration: parseInt(matches[1]), scale: `month` }; return { duration: parseInt(matches[1]), scale: `month` };
} else if (matches[2] === "d") { } else if (matches[2] === "d") {
return { duration: parseInt(matches[1]), scale: `day` }; return { duration: parseInt(matches[1]), scale: `day` };
} else if (matches[2] === "h") { } else if (matches[2] === "h") {
return { duration: parseInt(matches[1]), scale: `hour` }; return { duration: parseInt(matches[1]), scale: `hour` };
} else if (matches[2] === "min") { } else if (matches[2] === "min") {
return { duration: parseInt(matches[1]), scale: `minute` }; return { duration: parseInt(matches[1]), scale: `minute` };
} else if (matches[2] === "s") { } else if (matches[2] === "s") {
return { duration: parseInt(matches[1]), scale: `second` }; return { duration: parseInt(matches[1]), scale: `second` };
} else if (matches[2] === "ms") { } else if (matches[2] === "ms") {
return { duration: parseInt(matches[1]), scale: `millisecond` }; return { duration: parseInt(matches[1]), scale: `millisecond` };
} }
}
},
parse(date, date_separator = "-", time_separator = /[.:]/) {
if (date instanceof Date) {
return date;
}
if (typeof date === "string") {
let date_parts, time_parts;
const parts = date.split(" ");
date_parts = parts[0]
.split(date_separator)
.map((val) => parseInt(val, 10));
time_parts = parts[1] && parts[1].split(time_separator);
// month is 0 indexed
date_parts[1] = date_parts[1] - 1;
let vals = date_parts;
if (time_parts && time_parts.length) {
if (time_parts.length == 4) {
time_parts[3] = "0." + time_parts[3];
time_parts[3] = parseFloat(time_parts[3]) * 1000;
} }
}, vals = vals.concat(time_parts);
parse(date, date_separator = '-', time_separator = /[.:]/) { }
if (date instanceof Date) {
return date; return new Date(...vals);
}
},
to_string(date, with_time = false) {
if (!(date instanceof Date)) {
throw new TypeError("Invalid argument type");
}
const vals = this.get_date_values(date).map((val, i) => {
if (i === 1) {
// add 1 for month
val = val + 1;
}
if (i === 6) {
return padStart(val + "", 3, "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 : "");
},
format(date, format_string = "YYYY-MM-DD HH:mm:ss.SSS", lang = "en") {
const dateTimeFormat = new Intl.DateTimeFormat(lang, {
month: "long",
});
const month_name = dateTimeFormat.format(date);
const month_name_capitalized =
month_name.charAt(0).toUpperCase() + month_name.slice(1);
const values = this.get_date_values(date).map((d) => padStart(d, 2, 0));
const format_map = {
YYYY: values[0],
MM: padStart(+values[1] + 1, 2, 0),
DD: values[2],
HH: values[3],
mm: values[4],
ss: values[5],
SSS: values[6],
D: values[2],
MMMM: month_name_capitalized,
MMM: month_name_capitalized,
};
let str = format_string;
const formatted_values = [];
Object.keys(format_map)
.sort((a, b) => b.length - a.length) // big string first
.forEach((key) => {
if (str.includes(key)) {
str = str.replace(key, `$${formatted_values.length}`);
formatted_values.push(format_map[key]);
} }
if (typeof date === 'string') { });
let date_parts, time_parts;
const parts = date.split(' ');
date_parts = parts[0] formatted_values.forEach((value, i) => {
.split(date_separator) str = str.replace(`$${i}`, value);
.map((val) => parseInt(val, 10)); });
time_parts = parts[1] && parts[1].split(time_separator);
// month is 0 indexed return str;
date_parts[1] = date_parts[1] - 1; },
let vals = date_parts; diff(date_a, date_b, scale = DAY) {
let milliseconds, seconds, hours, minutes, days, months, years;
if (time_parts && time_parts.length) { milliseconds = date_a - date_b;
if (time_parts.length == 4) { seconds = milliseconds / 1000;
time_parts[3] = '0.' + time_parts[3]; minutes = seconds / 60;
time_parts[3] = parseFloat(time_parts[3]) * 1000; hours = minutes / 60;
} days = hours / 24;
vals = vals.concat(time_parts); months = days / 30;
} years = months / 12;
return new Date(...vals); if (!scale.endsWith("s")) {
} scale += "s";
}, }
to_string(date, with_time = false) { return Math.floor(
if (!(date instanceof Date)) { {
throw new TypeError('Invalid argument type'); milliseconds,
} seconds,
const vals = this.get_date_values(date).map((val, i) => { minutes,
if (i === 1) { hours,
// add 1 for month days,
val = val + 1; months,
} years,
}[scale],
);
},
if (i === 6) { today() {
return padStart(val + '', 3, '0'); const vals = this.get_date_values(new Date()).slice(0, 3);
} return new Date(...vals);
},
return padStart(val + '', 2, '0'); now() {
}); return new Date();
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 : ''); add(date, qty, scale) {
}, qty = parseInt(qty, 10);
const vals = [
date.getFullYear() + (scale === YEAR ? qty : 0),
date.getMonth() + (scale === MONTH ? qty : 0),
date.getDate() + (scale === DAY ? qty : 0),
date.getHours() + (scale === HOUR ? qty : 0),
date.getMinutes() + (scale === MINUTE ? qty : 0),
date.getSeconds() + (scale === SECOND ? qty : 0),
date.getMilliseconds() + (scale === MILLISECOND ? qty : 0),
];
return new Date(...vals);
},
format(date, format_string = 'YYYY-MM-DD HH:mm:ss.SSS', lang = 'en') { start_of(date, scale) {
const dateTimeFormat = new Intl.DateTimeFormat(lang, { const scores = {
month: 'long' [YEAR]: 6,
}); [MONTH]: 5,
const month_name = dateTimeFormat.format(date); [DAY]: 4,
const month_name_capitalized = [HOUR]: 3,
month_name.charAt(0).toUpperCase() + month_name.slice(1); [MINUTE]: 2,
[SECOND]: 1,
[MILLISECOND]: 0,
};
const values = this.get_date_values(date).map(d => padStart(d, 2, 0)); function should_reset(_scale) {
const format_map = { const max_score = scores[scale];
YYYY: values[0], return scores[_scale] <= max_score;
MM: padStart(+values[1] + 1, 2, 0), }
DD: values[2],
HH: values[3],
mm: values[4],
ss: values[5],
SSS: values[6],
D: values[2],
MMMM: month_name_capitalized,
MMM: month_name_capitalized,
};
let str = format_string; const vals = [
const formatted_values = []; date.getFullYear(),
should_reset(YEAR) ? 0 : date.getMonth(),
should_reset(MONTH) ? 1 : date.getDate(),
should_reset(DAY) ? 0 : date.getHours(),
should_reset(HOUR) ? 0 : date.getMinutes(),
should_reset(MINUTE) ? 0 : date.getSeconds(),
should_reset(SECOND) ? 0 : date.getMilliseconds(),
];
Object.keys(format_map) return new Date(...vals);
.sort((a, b) => b.length - a.length) // big string first },
.forEach((key) => {
if (str.includes(key)) {
str = str.replace(key, `$${formatted_values.length}`);
formatted_values.push(format_map[key]);
}
});
formatted_values.forEach((value, i) => { clone(date) {
str = str.replace(`$${i}`, value); return new Date(...this.get_date_values(date));
}); },
return str; get_date_values(date) {
}, return [
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds(),
];
},
diff(date_a, date_b, scale = DAY) { get_days_in_month(date) {
let milliseconds, seconds, hours, minutes, days, months, years; const no_of_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
milliseconds = date_a - date_b; const month = date.getMonth();
seconds = milliseconds / 1000;
minutes = seconds / 60;
hours = minutes / 60;
days = hours / 24;
months = days / 30;
years = months / 12;
if (!scale.endsWith('s')) { if (month !== 1) {
scale += 's'; return no_of_days[month];
} }
return Math.floor( // Feb
{ const year = date.getFullYear();
milliseconds, if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
seconds, return 29;
minutes, }
hours, return 28;
days, },
months,
years,
}[scale]
);
},
today() {
const vals = this.get_date_values(new Date()).slice(0, 3);
return new Date(...vals);
},
now() {
return new Date();
},
add(date, qty, scale) {
qty = parseInt(qty, 10);
const vals = [
date.getFullYear() + (scale === YEAR ? qty : 0),
date.getMonth() + (scale === MONTH ? qty : 0),
date.getDate() + (scale === DAY ? qty : 0),
date.getHours() + (scale === HOUR ? qty : 0),
date.getMinutes() + (scale === MINUTE ? qty : 0),
date.getSeconds() + (scale === SECOND ? qty : 0),
date.getMilliseconds() + (scale === MILLISECOND ? qty : 0),
];
return new Date(...vals);
},
start_of(date, scale) {
const scores = {
[YEAR]: 6,
[MONTH]: 5,
[DAY]: 4,
[HOUR]: 3,
[MINUTE]: 2,
[SECOND]: 1,
[MILLISECOND]: 0,
};
function should_reset(_scale) {
const max_score = scores[scale];
return scores[_scale] <= max_score;
}
const vals = [
date.getFullYear(),
should_reset(YEAR) ? 0 : date.getMonth(),
should_reset(MONTH) ? 1 : date.getDate(),
should_reset(DAY) ? 0 : date.getHours(),
should_reset(HOUR) ? 0 : date.getMinutes(),
should_reset(MINUTE) ? 0 : date.getSeconds(),
should_reset(SECOND) ? 0 : date.getMilliseconds(),
];
return new Date(...vals);
},
clone(date) {
return new Date(...this.get_date_values(date));
},
get_date_values(date) {
return [
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds(),
];
},
get_days_in_month(date) {
const no_of_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const month = date.getMonth();
if (month !== 1) {
return no_of_days[month];
}
// Feb
const year = date.getFullYear();
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
return 29;
}
return 28;
},
}; };
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
function padStart(str, targetLength, padString) { function padStart(str, targetLength, padString) {
str = str + ''; str = str + "";
targetLength = targetLength >> 0; targetLength = targetLength >> 0;
padString = String(typeof padString !== 'undefined' ? padString : ' '); padString = String(typeof padString !== "undefined" ? padString : " ");
if (str.length > targetLength) { if (str.length > targetLength) {
return String(str); return String(str);
} else { } else {
targetLength = targetLength - str.length; targetLength = targetLength - str.length;
if (targetLength > padString.length) { if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length); padString += padString.repeat(targetLength / padString.length);
}
return padString.slice(0, targetLength) + String(str);
} }
return padString.slice(0, targetLength) + String(str);
}
} }

View File

@ -1,7 +1,7 @@
@import "./dark.scss"; @import "./dark.scss";
$bar-color: #b8c2cc !default; $bar-color: #b8c2cc !default;
$bar-stroke: #8D99A6 !default; $bar-stroke: #8d99a6 !default;
$border-color: #e0e0e0 !default; $border-color: #e0e0e0 !default;
$light-bg: #f5f5f5 !default; $light-bg: #f5f5f5 !default;
$light-border-color: #ebeff2 !default; $light-border-color: #ebeff2 !default;
@ -14,178 +14,179 @@ $handle-color: #ddd !default;
$light-blue: #c4c4e9 !default; $light-blue: #c4c4e9 !default;
.gantt { .gantt {
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
.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;
}
.week-highlight { .grid-background {
fill: $light-yellow; fill: none;
opacity: 0.5; }
} .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;
}
.month-highlight { .week-highlight {
fill: $light-yellow; fill: $light-yellow;
opacity: 0.5; opacity: 0.5;
} }
.year-highlight { .month-highlight {
fill: $light-yellow; fill: $light-yellow;
opacity: 0.5; opacity: 0.5;
} }
.arrow { .year-highlight {
fill: none; fill: $light-yellow;
stroke: $text-muted; opacity: 0.5;
stroke-width: 1.4; }
}
.bar { .arrow {
fill: $bar-color; fill: none;
stroke: $bar-stroke; stroke: $text-muted;
stroke-width: 0; stroke-width: 1.4;
transition: stroke-width .3s ease; }
}
.bar-progress {
fill: $blue;
}
.bar-expected-progress {
fill: $light-blue;
}
.bar-invalid {
fill: transparent;
stroke: $bar-stroke;
stroke-width: 1;
stroke-dasharray: 5;
&~.bar-label { .bar {
fill: $text-light; fill: $bar-color;
} stroke: $bar-stroke;
} stroke-width: 0;
.bar-label { transition: stroke-width 0.3s ease;
fill: #fff; }
dominant-baseline: central; .bar-progress {
text-anchor: middle; fill: $blue;
font-size: 12px; }
font-weight: lighter; .bar-expected-progress {
fill: $light-blue;
}
.bar-invalid {
fill: transparent;
stroke: $bar-stroke;
stroke-width: 1;
stroke-dasharray: 5;
&.big { & ~ .bar-label {
fill: $text-light; fill: $text-light;
text-anchor: start; }
} }
} .bar-label {
fill: #fff;
dominant-baseline: central;
text-anchor: middle;
font-size: 12px;
font-weight: lighter;
.handle { &.big {
fill: $handle-color; fill: $text-light;
cursor: ew-resize; text-anchor: start;
opacity: 0; }
visibility: hidden; }
transition: opacity .3s ease;
}
.bar-wrapper { .handle {
cursor: pointer; fill: $handle-color;
outline: none; cursor: ew-resize;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease;
}
&:hover { .bar-wrapper {
.bar { cursor: pointer;
fill: darken($bar-color, 5); outline: none;
}
.bar-progress { &:hover {
fill: darken($blue, 5); .bar {
} fill: darken($bar-color, 5);
}
.handle { .bar-progress {
visibility: visible; fill: darken($blue, 5);
opacity: 1; }
}
}
&.active { .handle {
.bar { visibility: visible;
fill: darken($bar-color, 5); opacity: 1;
} }
}
.bar-progress { &.active {
fill: darken($blue, 5); .bar {
} fill: darken($bar-color, 5);
} }
}
.lower-text, .upper-text { .bar-progress {
font-size: 12px; fill: darken($blue, 5);
text-anchor: middle; }
} }
.upper-text { }
fill: $text-light;
}
.lower-text {
fill: $text-color;
}
.hide { .lower-text,
display: none; .upper-text {
} font-size: 12px;
text-anchor: middle;
}
.upper-text {
fill: $text-light;
}
.lower-text {
fill: $text-color;
}
.hide {
display: none;
}
} }
.gantt-container { .gantt-container {
position: relative; position: relative;
overflow: auto; overflow: auto;
font-size: 12px; font-size: 12px;
.popup-wrapper { .popup-wrapper {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background: rgba(0, 0, 0, 0.8); background: rgba(0, 0, 0, 0.8);
padding: 0; padding: 0;
color: #959da5; color: #959da5;
border-radius: 3px; border-radius: 3px;
.title { .title {
border-bottom: 3px solid $blue; border-bottom: 3px solid $blue;
padding: 10px; padding: 10px;
} }
.subtitle { .subtitle {
padding: 10px; padding: 10px;
color: #dfe2e5; color: #dfe2e5;
} }
.pointer { .pointer {
position: absolute; position: absolute;
height: 5px; height: 5px;
margin: 0 0 0 -5px; margin: 0 0 0 -5px;
border: 5px solid transparent; border: 5px solid transparent;
border-top-color: rgba(0, 0, 0, 0.8); border-top-color: rgba(0, 0, 0, 0.8);
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +1,69 @@
export default class Popup { export default class Popup {
constructor(parent, custom_html) { constructor(parent, custom_html) {
this.parent = parent; this.parent = parent;
this.custom_html = custom_html; this.custom_html = custom_html;
this.make(); this.make();
} }
make() { make() {
this.parent.innerHTML = ` this.parent.innerHTML = `
<div class="title"></div> <div class="title"></div>
<div class="subtitle"></div> <div class="subtitle"></div>
<div class="pointer"></div> <div class="pointer"></div>
`; `;
this.hide(); this.hide();
this.title = this.parent.querySelector('.title'); this.title = this.parent.querySelector(".title");
this.subtitle = this.parent.querySelector('.subtitle'); this.subtitle = this.parent.querySelector(".subtitle");
this.pointer = this.parent.querySelector('.pointer'); this.pointer = this.parent.querySelector(".pointer");
}
show(options) {
if (!options.target_element) {
throw new Error("target_element is required to show popup");
}
if (!options.position) {
options.position = "left";
}
const target_element = options.target_element;
if (this.custom_html) {
let html = this.custom_html(options.task);
html += '<div class="pointer"></div>';
this.parent.innerHTML = html;
this.pointer = this.parent.querySelector(".pointer");
} else {
// set data
this.title.innerHTML = options.title;
this.subtitle.innerHTML = options.subtitle;
this.parent.style.width = this.parent.clientWidth + "px";
} }
show(options) { // set position
if (!options.target_element) { let position_meta;
throw new Error('target_element is required to show popup'); if (target_element instanceof HTMLElement) {
} position_meta = target_element.getBoundingClientRect();
if (!options.position) { } else if (target_element instanceof SVGElement) {
options.position = 'left'; position_meta = options.target_element.getBBox();
}
const target_element = options.target_element;
if (this.custom_html) {
let html = this.custom_html(options.task);
html += '<div class="pointer"></div>';
this.parent.innerHTML = html;
this.pointer = this.parent.querySelector('.pointer');
} else {
// set data
this.title.innerHTML = options.title;
this.subtitle.innerHTML = options.subtitle;
this.parent.style.width = this.parent.clientWidth + 'px';
}
// set position
let position_meta;
if (target_element instanceof HTMLElement) {
position_meta = target_element.getBoundingClientRect();
} else if (target_element instanceof SVGElement) {
position_meta = options.target_element.getBBox();
}
if (options.position === 'left') {
this.parent.style.left =
position_meta.x + (position_meta.width + 10) + 'px';
this.parent.style.top = position_meta.y + 'px';
this.pointer.style.transform = 'rotateZ(90deg)';
this.pointer.style.left = '-7px';
this.pointer.style.top = '2px';
}
// show
this.parent.style.opacity = 1;
} }
hide() { if (options.position === "left") {
this.parent.style.opacity = 0; this.parent.style.left =
this.parent.style.left = 0; position_meta.x + (position_meta.width + 10) + "px";
this.parent.style.top = position_meta.y + "px";
this.pointer.style.transform = "rotateZ(90deg)";
this.pointer.style.left = "-7px";
this.pointer.style.top = "2px";
} }
// show
this.parent.style.opacity = 1;
}
hide() {
this.parent.style.opacity = 0;
this.parent.style.left = 0;
}
} }

View File

@ -1,133 +1,133 @@
export function $(expr, con) { export function $(expr, con) {
return typeof expr === 'string' return typeof expr === "string"
? (con || document).querySelector(expr) ? (con || document).querySelector(expr)
: expr || null; : expr || null;
} }
export function createSVG(tag, attrs) { 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) { for (let attr in attrs) {
if (attr === 'append_to') { if (attr === "append_to") {
const parent = attrs.append_to; const parent = attrs.append_to;
parent.appendChild(elem); parent.appendChild(elem);
} else if (attr === 'innerHTML') { } else if (attr === "innerHTML") {
elem.innerHTML = attrs.innerHTML; elem.innerHTML = attrs.innerHTML;
} else { } else {
elem.setAttribute(attr, attrs[attr]); elem.setAttribute(attr, attrs[attr]);
}
} }
return elem; }
return elem;
} }
export function animateSVG(svgElement, attr, from, to) { export function animateSVG(svgElement, attr, from, to) {
const animatedSvgElement = getAnimationElement(svgElement, attr, from, to); const animatedSvgElement = getAnimationElement(svgElement, attr, from, to);
if (animatedSvgElement === svgElement) { if (animatedSvgElement === svgElement) {
// triggered 2nd time programmatically // triggered 2nd time programmatically
// trigger artificial click event // trigger artificial click event
const event = document.createEvent('HTMLEvents'); const event = document.createEvent("HTMLEvents");
event.initEvent('click', true, true); event.initEvent("click", true, true);
event.eventName = 'click'; event.eventName = "click";
animatedSvgElement.dispatchEvent(event); animatedSvgElement.dispatchEvent(event);
} }
} }
function getAnimationElement( function getAnimationElement(
svgElement, svgElement,
attr, attr,
from,
to,
dur = "0.4s",
begin = "0.1s",
) {
const animEl = svgElement.querySelector("animate");
if (animEl) {
$.attr(animEl, {
attributeName: attr,
from,
to,
dur,
begin: "click + " + begin, // artificial click
});
return svgElement;
}
const animateElement = createSVG("animate", {
attributeName: attr,
from, from,
to, to,
dur = '0.4s', dur,
begin = '0.1s' begin,
) { calcMode: "spline",
const animEl = svgElement.querySelector('animate'); values: from + ";" + to,
if (animEl) { keyTimes: "0; 1",
$.attr(animEl, { keySplines: cubic_bezier("ease-out"),
attributeName: attr, });
from, svgElement.appendChild(animateElement);
to,
dur,
begin: 'click + ' + begin, // artificial click
});
return svgElement;
}
const animateElement = createSVG('animate', { return svgElement;
attributeName: attr,
from,
to,
dur,
begin,
calcMode: 'spline',
values: from + ';' + to,
keyTimes: '0; 1',
keySplines: cubic_bezier('ease-out'),
});
svgElement.appendChild(animateElement);
return svgElement;
} }
function cubic_bezier(name) { function cubic_bezier(name) {
return { return {
ease: '.25 .1 .25 1', ease: ".25 .1 .25 1",
linear: '0 0 1 1', linear: "0 0 1 1",
'ease-in': '.42 0 1 1', "ease-in": ".42 0 1 1",
'ease-out': '0 0 .58 1', "ease-out": "0 0 .58 1",
'ease-in-out': '.42 0 .58 1', "ease-in-out": ".42 0 .58 1",
}[name]; }[name];
} }
$.on = (element, event, selector, callback) => { $.on = (element, event, selector, callback) => {
if (!callback) { if (!callback) {
callback = selector; callback = selector;
$.bind(element, event, callback); $.bind(element, event, callback);
} else { } else {
$.delegate(element, event, selector, callback); $.delegate(element, event, selector, callback);
} }
}; };
$.off = (element, event, handler) => { $.off = (element, event, handler) => {
element.removeEventListener(event, handler); element.removeEventListener(event, handler);
}; };
$.bind = (element, event, callback) => { $.bind = (element, event, callback) => {
event.split(/\s+/).forEach(function (event) { event.split(/\s+/).forEach(function (event) {
element.addEventListener(event, callback); element.addEventListener(event, callback);
}); });
}; };
$.delegate = (element, event, selector, callback) => { $.delegate = (element, event, selector, callback) => {
element.addEventListener(event, function (e) { element.addEventListener(event, function (e) {
const delegatedTarget = e.target.closest(selector); const delegatedTarget = e.target.closest(selector);
if (delegatedTarget) { if (delegatedTarget) {
e.delegatedTarget = delegatedTarget; e.delegatedTarget = delegatedTarget;
callback.call(this, e, delegatedTarget); callback.call(this, e, delegatedTarget);
} }
}); });
}; };
$.closest = (selector, element) => { $.closest = (selector, element) => {
if (!element) return null; if (!element) return null;
if (element.matches(selector)) { if (element.matches(selector)) {
return element; return element;
} }
return $.closest(selector, element.parentNode); return $.closest(selector, element.parentNode);
}; };
$.attr = (element, attr, value) => { $.attr = (element, attr, value) => {
if (!value && typeof attr === 'string') { if (!value && typeof attr === "string") {
return element.getAttribute(attr); return element.getAttribute(attr);
} }
if (typeof attr === 'object') { if (typeof attr === "object") {
for (let key in attr) { for (let key in attr) {
$.attr(element, key, attr[key]); $.attr(element, key, attr[key]);
}
return;
} }
return;
}
element.setAttribute(attr, value); element.setAttribute(attr, value);
}; };

View File

@ -1,124 +1,124 @@
import date_utils from '../src/date_utils'; import date_utils from "../src/date_utils";
test('Parse: parses string date', () => { test("Parse: parses string date", () => {
const date = date_utils.parse('2017-09-09'); const date = date_utils.parse("2017-09-09");
expect(date.getDate()).toBe(9); expect(date.getDate()).toBe(9);
expect(date.getMonth()).toBe(8); expect(date.getMonth()).toBe(8);
expect(date.getFullYear()).toBe(2017); expect(date.getFullYear()).toBe(2017);
}); });
test('Parse: parses string datetime', () => { test("Parse: parses string datetime", () => {
const date = date_utils.parse('2017-08-27 16:08:34'); const date = date_utils.parse("2017-08-27 16:08:34");
expect(date.getFullYear()).toBe(2017); expect(date.getFullYear()).toBe(2017);
expect(date.getMonth()).toBe(7); expect(date.getMonth()).toBe(7);
expect(date.getDate()).toBe(27); expect(date.getDate()).toBe(27);
expect(date.getHours()).toBe(16); expect(date.getHours()).toBe(16);
expect(date.getMinutes()).toBe(8); expect(date.getMinutes()).toBe(8);
expect(date.getSeconds()).toBe(34); expect(date.getSeconds()).toBe(34);
}); });
test('Parse: parses string datetime', () => { test("Parse: parses string datetime", () => {
const date = date_utils.parse('2016-02-29 16:08:34.3'); const date = date_utils.parse("2016-02-29 16:08:34.3");
expect(date.getFullYear()).toBe(2016); expect(date.getFullYear()).toBe(2016);
expect(date.getMonth()).toBe(1); expect(date.getMonth()).toBe(1);
expect(date.getDate()).toBe(29); expect(date.getDate()).toBe(29);
expect(date.getHours()).toBe(16); expect(date.getHours()).toBe(16);
expect(date.getMinutes()).toBe(8); expect(date.getMinutes()).toBe(8);
expect(date.getSeconds()).toBe(34); expect(date.getSeconds()).toBe(34);
expect(date.getMilliseconds()).toBe(300); expect(date.getMilliseconds()).toBe(300);
}); });
test('Parse: parses string datetime', () => { test("Parse: parses string datetime", () => {
const date = date_utils.parse('2015-07-01 00:00:59.200'); const date = date_utils.parse("2015-07-01 00:00:59.200");
expect(date.getFullYear()).toBe(2015); expect(date.getFullYear()).toBe(2015);
expect(date.getMonth()).toBe(6); expect(date.getMonth()).toBe(6);
expect(date.getDate()).toBe(1); expect(date.getDate()).toBe(1);
expect(date.getHours()).toBe(0); expect(date.getHours()).toBe(0);
expect(date.getMinutes()).toBe(0); expect(date.getMinutes()).toBe(0);
expect(date.getSeconds()).toBe(59); expect(date.getSeconds()).toBe(59);
expect(date.getMilliseconds()).toBe(200); expect(date.getMilliseconds()).toBe(200);
}); });
test('Format: converts date object to string', () => { test("Format: converts date object to string", () => {
const date = new Date('2017-09-18'); const date = new Date("2017-09-18");
expect(date_utils.to_string(date)).toBe('2017-09-18'); expect(date_utils.to_string(date)).toBe("2017-09-18");
}); });
test('Format: converts date object to string', () => { test("Format: converts date object to string", () => {
const date = new Date('2016-02-29 16:08:34.3'); 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'); expect(date_utils.to_string(date, true)).toBe("2016-02-29 16:08:34.300");
}); });
test('Format: converts date object to string', () => { test("Format: converts date object to string", () => {
const date = new Date('2016-02-29 16:08:34.3'); 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'); 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 d = new Date();
const date = date_utils.parse(d); const date = date_utils.parse(d);
expect(d).toBe(date); expect(d).toBe(date);
}); });
test('Diff: returns diff between 2 date objects', () => { test("Diff: returns diff between 2 date objects", () => {
const a = date_utils.parse('2017-09-08'); const a = date_utils.parse("2017-09-08");
const b = date_utils.parse('2017-06-07'); const b = date_utils.parse("2017-06-07");
expect(date_utils.diff(a, b, 'day')).toBe(93); expect(date_utils.diff(a, b, "day")).toBe(93);
expect(date_utils.diff(a, b, 'month')).toBe(3); expect(date_utils.diff(a, b, "month")).toBe(3);
expect(date_utils.diff(a, b, 'year')).toBe(0); expect(date_utils.diff(a, b, "year")).toBe(0);
}); });
test('StartOf', () => { test("StartOf", () => {
const date = date_utils.parse('2017-08-12 15:07:34.012'); 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( 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( 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( 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( 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( 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( 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( 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', () => { test("format", () => {
const date = date_utils.parse('2017-08-12 15:07:23'); const date = date_utils.parse("2017-08-12 15:07:23");
expect(date_utils.format(date, 'YYYY-MM-DD')).toBe('2017-08-12'); expect(date_utils.format(date, "YYYY-MM-DD")).toBe("2017-08-12");
}); });
test('format', () => { test("format", () => {
const date = date_utils.parse('2016-02-29 16:08:34.3'); 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'); expect(date_utils.format(date)).toBe("2016-02-29 16:08:34.300");
}); });

View File

@ -2907,10 +2907,10 @@ preserve@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^2.6.2: prettier@3.2.5:
version "2.6.2" version "3.2.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"
integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
pretty-format@^22.1.0: pretty-format@^22.1.0:
version "22.1.0" version "22.1.0"