This commit is contained in:
Safwan Samsudeen 2024-06-17 18:50:25 +05:30
commit 35d810ba7c
32 changed files with 17531 additions and 9069 deletions

View File

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

View File

@ -1,19 +1,19 @@
name: Publish on NPM
on:
push:
branches: [master]
push:
branches: [master]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 14
- run: yarn install
- run: yarn prettier-check
- run: yarn test
- uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 14
- run: yarn install
- run: yarn prettier-check
- run: yarn test
- uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}

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

View File

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

1
.yarnrc.yml Normal file
View File

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

View File

@ -51,19 +51,19 @@ var gantt = new Gantt("#gantt", tasks);
You can also pass various options to the Gantt constructor:
```js
var gantt = new Gantt("#gantt", tasks, {
header_height: 50,
column_width: 30,
step: 24,
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'
custom_popup_html: null,
var gantt = new Gantt('#gantt', tasks, {
header_height: 50,
column_width: 30,
step: 24,
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'
custom_popup_html: null,
});
```
@ -91,25 +91,25 @@ Assuming the last commit (or a couple of commits) were enhancements or fixes,
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. Type the new version at the prompt
Depending on the type of change, you can either bump the patch version or the minor version.
For e.g.,
Depending on the type of change, you can either bump the patch version or the minor version.
For e.g.,
```
0.5.0 -> 0.6.0 (minor version bump)
0.5.0 -> 0.5.1 (patch version bump)
```
```
0.5.0 -> 0.6.0 (minor 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:
```
git add dist
git commit --amend
git push origin master
```
```
git add dist
git commit --amend
git push origin master
```
License: MIT

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);
}

3543
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

@ -1,130 +1,140 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Simple Gantt</title>
<style>
body {
font-family: sans-serif;
background: #ccc;
}
.container {
width: 80%;
margin: 0 auto;
}
/* custom class */
.gantt .bar-milestone .bar {
fill: tomato;
}
.heading {
text-align: center;
}
.gantt-target.dark {
background-color: #252525;
}
</style>
<link rel="stylesheet" href="dist/frappe-gantt.css" />
<script src="dist/frappe-gantt.js"></script>
</head>
<body>
<div class="container">
<h2 class="heading">Interactive Gantt Chart entirely made in SVG!</h2>
<div class="gantt-target"></div>
</div>
<script>
let tasks = [
{
start: '2024-04-01',
end: '2024-04-01',
name: 'Redesign website',
id: "Task 0",
progress: 30
},
{
start: '2024-03-26',
// Utilizes duration
duration: '6d',
name: 'Write new content',
id: "Task 1",
progress: 5,
important: true
},
{
start: '2024-04-04',
end: '2024-04-08',
name: 'Apply new styles',
id: "Task 2",
progress: 80,
dependencies: 'Task 1'
},
{
start: '2024-04-08',
end: '2024-04-09',
name: 'Review',
id: "Task 3",
progress: 5,
dependencies: 'Task 2'
},
{
start: '2024-04-08',
end: '2024-04-10',
name: 'Deploy',
id: "Task 4",
progress: 0,
// dependencies: 'Task 2'
},
{
start: '2024-04-21',
end: '2024-04-29',
name: 'Go Live!',
id: "Task 5",
progress: 0,
dependencies: 'Task 2',
custom_class: 'bar-milestone'
},
// {
// start: '2014-01-05',
// end: '2019-10-12',
// name: 'Long term task',
// id: "Task 6",
// progress: 0
// }
];
<head>
<meta charset="UTF-8" />
<title>Simple Gantt</title>
<style>
body {
font-family: sans-serif;
background: #ccc;
}
.container {
width: 80%;
margin: 0 auto;
}
/* custom class */
.gantt .bar-milestone .bar {
fill: tomato;
}
.heading {
text-align: center;
}
.gantt-target.dark {
background-color: #252525;
}
</style>
<link rel="stylesheet" href="dist/style.css" />
<!-- <script type="module" src="dist/frappe-gantt.js"></script> -->
</head>
<body>
<div class="container">
<h2 class="heading">
Interactive Gantt Chart entirely made in SVG!
</h2>
<div class="gantt-target"></div>
</div>
<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,
},
{
start: '2024-03-26',
// Utilizes duration
duration: '6d',
name: 'Write new content',
id: 'Task 1',
progress: 5,
important: true,
},
{
start: '2024-04-04',
end: '2024-04-08',
name: 'Apply new styles',
id: 'Task 2',
progress: 80,
dependencies: 'Task 1',
},
{
start: '2024-04-08',
end: '2024-04-09',
name: 'Review',
id: 'Task 3',
progress: 5,
dependencies: 'Task 2',
},
{
start: '2024-04-08',
end: '2024-04-10',
name: 'Deploy',
id: 'Task 4',
progress: 0,
// dependencies: 'Task 2'
},
{
start: '2024-04-21',
end: '2024-04-29',
name: 'Go Live!',
id: 'Task 5',
progress: 0,
dependencies: 'Task 2',
custom_class: 'bar-milestone',
},
// {
// start: '2014-01-05',
// end: '2019-10-12',
// name: 'Long term task',
// id: "Task 6",
// progress: 0
// }
];
// Uncomment to test fixed header
tasks = [...tasks, ...Array.from({length: tasks.length * 3}, (_, i) => ({...tasks[i % 3], id: i}))]
let gantt_chart = new Gantt(".gantt-target", tasks, {
on_click: (task) => {
console.log("Click", task);
},
// on_double_click: (task) => {
// console.log("Double Click", task);
// },
// on_date_change: (task, start, end) => {
// console.log("Date change", task, start, end);
// },
// on_progress_change: (task, progress) => {
// console.log("Progress Change", task, progress);
// },
// on_view_change: (mode) => {
// console.log("View Change", mode);
// },
// on_hover: (task, x, y) => {
// console.log("Hover", x, y);
// },
view_mode: "Day",
view_mode_padding: { DAY: "3d" },
popup: false,
// scroll_to: 'today',
// view_mode_select: 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>
// Uncomment to test fixed header
tasks = [
...tasks,
...Array.from({ length: tasks.length * 3 }, (_, i) => ({
...tasks[i % 3],
id: i,
})),
];
let gantt_chart = new Gantt('.gantt-target', tasks, {
on_click: (task) => {
console.log('Click', task);
},
// on_double_click: (task) => {
// console.log("Double Click", task);
// },
// on_date_change: (task, start, end) => {
// console.log("Date change", task, start, end);
// },
// on_progress_change: (task, progress) => {
// console.log("Progress Change", task, progress);
// },
// on_view_change: (mode) => {
// console.log("View Change", mode);
// },
// on_hover: (task, x, y) => {
// console.log("Hover", x, y);
// },
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>

6265
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +1,60 @@
{
"name": "frappe-gantt",
"version": "0.6.1",
"description": "A simple, modern, interactive gantt library for the web",
"main": "src/index.js",
"scripts": {
"start": "yarn run dev",
"build": "rollup -c",
"dev": "rollup -c -w",
"test": "jest",
"test:watch": "jest --watch",
"prettier": "prettier --write \"{src/*,tests/*,rollup.config}.js\"",
"prettier-check": "prettier --check \"{src/*,tests/*,rollup.config}.js\""
},
"repository": {
"type": "git",
"url": "https://github.com/frappe/gantt.git"
},
"files": [
"src",
"dist",
"README.md"
],
"keywords": [
"gantt",
"svg",
"simple gantt",
"project timeline",
"interactive gantt",
"project management"
],
"author": "Faris Ansari",
"license": "MIT",
"bugs": {
"url": "https://github.com/frappe/gantt/issues"
},
"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",
"prettier": "3.2.5",
"rollup": "^2.70.2",
"rollup-plugin-sass": "^1.2.12",
"rollup-plugin-terser": "^7.0.2"
},
"eslintIgnore": [
"dist"
]
"name": "frappe-gantt",
"version": "0.7",
"description": "A simple, modern, interactive gantt library for the web",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "yarn run dev",
"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\""
},
"repository": {
"type": "git",
"url": "https://github.com/frappe/gantt.git"
},
"files": [
"src",
"dist",
"README.md"
],
"exports": {
".": {
"import": "./dist/frappe-gantt.es.js",
"require": "./dist/frappe-gantt.umd.js"
}
},
"keywords": [
"gantt",
"svg",
"simple gantt",
"project timeline",
"interactive gantt",
"project management"
],
"author": "Faris Ansari",
"license": "MIT",
"bugs": {
"url": "https://github.com/frappe/gantt/issues"
},
"homepage": "https://github.com/frappe/gantt",
"devDependencies": {
"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",
"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,52 +1,53 @@
import { createSVG } from "./svg_utils";
import { createSVG } from './svg_utils';
export default class Arrow {
constructor(gantt, from_task, to_task) {
this.gantt = gantt;
this.from_task = from_task;
this.to_task = to_task;
constructor(gantt, from_task, to_task) {
this.gantt = gantt;
this.from_task = from_task;
this.to_task = to_task;
this.calculate_path();
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;
this.calculate_path();
this.draw();
}
const start_y =
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;
calculate_path() {
let start_x =
this.from_task.$bar.getX() + this.from_task.$bar.getWidth() / 2;
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 +
(this.gantt.options.padding + this.gantt.options.bar_height) *
this.to_task.task._index +
this.gantt.options.padding;
const condition = () =>
this.to_task.$bar.getX() < start_x + this.gantt.options.padding &&
start_x > this.from_task.$bar.getX() + 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;
while (condition()) {
start_x -= 10;
}
this.path = `
const start_y =
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 - 7;
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}
V ${offset}
a ${curve} ${curve} 0 0 ${clockwise} ${curve} ${curve_y}
@ -55,16 +56,18 @@ export default class Arrow {
l 5 5
l -5 5`;
if (
this.to_task.$bar.getX() <
this.from_task.$bar.getX() + this.gantt.options.padding
) {
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;
const left = this.to_task.$bar.getX() - this.gantt.options.padding;
if (
this.to_task.$bar.getX() <
this.from_task.$bar.getX() + this.gantt.options.padding
) {
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;
const left = this.to_task.$bar.getX() - this.gantt.options.padding;
this.path = `
this.path = `
M ${start_x} ${start_y}
v ${down_1}
a ${curve} ${curve} 0 0 1 -${curve} ${curve}
@ -76,19 +79,19 @@ export default class Arrow {
m -5 -5
l 5 5
l -5 5`;
}
}
}
draw() {
this.element = createSVG("path", {
d: this.path,
"data-from": this.from_task.task.id,
"data-to": this.to_task.task.id,
});
}
draw() {
this.element = createSVG('path', {
d: this.path,
'data-from': this.from_task.task.id,
'data-to': this.to_task.task.id,
});
}
update() {
this.calculate_path();
this.element.setAttribute("d", this.path);
}
update() {
this.calculate_path();
this.element.setAttribute('d', this.path);
}
}

