Merge pull request #394 from safwansamsudeen/master
Tooling and Bug Fixes
This commit is contained in:
commit
22ff6e202e
30
.github/workflows/publish.yml
vendored
30
.github/workflows/publish.yml
vendored
@ -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
1
.gitignore
vendored
@ -25,6 +25,7 @@ build/Release
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
.yarn
|
||||
|
||||
.DS_Store
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true
|
||||
}
|
||||
|
||||
1
.yarnrc.yml
Normal file
1
.yarnrc.yml
Normal file
@ -0,0 +1 @@
|
||||
nodeLinker: node-modules
|
||||
50
README.md
50
README.md
@ -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
295
dist/frappe-gantt.css
vendored
@ -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);
|
||||
}
|
||||
3516
dist/frappe-gantt.js
vendored
3516
dist/frappe-gantt.js
vendored
File diff suppressed because it is too large
Load Diff
1
dist/frappe-gantt.js.map
vendored
1
dist/frappe-gantt.js.map
vendored
File diff suppressed because one or more lines are too long
1
dist/frappe-gantt.min.css
vendored
1
dist/frappe-gantt.min.css
vendored
@ -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)}
|
||||
2
dist/frappe-gantt.min.js
vendored
2
dist/frappe-gantt.min.js
vendored
File diff suppressed because one or more lines are too long
1
dist/frappe-gantt.min.js.map
vendored
1
dist/frappe-gantt.min.js.map
vendored
File diff suppressed because one or more lines are too long
23
dist/frappe-gantt.umd.cjs
vendored
Normal file
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
1
dist/style.css
vendored
Normal file
File diff suppressed because one or more lines are too long
262
index.html
262
index.html
@ -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 src="dist/frappe-gantt.umd.cjs"></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";
|
||||
|
||||
// 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>
|
||||
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,
|
||||
// dates_readonly: true,
|
||||
// today_button: false,
|
||||
// readonly: true,
|
||||
// lines: 'vertical',
|
||||
// lower_text: (date) => date.getDay(),
|
||||
// upper_text: (date, view_mode, def) => def,
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Frappe Technologies Pvt. Ltd.
|
||||
Copyright (c) 2024 Frappe Technologies Pvt. Ltd.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
6265
package-lock.json
generated
Normal file
6265
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
108
package.json
108
package.json
@ -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.8",
|
||||
"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
4
postcss.config.cjs
Normal file
@ -0,0 +1,4 @@
|
||||
/* eslint-disable */
|
||||
module.exports = {
|
||||
plugins: [require('postcss-nesting')],
|
||||
};
|
||||
@ -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];
|
||||
127
src/arrow.js
127
src/arrow.js
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
1231
src/bar.js
1231
src/bar.js
File diff suppressed because it is too large
Load Diff
99
src/dark.css
Normal file
99
src/dark.css
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
306
src/gantt.css
Normal 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;
|
||||
}
|
||||
}
|
||||
316
src/gantt.scss
316
src/gantt.scss
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
2389
src/index.js
2389
src/index.js
File diff suppressed because it is too large
Load Diff
97
src/popup.js
97
src/popup.js
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
188
src/svg_utils.js
188
src/svg_utils.js
@ -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);
|
||||
};
|
||||
|
||||
@ -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');
|
||||
});
|
||||
|
||||
22
vite.config.js
Normal file
22
vite.config.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.js'),
|
||||
name: 'Gantt',
|
||||
fileName: 'frappe-gantt',
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['vue'],
|
||||
output: {
|
||||
globals: {
|
||||
vue: 'Vue'
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
output: { interop: 'auto' },
|
||||
server: { watch: { include: ['dist/*', 'src/*'] } }
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user