1232
src/bar.js

File diff suppressed because it is too large Load Diff

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,266 +1,266 @@
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 matches = regex.exec(duration);
if (matches !== null) {
if (matches[2] === "y") {
return { duration: parseInt(matches[1]), scale: `year` };
} else if (matches[2] === "m") {
return { duration: parseInt(matches[1]), scale: `month` };
} else if (matches[2] === "d") {
return { duration: parseInt(matches[1]), scale: `day` };
} else if (matches[2] === "h") {
return { duration: parseInt(matches[1]), scale: `hour` };
} else if (matches[2] === "min") {
return { duration: parseInt(matches[1]), scale: `minute` };
} else if (matches[2] === "s") {
return { duration: parseInt(matches[1]), scale: `second` };
} else if (matches[2] === "ms") {
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] ? date_parts[1] - 1 : 0;
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;
parse_duration(duration) {
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') {
return { duration: parseInt(matches[1]), scale: `year` };
} else if (matches[2] === 'm') {
return { duration: parseInt(matches[1]), scale: `month` };
} else if (matches[2] === 'd') {
return { duration: parseInt(matches[1]), scale: `day` };
} else if (matches[2] === 'h') {
return { duration: parseInt(matches[1]), scale: `hour` };
} else if (matches[2] === 'min') {
return { duration: parseInt(matches[1]), scale: `minute` };
} else if (matches[2] === 's') {
return { duration: parseInt(matches[1]), scale: `second` };
} else if (matches[2] === 'ms') {
return { duration: parseInt(matches[1]), scale: `millisecond` };
}
}
vals = vals.concat(time_parts);
}
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: SHORTENED[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.replaceAll(key, `$${formatted_values.length}`);
formatted_values.push(format_map[key]);
},
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);
formatted_values.forEach((value, i) => {
str = str.replaceAll(`$${i}`, value);
});
// month is 0 indexed
date_parts[1] = date_parts[1] ? date_parts[1] - 1 : 0;
return str;
},
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) {
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);
}
return new Date(...vals);
}
},
milliseconds = date_a - date_b;
seconds = milliseconds / 1000;
minutes = seconds / 60;
hours = minutes / 60;
days = hours / 24;
months = days / 30;
years = months / 12;
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 (!scale.endsWith("s")) {
scale += "s";
}
if (i === 6) {
return padStart(val + '', 3, '0');
}
return Math.floor(
{
milliseconds,
seconds,
minutes,
hours,
days,
months,
years,
}[scale],
);
},
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]}`;
today() {
const vals = this.get_date_values(new Date()).slice(0, 3);
return new Date(...vals);
},
return date_string + (with_time ? ' ' + time_string : '');
},
now() {
return new Date();
},
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);
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);
},
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: SHORTENED[month_name_capitalized],
};
start_of(date, scale) {
const scores = {
[YEAR]: 6,
[MONTH]: 5,
[DAY]: 4,
[HOUR]: 3,
[MINUTE]: 2,
[SECOND]: 1,
[MILLISECOND]: 0,
};
let str = format_string;
const formatted_values = [];
function should_reset(_scale) {
const max_score = scores[scale];
return scores[_scale] <= max_score;
}
Object.keys(format_map)
.sort((a, b) => b.length - a.length) // big string first
.forEach((key) => {
if (str.includes(key)) {
str = str.replaceAll(key, `$${formatted_values.length}`);
formatted_values.push(format_map[key]);
}
});
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(),
];
formatted_values.forEach((value, i) => {
str = str.replaceAll(`$${i}`, value);
});
return new Date(...vals);
},
return str;
},
clone(date) {
return new Date(...this.get_date_values(date));
},
diff(date_a, date_b, scale = DAY) {
let milliseconds, seconds, hours, minutes, days, months, years;
get_date_values(date) {
return [
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds(),
];
},
milliseconds = date_a - date_b;
seconds = milliseconds / 1000;
minutes = seconds / 60;
hours = minutes / 60;
days = hours / 24;
months = days / 30;
years = months / 12;
get_days_in_month(date) {
const no_of_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (!scale.endsWith('s')) {
scale += 's';
}
const month = date.getMonth();
return Math.floor(
{
milliseconds,
seconds,
minutes,
hours,
days,
months,
years,
}[scale],
);
},
if (month !== 1) {
return no_of_days[month];
}
today() {
const vals = this.get_date_values(new Date()).slice(0, 3);
return new Date(...vals);
},
// Feb
const year = date.getFullYear();
if ((year % 4 === 0 && year % 100 != 0) || year % 400 === 0) {
return 29;
}
return 28;
},
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
function padStart(str, targetLength, padString) {
str = str + "";
targetLength = targetLength >> 0;
padString = String(typeof padString !== "undefined" ? padString : " ");
if (str.length > targetLength) {
return String(str);
} else {
targetLength = targetLength - str.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length);
str = str + '';
targetLength = targetLength >> 0;
padString = String(typeof padString !== 'undefined' ? padString : ' ');
if (str.length > targetLength) {
return String(str);
} else {
targetLength = targetLength - str.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length);
}
return padString.slice(0, targetLength) + String(str);
}
return padString.slice(0, targetLength) + String(str);
}
}

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

@ -1,61 +1,62 @@
export default class Popup {
constructor(parent, custom_html) {
this.parent = parent;
this.custom_html = custom_html;
this.make();
}
constructor(parent, custom_html) {
this.parent = parent;
this.custom_html = custom_html;
this.make();
}
make() {
this.parent.innerHTML = `
make() {
this.parent.innerHTML = `
<div class="title"></div>
<div class="subtitle"></div>
<div class="pointer"></div>
`;
this.hide();
this.hide();
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");
}
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.title = this.parent.querySelector('.title');
this.subtitle = this.parent.querySelector('.subtitle');
this.pointer = this.parent.querySelector('.pointer');
}
// 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();
show(options) {
if (!options.target_element) {
throw new Error('target_element is required to show popup');
}
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;
}
// 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();
}
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';
// show
this.parent.style.opacity = 1;
}
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";
// show
this.parent.style.opacity = 1;
}
hide() {
this.parent.style.opacity = 0;
this.parent.style.left = 0;
}
hide() {
this.parent.style.opacity = 0;
this.parent.style.left = 0;
}
}

View File

@ -1,135 +1,135 @@
export function $(expr, con) {
return typeof expr === "string"
? (con || document).querySelector(expr)
: expr || null;
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);
for (let attr in attrs) {
if (attr === "append_to") {
const parent = attrs.append_to;
parent.appendChild(elem);
} else if (attr === "innerHTML") {
elem.innerHTML = attrs.innerHTML;
} else if (attr === 'clipPath') {
elem.setAttribute('clip-path', 'url(#' + attrs[attr] + ')');
} else {
elem.setAttribute(attr, attrs[attr]);
const elem = document.createElementNS('http://www.w3.org/2000/svg', tag);
for (let attr in attrs) {
if (attr === 'append_to') {
const parent = attrs.append_to;
parent.appendChild(elem);
} else if (attr === 'innerHTML') {
elem.innerHTML = attrs.innerHTML;
} else if (attr === 'clipPath') {
elem.setAttribute('clip-path', 'url(#' + attrs[attr] + ')');
} else {
elem.setAttribute(attr, attrs[attr]);
}
}
}
return elem;
return elem;
}
export function animateSVG(svgElement, attr, from, to) {
const animatedSvgElement = getAnimationElement(svgElement, attr, from, to);
const animatedSvgElement = getAnimationElement(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";
animatedSvgElement.dispatchEvent(event);
}
if (animatedSvgElement === svgElement) {
// triggered 2nd time programmatically
// trigger artificial click event
const event = document.createEvent('HTMLEvents');
event.initEvent('click', true, true);
event.eventName = 'click';
animatedSvgElement.dispatchEvent(event);
}
}
function getAnimationElement(
svgElement,
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,
svgElement,
attr,
from,
to,
dur,
begin,
calcMode: "spline",
values: from + ";" + to,
keyTimes: "0; 1",
keySplines: cubic_bezier("ease-out"),
});
svgElement.appendChild(animateElement);
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;
}
return svgElement;
const animateElement = createSVG('animate', {
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) {
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",
}[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',
}[name];
}
$.on = (element, event, selector, callback) => {
if (!callback) {
callback = selector;
$.bind(element, event, callback);
} else {
$.delegate(element, event, selector, callback);
}
if (!callback) {
callback = selector;
$.bind(element, event, callback);
} else {
$.delegate(element, event, selector, callback);
}
};
$.off = (element, event, handler) => {
element.removeEventListener(event, handler);
element.removeEventListener(event, handler);
};
$.bind = (element, event, callback) => {
event.split(/\s+/).forEach(function (event) {
element.addEventListener(event, callback);
});
event.split(/\s+/).forEach(function (event) {
element.addEventListener(event, callback);
});
};
$.delegate = (element, event, selector, callback) => {
element.addEventListener(event, function (e) {
const delegatedTarget = e.target.closest(selector);
if (delegatedTarget) {
e.delegatedTarget = delegatedTarget;
callback.call(this, e, delegatedTarget);
}
});
element.addEventListener(event, function (e) {
const delegatedTarget = e.target.closest(selector);
if (delegatedTarget) {
e.delegatedTarget = delegatedTarget;
callback.call(this, e, delegatedTarget);
}
});
};
$.closest = (selector, element) => {
if (!element) return null;
if (!element) return null;
if (element.matches(selector)) {
return element;
}
if (element.matches(selector)) {
return element;
}
return $.closest(selector, element.parentNode);
return $.closest(selector, element.parentNode);
};
$.attr = (element, attr, value) => {
if (!value && typeof attr === "string") {
return element.getAttribute(attr);
}
if (typeof attr === "object") {
for (let key in attr) {
$.attr(element, key, attr[key]);
if (!value && typeof attr === 'string') {
return element.getAttribute(attr);
}
return;
}
element.setAttribute(attr, value);
if (typeof attr === 'object') {
for (let key in attr) {
$.attr(element, key, attr[key]);
}
return;
}
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", () => {
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);
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);
expect(date.getDate()).toBe(27);
expect(date.getHours()).toBe(16);
expect(date.getMinutes()).toBe(8);
expect(date.getSeconds()).toBe(34);
expect(date.getFullYear()).toBe(2017);
expect(date.getMonth()).toBe(7);
expect(date.getDate()).toBe(27);
expect(date.getHours()).toBe(16);
expect(date.getMinutes()).toBe(8);
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);
expect(date.getDate()).toBe(29);
expect(date.getHours()).toBe(16);
expect(date.getMinutes()).toBe(8);
expect(date.getSeconds()).toBe(34);
expect(date.getMilliseconds()).toBe(300);
expect(date.getFullYear()).toBe(2016);
expect(date.getMonth()).toBe(1);
expect(date.getDate()).toBe(29);
expect(date.getHours()).toBe(16);
expect(date.getMinutes()).toBe(8);
expect(date.getSeconds()).toBe(34);
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);
expect(date.getDate()).toBe(1);
expect(date.getHours()).toBe(0);
expect(date.getMinutes()).toBe(0);
expect(date.getSeconds()).toBe(59);
expect(date.getMilliseconds()).toBe(200);
expect(date.getFullYear()).toBe(2015);
expect(date.getMonth()).toBe(6);
expect(date.getDate()).toBe(1);
expect(date.getHours()).toBe(0);
expect(date.getMinutes()).toBe(0);
expect(date.getSeconds()).toBe(59);
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", () => {
const d = new Date();
const date = date_utils.parse(d);
test('Parse: returns Date Object as is', () => {
const d = new Date();
const date = date_utils.parse(d);
expect(d).toBe(date);
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");
expect(date_utils.to_string(start_of_millisecond, true)).toBe(
"2017-08-12 15:07:34.012",
);
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',
);
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",
);
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',
);
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",
);
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',
);
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",
);
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',
);
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",
);
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',
);
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",
);
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',
);
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",
);
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',
);
});
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');
});

28
vite.config.js Normal file
View File

@ -0,0 +1,28 @@
import { resolve } from 'path';
import { defineConfig } from 'vite';
import pkg from './package.json';
import path from 'node:path';
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.js'),
name: pkg.name,
fileName: 'frappe-gantt',
},
rollupOptions: {
external: ['vue'],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: 'Vue'
}
},
// input: [path.join(process.cwd(), 'index.html'), path.join(process.cwd(), 'src', 'index.js')],
// preserveEntrySignatures: "allow-extension"
},
},
output: { interop: 'auto' },
server: { watch: { include: ['dist/*', 'src/*'] } }
});

10471
yarn.lock

File diff suppressed because it is too large Load Diff