Compare commits

..

344 Commits

Author SHA1 Message Date
Luigi Trabacchin
108eeb5898 Handle double tab as if it was a double_click 2025-02-21 20:39:27 +05:30
Michael G
7419b37847 Revert "remove index"
This reverts commit ae7bc8db60653d60f36aa1e10a837d38d89135f2.

Index.htnl was deleted by the maintainer, unclear why.
2025-02-10 10:38:39 +05:30
Tuomas Jaakola
3487916883 fix: use color_progress 2025-02-03 18:09:16 +05:30
Safwan Samsudeen
df08d74eeb fix: select issue 2025-01-24 11:31:22 +05:30
hiawui
eded996177 fix: child can be moved before parent when has multiple parents 2025-01-23 11:56:19 +05:30
Safwan Samsudeen
e71369da34 chore: format 2025-01-23 11:52:59 +05:30
Safwan Samsudeen
b617dfffbe chore: bump version 2025-01-23 11:50:39 +05:30
Safwan Samsudeen
41ab8de74f fix: improvements 2025-01-23 11:50:26 +05:30
Safwan Samsudeen
6311a1e32f fix: progress bug 2025-01-23 11:48:04 +05:30
Safwan Samsudeen
0e2c8dadbe fix: stylistic issue 2025-01-23 11:44:34 +05:30
Safwan Samsudeen
4d297bb7f8 fix: improved ui 2025-01-22 13:01:26 +05:30
dependabot[bot]
3bcaf72027 chore(deps-dev): bump vite from 5.4.11 to 5.4.12
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.11 to 5.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 11:06:07 +05:30
Safwan Samsudeen
d53266f96c fix: dropdown styles 2025-01-20 12:03:03 +05:30
Safwan Samsudeen
41838a913c fix: update styling 2025-01-20 11:53:27 +05:30
Praveen
1a09a38a6e fix: on calling update_task, start and end date not being updated 2025-01-16 15:27:27 +05:30
Safwan Samsudeen
6a34a20e15 fix: update arrow color 2025-01-16 12:06:35 +05:30
Safwan Samsudeen
2850787e72 chore: bump version 2025-01-16 11:55:20 +05:30
Safwan Samsudeen
c151416048 Merge branch 'demo' 2025-01-16 11:54:23 +05:30
Safwan Samsudeen
02012fff05 fix: minor ui issues 2025-01-16 11:51:26 +05:30
Safwan Samsudeen
ae7bc8db60 remove index 2025-01-08 17:26:56 +05:30
Safwan Samsudeen
45a17c792c chore: prepare for v1 2025-01-08 17:17:36 +05:30
Safwan Samsudeen
188ab1d35e feat: reset after view mode change 2025-01-08 16:31:47 +05:30
Safwan Samsudeen
a8d7814c82 chore: update screenshot 2025-01-08 15:43:54 +05:30
Safwan Samsudeen
521107412a fix: readonly bug 2025-01-08 15:35:47 +05:30
Safwan Samsudeen
65ea2377db feat: add color to bars 2025-01-08 15:30:04 +05:30
Safwan Samsudeen
34422d1886 fix: make buttons standard, fix bugs 2025-01-08 15:26:26 +05:30
Safwan Samsudeen
1f1a5f04e1 fix: many small bugs 2025-01-08 14:52:46 +05:30
Safwan Samsudeen
7dcc132830 fix: handle bar disappearing 2025-01-08 13:01:19 +05:30
Safwan Samsudeen
2c15115198 fix: bugs in popup 2025-01-08 12:41:44 +05:30
Safwan Samsudeen
03d886665b feat: add docs
chore: make more consistent
2025-01-08 10:53:09 +05:30
Safwan Samsudeen
3973293961 chore: lint 2025-01-07 17:31:38 +05:30
Safwan Samsudeen
3217f7d986 feat: v1 of demo 2025-01-07 17:27:41 +05:30
Safwan Samsudeen
bab2afee54 feat: circle marker for current time 2025-01-07 17:26:42 +05:30
Safwan Samsudeen
84a677d26e chore: cleanup, revert demo testing 2025-01-07 15:20:11 +05:30
Safwan Samsudeen
2f6b578fc9 fix: bar dragging tracker bug 2025-01-07 14:37:47 +05:30
Safwan Samsudeen
3dd58fc00d fix: small bugs and style changes 2025-01-07 14:32:24 +05:30
Safwan Samsudeen
8b523c7125 feat: make like notion; fix: bar rendering bugs 2025-01-06 14:59:19 +05:30
Safwan Samsudeen
abeec1c363 feat: more like notion 2025-01-02 14:33:38 +05:30
Safwan Samsudeen
218202c16d add auto container height 2025-01-02 13:50:04 +05:30
Safwan Samsudeen
f9c5b1f3ac feat: demo in builder 2024-12-23 17:25:34 +05:30
Safwan Samsudeen
0b33e88672 fix: scrolling bug in infinite padding 2024-12-23 16:45:37 +05:30
Safwan Samsudeen
85399e7fdc feat: get demo on builder 2024-12-23 16:45:37 +05:30
Safwan Samsudeen
393a91ef11
update README.md 2024-12-23 14:46:03 +05:30
Safwan Samsudeen
5c9f46a69c feat: stop scroll on view_mode change 2024-12-19 12:34:00 +05:30
Safwan Samsudeen
8be3b02a9e feat: api to update task; fix: couple bugs 2024-12-19 11:59:34 +05:30
Safwan Samsudeen
d5ee5a4e45 fix: dates broken in dst 2024-12-17 12:37:25 +02:00
Safwan Samsudeen
097d69ce90 fix: bug with current marking 2024-12-17 15:33:44 +05:30
Safwan Samsudeen
fcd04922bd fix: few bugs in popup 2024-12-17 15:23:04 +05:30
Safwan Samsudeen
350dc88785 feat: replace tooltip with popup 2024-12-17 14:41:24 +05:30
Safwan Samsudeen
a3c9f15cf9 feat: go back to main timeframe 2024-12-16 15:34:36 +05:30
Safwan Samsudeen
019e550b6e fix: small improvements 2024-12-16 14:18:58 +05:30
Safwan Samsudeen
196299db50 fix: arrow bugs 2024-12-13 16:56:53 +05:30
Safwan Samsudeen
e6a1c30410 fix: issues discovered while porting to builder 2024-12-13 15:52:37 +05:30
Safwan Samsudeen
d1cc6cc79e fix: bug in upper header, remove direct dom manipulation 2024-12-13 14:56:18 +05:30
Safwan Samsudeen
a7d38841b3 fix: bugs in view mode renderings 2024-12-13 14:32:39 +05:30
Safwan Samsudeen
539482e6d9 feat: cleaner styling, enable theming 2024-12-13 12:23:55 +05:30
Safwan Samsudeen
ba660d53d2
fix: remove badges 2024-12-13 09:04:51 +05:30
Safwan Samsudeen
178262033d fix: image 2024-12-13 09:03:18 +05:30
Safwan Samsudeen
c15cc3197d chore: update demo, defaults 2024-12-12 17:27:35 +05:30
Safwan Samsudeen
dae014d009 feat: implement header with css 2024-12-12 17:27:35 +05:30
Safwan Samsudeen
ffb53b935f
update image 2024-12-12 16:15:24 +05:30
Safwan Samsudeen
343324695a fix: better screenshot 2024-12-12 16:14:20 +05:30
Safwan Samsudeen
e48960366f
Update README.md 2024-12-12 16:08:01 +05:30
Safwan Samsudeen
bb448c774f add license badge 2024-12-12 14:51:26 +05:30
Safwan Samsudeen
1dfcc7bbf2 fix: add images, badge 2024-12-12 14:50:02 +05:30
Safwan Samsudeen
4a1e343d67 chore: update readme based on feedback 2024-12-12 14:47:18 +05:30
Safwan Samsudeen
3bdc6828c0 fix: input type error 2024-12-11 23:23:46 +05:30
Safwan Samsudeen
610da9d2e2 chore: update demo file 2024-12-11 19:47:07 +05:30
Safwan Samsudeen
0b1c6c9efb feat: smoother infinite padding, improved config 2024-12-11 13:23:07 +05:30
Safwan Samsudeen
f0a8bf7081 upload image 2024-12-10 17:47:05 +05:30
Safwan Samsudeen
426c8a826e
update README.md 2024-12-10 17:45:33 +05:30
Safwan Samsudeen
8b2e544bb4 update readme to match standard 2024-12-10 17:44:14 +05:30
safwansamsudeen
abe2e894b5 basic interactive demo 2024-12-10 16:36:41 +05:30
safwansamsudeen
f5f61afd99 changes for demo 2024-12-10 16:36:26 +05:30
safwansamsudeen
653a5d0f30 fix progress calculation 2024-12-10 15:30:11 +05:30
safwansamsudeen
a449ac9acf snap bars in ignored areas 2024-12-10 14:29:23 +05:30
safwansamsudeen
341e51b2bb feat: labels 2024-12-10 12:08:47 +05:30
safwansamsudeen
0c6266e0fe fix many bugs as demo is built 2024-12-06 18:09:35 +05:30
safwansamsudeen
f37fe4e80f fix button issues, positioned properly now 2024-12-05 16:28:12 +05:30
safwansamsudeen
d740d407b4 infinite extensions 2024-12-05 14:58:40 +05:30
safwansamsudeen
af1c94b56a perfect progress in ignore 2024-12-05 14:24:51 +05:30
safwansamsudeen
0c9e4aeb3e fix scrolling issue, make smoother scrolling 2024-12-05 12:58:45 +05:30
safwansamsudeen
6c8cc993c4 fix: couple ignoring bugs 2024-12-05 12:39:45 +05:30
safwansamsudeen
9caf2322b9 feat: ignore regions 2024-12-05 11:09:03 +05:30
safwansamsudeen
6984d3e544 feat: configurable highlights 2024-12-03 15:46:53 +05:30
safwansamsudeen
b776376f4e make release branch 2024-12-03 15:42:57 +05:30
Safwan Samsudeen
02ea61d7c3
Merge pull request #474 from frappe/view-modes
View Modes
2024-12-03 15:41:45 +05:30
safwansamsudeen
268d82ad35 upper text issues 2024-12-02 17:52:18 +05:30
safwansamsudeen
e20c7e0b6b fix disappearing text bug 2024-12-02 17:41:18 +05:30
Safwan Samsudeen
f4f97cdb18
Merge branch 'master' into view-modes 2024-12-02 17:38:40 +05:30
safwansamsudeen
50f9c2ca13 configurable snapping 2024-12-02 17:36:40 +05:30
Safwan Samsudeen
79b0e0d2ab
Merge pull request #475 from MercurialUroboros/fix/highlight-issues
fix: issues with highlight system and todayDate.
2024-12-02 17:34:25 +05:30
safwansamsudeen
440d717bc4 format 2024-12-02 17:33:36 +05:30
safwansamsudeen
d87022eb3f modify gitignore 2024-12-02 17:29:42 +05:30
Cristiano Soleti
6d89ee0154 fix: issues with highlight system and todayDate. 2024-12-02 11:35:31 +01:00
safwansamsudeen
8152f5ad17 fix: all format uses lang 2024-12-02 15:48:09 +05:30
safwansamsudeen
d7fb48031a chore: format 2024-12-02 14:52:15 +05:30
safwansamsudeen
3519755dc3 improve date handling and fix empty tasks error 2024-12-02 14:34:11 +05:30
safwansamsudeen
7ac5422950 more simplification 2024-12-02 13:07:05 +05:30
safwansamsudeen
fc71da6550 remove VIEW_MODE usages, fix bugs 2024-12-02 12:53:29 +05:30
safwansamsudeen
c13c0cde4d feat: snap_by_day option 2024-12-02 12:23:10 +05:30
safwansamsudeen
8c526b5ed6 fix highlighting bugs 2024-12-02 12:06:33 +05:30
safwansamsudeen
2eaa127c5b fix date calculation bugs 2024-12-02 11:28:48 +05:30
safwansamsudeen
6e0e0a9704 make daily mode work after rewrite of date system 2024-11-29 16:34:33 +05:30
Safwan Samsudeen
8f03ba0572
Merge pull request #423 from safwansamsudeen/master
Pre v1 Cleanup
2024-11-29 13:04:56 +05:30
Safwan Samsudeen
3e28b63e14
Merge branch 'master' into master 2024-11-29 13:04:19 +05:30
Safwan Samsudeen
59f5146b20
Merge pull request #441 from tagliala/chore/import-directive-webpack
Fix regression with webpack
2024-11-29 13:01:31 +05:30
safwansamsudeen
5d81ffdcef update node version 2024-11-29 12:56:57 +05:30
safwansamsudeen
189c750848 chore: update workflow 2024-11-29 12:55:51 +05:30
safwansamsudeen
a22c9dcc41 chore: publish new version, build online 2024-11-29 12:52:06 +05:30
safwansamsudeen
da04ea777f fix: dragging left bug 2024-11-29 12:32:33 +05:30
safwansamsudeen
0d357b03e0 chore: remove locks 2024-11-29 10:41:09 +05:30
Safwan Samsudeen
24e66dc1fb
Merge pull request #464 from frappe/dependabot/npm_and_yarn/rollup-4.27.4
chore(deps-dev): bump rollup from 4.17.1 to 4.27.4
2024-11-29 10:40:21 +05:30
safwansamsudeen
d3b4a2fa06 chore: format 2024-11-28 17:23:48 +05:30
safwansamsudeen
dd01835864 chore: run build files 2024-11-28 17:20:32 +05:30
Safwan Samsudeen
788a96f83c
Merge pull request #454 from moradlarbi/customViews
feat: add support for custom time views
2024-11-28 17:20:10 +05:30
Safwan Samsudeen
a08de40f47
Merge branch 'master' into customViews 2024-11-28 17:19:08 +05:30
Morad larbi messaoudi
e4df4fe30a feat: add support for custom views with units(hour,day,month) 2024-11-28 11:38:54 +00:00
safwansamsudeen
d7b2051b7a feat: freeze dependency option and scroll bar bug 2024-11-28 17:06:22 +05:30
dependabot[bot]
242df789c9
chore(deps-dev): bump rollup from 4.17.1 to 4.27.4
Bumps [rollup](https://github.com/rollup/rollup) from 4.17.1 to 4.27.4.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.17.1...v4.27.4)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-28 11:03:54 +00:00
safwansamsudeen
bdcf9f921b chore: update lock 2024-11-28 16:32:25 +05:30
safwansamsudeen
53c27c950c chore: add pnpm install 2024-11-28 16:31:06 +05:30
safwansamsudeen
45f02df52d support popup on click 2024-11-28 16:25:30 +05:30
safwansamsudeen
75bf77ed5d tooling part 2 2024-11-28 15:03:29 +05:30
Safwan Samsudeen
ff57ae2cb6
Merge pull request #462 from frappe/dependabot/npm_and_yarn/multi-166fd8f40b
chore(deps): bump cross-spawn, eslint and jest
2024-11-28 15:00:19 +05:30
Safwan Samsudeen
629d55e2bc
Merge branch 'master' into chore/import-directive-webpack 2024-11-28 14:55:44 +05:30
dependabot[bot]
3461888194
chore(deps): bump cross-spawn, eslint and jest
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) to 7.0.6 and updates ancestor dependencies [cross-spawn](https://github.com/moxystudio/node-cross-spawn), [eslint](https://github.com/eslint/eslint) and [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest). These dependencies need to be updated together.


Updates `cross-spawn` from 5.1.0 to 7.0.6
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/5.1.0...v7.0.6)

Updates `eslint` from 4.17.0 to 9.15.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v4.17.0...v9.15.0)

Updates `jest` from 22.2.1 to 29.7.0
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v29.7.0/packages/jest)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
- dependency-name: eslint
  dependency-type: direct:development
- dependency-name: jest
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-28 09:22:49 +00:00
Safwan Samsudeen
ced1080931 chore: correct tooling 2024-11-28 14:50:59 +05:30
Safwan Samsudeen
de53f780b7 chore: format 2024-11-28 14:33:17 +05:30
Safwan Samsudeen
bc791491b1
Merge pull request #443 from frappe/dependabot/npm_and_yarn/rollup-4.22.4
chore(deps): bump rollup from 4.17.1 to 4.22.4
2024-10-15 10:11:02 +05:30
Safwan Samsudeen
026bc31e14
Merge pull request #440 from tagliala/chore/fix-docs
Fix custom popup html documentation for v0.8
2024-10-15 10:10:42 +05:30
Geremia Taglialatela
c88e8fce11
Fix regression with webpack
Add some entries to `package.json` to improve compatibility with webpack

- Add `import` export as per webpack requirements
- Add `style` export for CSS file
- Add `*.css` to "side effects" to prevent issues with tree-shaking

With this commit, it is possible to import frappe-gantt without breaking
changes in webpack-based applications

Refs:
- https://webpack.js.org/guides/package-exports/

Close #439
2024-09-24 09:44:15 +02:00
dependabot[bot]
0ea8d48d3a
chore(deps): bump rollup from 4.17.1 to 4.22.4
Bumps [rollup](https://github.com/rollup/rollup) from 4.17.1 to 4.22.4.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.17.1...v4.22.4)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-24 00:43:15 +00:00
Geremia Taglialatela
dfe4907b3e
Fix custom popup html documentation for v0.8
Ref: #393
2024-09-22 19:59:24 +02:00
Safwan Samsudeen
5bead3a4ff
Merge pull request #434 from frappe/dependabot/npm_and_yarn/vite-5.2.14
chore(deps-dev): bump vite from 5.2.10 to 5.2.14
2024-09-19 19:30:58 +05:30
Safwan Samsudeen
b834da1ad5
Merge pull request #437 from thomasync/fix-today-button
fix: check if today_button is defined before use it
2024-09-19 19:28:52 +05:30
thomasync
e5a3a3bfca fix: check if today_button is defined before use it 2024-09-19 14:19:10 +02:00
Safwan Samsudeen
6f97f816f1 fix: bug in higher view modes 2024-09-18 18:07:39 +05:30
Safwan Samsudeen
3e197c2369 feat: allow accessing this 2024-09-18 17:54:23 +05:30
dependabot[bot]
1d58c547a9
chore(deps-dev): bump vite from 5.2.10 to 5.2.14
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.10 to 5.2.14.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.2.14/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.2.14/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 12:13:07 +00:00
Safwan Samsudeen
78e7b2556e chore: bump version for tooling 2024-09-18 17:41:32 +05:30
Safwan Samsudeen
fc97f3121c fix: make changes to tooling 2024-09-18 17:41:32 +05:30
Safwan Samsudeen
1cd9456e25
Update README.md 2024-09-18 17:36:58 +05:30
Safwan Samsudeen
d6e8700eab fix: package version 2024-09-11 21:10:06 +05:30
Safwan Samsudeen
31551467d1 fix: remove test for now 2024-09-11 21:08:01 +05:30
Safwan Samsudeen
350ff7f0c3 chore: format 2024-09-11 21:05:20 +05:30
Safwan Samsudeen
4e09e0231d fix: bump even higher 2024-09-11 21:03:24 +05:30
Safwan Samsudeen
fd44499b60 fix: bump node version 2024-09-11 21:02:27 +05:30
Safwan Samsudeen
66e2b1db5f
fix: change yarn version 2024-09-11 20:59:27 +05:30
Safwan Samsudeen
af206ebbd7 fix: changes from #397 2024-09-11 20:51:53 +05:30
Safwan Samsudeen
faf41b7220 fix: use css ids that don't start with number 2024-09-11 20:49:06 +05:30
Safwan Samsudeen
f8d77b2a73 fix: today highlight bug 2024-09-11 20:40:08 +05:30
Safwan Samsudeen
0074a092a5 fix: bump node version 2024-09-11 20:34:28 +05:30
Safwan Samsudeen
f4bfa43a57
Merge pull request #422 from safwansamsudeen/master
fix: use css to align lower text dates
2024-09-11 20:33:17 +05:30
Safwan Samsudeen
aa33ac9b4d fix: use css to align lower text dates 2024-09-11 20:28:04 +05:30
Safwan Samsudeen
22ff6e202e
Merge pull request #394 from safwansamsudeen/master
Tooling and Bug Fixes
2024-09-11 20:19:52 +05:30
Safwan Samsudeen
9e64337ea8 chore: format, license, bump version 2024-09-11 20:19:18 +05:30
Safwan Samsudeen
daaf0d3be2 fix: resize bugs 2024-09-11 20:11:01 +05:30
Safwan Samsudeen
28932ee6c9 fix: make sure Gantt works as before 2024-09-11 19:57:53 +05:30
Safwan Samsudeen
5c212725fc fix: button background 2024-06-17 18:54:57 +05:30
Safwan Samsudeen
35d810ba7c Merge branch 'master' of https://github.com/safwansamsudeen/gantt 2024-06-17 18:50:25 +05:30
Safwan Samsudeen
73aa284e05 fix: couple bugs 2024-06-17 18:44:26 +05:30
Safwan Samsudeen
e648769ec5 fix: more bugs 2024-05-02 16:05:08 +05:30
Safwan Samsudeen
30a8d5a78b fix: a bug 2024-05-02 15:06:04 +05:30
Safwan Samsudeen
9cd3633dd7 chore: format 2024-04-30 17:51:02 +05:30
Safwan Samsudeen
5e18b534bf fix: change progress icon 2024-04-30 17:49:54 +05:30
Safwan Samsudeen
af0276c152 fix: timezone issues 2024-04-30 17:43:01 +05:30
Safwan Samsudeen
b29076afed fix: firefox bugs 2024-04-30 17:39:03 +05:30
Safwan Samsudeen
6ce4da519b fix: multiple bug fixes, more config 2024-04-30 17:23:43 +05:30
Faris Ansari
b7deffd539
Merge pull request #393 from safwansamsudeen/master
Rewrite Framework
2024-04-30 16:52:07 +05:30
Safwan Samsudeen
e151483f6d fix: correct safari bug 2024-04-30 06:49:49 +05:30
Safwan Samsudeen
a65bcff5c1 feat: first round of tooling upgrades 2024-04-30 06:24:33 +05:30
Safwan Samsudeen
3194f883ad chore: format 2024-04-30 05:06:17 +05:30
Safwan Samsudeen
1403f2e581 feat: popup, build 2024-04-28 18:53:42 +05:30
Safwan Samsudeen
55be33cfa9 feat: modify lower text option 2024-04-28 18:09:44 +05:30
Safwan Samsudeen
99261dfe1a feat: popup after delay
chore: build
2024-04-28 18:06:47 +05:30
Safwan Samsudeen
e9931bde24 feat: improved progress handle 2024-04-28 17:46:01 +05:30
Safwan Samsudeen
8bdd488618 fix: improve view mode change, side header 2024-04-28 17:22:47 +05:30
Safwan Samsudeen
901f78e717 feat: align design with figma 2024-04-28 17:01:27 +05:30
Safwan Samsudeen
0cefc57624 fix: date higlight 2024-04-27 16:58:41 +05:30
Safwan Samsudeen
a72b75a2f1 chore: build 2024-04-26 16:56:24 +05:30
Safwan Samsudeen
86db2221ff Merge branch 'new-ui' 2024-04-26 16:56:15 +05:30
Safwan Samsudeen
e7c735db68 fix: date highlight, aligning
chore: build
2024-04-25 21:08:04 +05:30
Safwan Samsudeen
0ee9b6ad69 feat: auto change view mode, fix multiple bugs 2024-04-25 20:28:59 +05:30
Safwan Samsudeen
2fd3aefd79 feat: today button, improved scroll
chore: build
2024-04-23 20:29:40 +05:30
Safwan Samsudeen
dd28b8ee0e feat: fixed header in html 2024-04-23 20:10:08 +05:30
Safwan Samsudeen
35966f0f3d fix: improve box shadow 2024-04-23 19:31:16 +05:30
Safwan Samsudeen
25d55e156b fix: double click issue 2024-04-22 20:23:55 +05:30
Safwan Samsudeen
47fe68eb4b feat: improved popup, switch handle and popup 2024-04-22 20:17:41 +05:30
Safwan Samsudeen
efb632fb23 replace outline with box shadow 2024-04-22 19:38:22 +05:30
Safwan Samsudeen
591b599b51 fix: date highlight resizes 2024-04-22 19:28:38 +05:30
Safwan Samsudeen
ecbec30fd7 feat: configurable lines 2024-04-22 19:19:35 +05:30
Safwan Samsudeen
ab9ecf753f chore: build 2024-04-20 11:54:03 +05:30
Safwan Samsudeen
0e6e0154b4 feat: enhanced popup 2024-04-20 11:53:18 +05:30
Safwan Samsudeen
4f66e83eaa feat: improved coloring scheme 2024-04-20 11:11:43 +05:30
Safwan Samsudeen
769233a71a feat: date highlight, handles out, improved font 2024-04-20 10:14:04 +05:30
Safwan Samsudeen
85c943b191 chore: build, make thick strokes more prominent 2024-04-14 18:28:52 +05:30
Safwan Samsudeen
0ebea3d1b8 feat: scroll to today 2024-04-14 18:27:51 +05:30
Safwan Samsudeen
94d2cf5772 chore: replace var with let, improve demo, build 2024-04-14 17:59:53 +05:30
Safwan Samsudeen
c13c9c08ec feat: add extra events 2024-04-14 17:53:19 +05:30
Safwan Samsudeen
c815dcd31a fix: progress bug, deselect bar by double click 2024-04-14 17:21:01 +05:30
Safwan Samsudeen
0afbf76881 fix: bug in month view label alignment 2024-04-14 13:47:52 +05:30
Safwan Samsudeen
4c54ea847a fix: year mode proper 2024-04-14 13:39:47 +05:30
Safwan Samsudeen
4c09eccef5 fix: bug in half day view 2024-04-14 13:14:55 +05:30
Safwan Samsudeen
b42a63c547 fix: replace with replace all 2024-04-14 12:57:16 +05:30
Safwan Samsudeen
e0bcc333ee fix: bars disappearing bug 2024-04-10 23:21:48 +05:30
Safwan Samsudeen
889a03a828 chore: consistently use strict equality operator 2024-04-10 23:06:25 +05:30
Safwan Samsudeen
9401b35139 feat: fixed header, thumbnail, movable label;
bug of bar task id fixed
2024-04-10 23:02:25 +05:30
Safwan Samsudeen
b8bad72109 feat: highlight weekends, build dist 2024-04-08 03:20:28 +05:30
Safwan Samsudeen
bbbf28819b feat: allow readonly 2024-04-08 02:09:14 +05:30
Safwan Samsudeen
ea6259adce
Merge pull request #387 from safwansamsudeen/master
New Features
2024-04-06 16:49:35 +05:30
Safwan Samsudeen
0b235d3397 feat: support empty task list 2024-04-05 16:37:13 +05:30
Safwan Samsudeen
bd7ce02c85 fix: timezone bug 2024-04-05 16:26:21 +05:30
Safwan Samsudeen
14f1770b53 fix: bug in start date 2024-04-05 16:20:35 +05:30
Safwan Samsudeen
93bf95d85d
Merge pull request #388 from frappe/dependabot/npm_and_yarn/async-2.6.4
chore(deps): bump async from 2.6.0 to 2.6.4
2024-04-05 16:04:28 +05:30
Safwan Samsudeen
533b9ea8fb
Merge pull request #390 from frappe/dependabot/npm_and_yarn/qs-6.5.3
chore(deps): bump qs from 6.5.1 to 6.5.3
2024-04-05 16:04:07 +05:30
dependabot[bot]
b0d3fdb51b
chore(deps): bump qs from 6.5.1 to 6.5.3
Bumps [qs](https://github.com/ljharb/qs) from 6.5.1 to 6.5.3.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.5.1...v6.5.3)

---
updated-dependencies:
- dependency-name: qs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 10:32:01 +00:00
dependabot[bot]
bcd52bf363
chore(deps): bump async from 2.6.0 to 2.6.4
Bumps [async](https://github.com/caolan/async) from 2.6.0 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.0...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 10:31:03 +00:00
Safwan Samsudeen
8dad3412d0
Merge pull request #385 from frappe/dependabot/npm_and_yarn/semver-5.7.2
chore(deps): bump semver from 5.5.0 to 5.7.2
2024-04-05 16:00:44 +05:30
Safwan Samsudeen
bd6784a641
Merge pull request #384 from frappe/dependabot/npm_and_yarn/fsevents-1.2.13
chore(deps): bump fsevents from 1.1.3 to 1.2.13
2024-04-05 16:00:35 +05:30
Safwan Samsudeen
f9da6ad42f chore: format (add prettier), fix minor bug, build 2024-04-05 15:39:04 +05:30
Safwan Samsudeen
a0c7cd36bd feat: allow customizable padding 2024-04-05 15:23:02 +05:30
Safwan Samsudeen
a4f7b727fe feat: allow hour mode 2024-04-05 14:53:50 +05:30
dependabot[bot]
cac6a4f116
chore(deps): bump fsevents from 1.1.3 to 1.2.13
Bumps [fsevents](https://github.com/fsevents/fsevents) from 1.1.3 to 1.2.13.
- [Release notes](https://github.com/fsevents/fsevents/releases)
- [Commits](https://github.com/fsevents/fsevents/compare/v1.1.3...v1.2.13)

---
updated-dependencies:
- dependency-name: fsevents
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 09:20:52 +00:00
Safwan Samsudeen
ddf2712531
Merge pull request #386 from frappe/dependabot/npm_and_yarn/handlebars-4.7.8
chore(deps): bump handlebars from 4.0.11 to 4.7.8
2024-04-05 14:49:56 +05:30
dependabot[bot]
e5717faaf8
chore(deps): bump handlebars from 4.0.11 to 4.7.8
Bumps [handlebars](https://github.com/handlebars-lang/handlebars.js) from 4.0.11 to 4.7.8.
- [Release notes](https://github.com/handlebars-lang/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/v4.7.8/release-notes.md)
- [Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.0.11...v4.7.8)

---
updated-dependencies:
- dependency-name: handlebars
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 09:03:57 +00:00
dependabot[bot]
51c717c3f9
chore(deps): bump semver from 5.5.0 to 5.7.2
Bumps [semver](https://github.com/npm/node-semver) from 5.5.0 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.5.0...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 09:03:41 +00:00
Safwan Samsudeen
a60d93cc16
Merge pull request #303 from frappe/dependabot/npm_and_yarn/minimatch-3.1.2
Bump minimatch from 3.0.4 to 3.1.2
2024-04-05 14:32:47 +05:30
Safwan Samsudeen
01082de642
Merge pull request #305 from frappe/dependabot/npm_and_yarn/terser-5.16.1
Bump terser from 5.13.0 to 5.16.1
2024-04-05 14:32:41 +05:30
Safwan Samsudeen
0f15ab1875 feat: allow duration instead of end_time 2024-04-05 14:19:19 +05:30
Faris Ansari
c34c09e837
Merge pull request #382 from safwansamsudeen/master
Minor Fixes
2024-04-04 19:34:19 +05:30
Safwan Samsudeen
7670d13634 fix: cut off starts 2024-04-04 17:39:33 +05:30
Safwan Samsudeen
0cc5a3cbf6 fix: inconsistent time display 2024-04-04 17:36:38 +05:30
Safwan Samsudeen
218818619c fix: progress polygon when progress = 0 2024-04-04 17:16:02 +05:30
Safwan Samsudeen
6e2f91db25 fix: first date disappearing 2024-04-04 17:11:13 +05:30
Safwan Samsudeen
d886100221 fix: all text not selectable, add safari support 2024-04-04 17:08:48 +05:30
Safwan Samsudeen
c880b72fa6 fix: bar not resizable if <1 unit 2024-04-04 17:06:22 +05:30
Rushabh Mehta
05b243f3f6
Merge pull request #377 from BHznJNs/master
feat: add dark mode support
2024-03-27 14:24:26 +05:30
Rushabh Mehta
ce43bb9b66
Merge branch 'master' into master 2024-03-27 14:24:18 +05:30
Rushabh Mehta
4ba1566ceb
Merge pull request #365 from GursheenK/expected-progress-bars
feat: expected progress bars
2024-03-27 14:19:52 +05:30
Rushabh Mehta
74bda8bef0
Merge pull request #381 from Deephacks619/grid-highlight
Feat: Add Grid highlight for current week, month, year
2024-03-26 21:21:47 +05:30
Deepak Kyatham
02aaf6a02f changes 2024-03-26 16:34:04 +05:30
Deepak Kyatham
6cb5c12fb7 Feat:Add Grid highlight for current week, month, year 2024-03-26 16:23:49 +05:30
BHznJNs
be1c875222 feat: add dark mode support 2024-02-04 20:26:49 +08:00
Gursheen Anand
c4e1553e55 chore: define colour for expected progress bar 2023-11-20 00:55:45 +05:30
Gursheen Anand
916fd15255 feat: option to enable expected progress bars 2023-11-20 00:54:49 +05:30
Sagar Sharma
1d024dcb55
Merge pull request #252 from stephanvierkant/patch-1
Use Intl.DateTimeFormat to translate month names
2023-06-08 13:54:56 +05:30
Stephan Vierkant
ddb35724cb chore: use Intl.DateTimeFormat to translate month names 2023-06-08 13:51:50 +05:30
Sagar Sharma
0381503301
Merge pull request #307 from s-aga-r/feat/languages
feat: add support for `german` and `hungarian` languages
2023-01-07 16:00:04 +05:30
s-aga-r
7381b84c76 chore: update language options in README.md 2023-01-07 15:59:35 +05:30
s-aga-r
342e97b56a feat: add support for german and hungarian languages 2023-01-07 15:56:35 +05:30
Sagar Sharma
bf80b07b42
Merge pull request #306 from s-aga-r/it-language-in-readme
chore: update `language` options in `README.md`
2023-01-04 22:13:20 +05:30
s-aga-r
e014234700 chore: update language options in README.md 2023-01-04 22:11:49 +05:30
Sagar Sharma
743f84f49d
Merge pull request #242 from SergAlexAnd/master
Add Italian month names
2023-01-04 19:51:01 +05:30
Sagar Sharma
80458f69ed
Merge pull request #271 from hiawui/bugfix-invalid-bar
fix: exception when moving invalid bar
2023-01-04 19:42:22 +05:30
Sagar Sharma
94fdb10f1b
Merge pull request #279 from hiawui/bugfix-month-mode-thick-tick
fix: wrong thick stick in view mode 'Month'
2023-01-04 18:47:09 +05:30
Sagar Sharma
228782a8a4
Merge pull request #292 from ThibaultBarolat/add-language-option-doc
Add language option in README
2023-01-04 11:03:56 +05:30
s-aga-r
73871b039e chore: add language option in README.md 2023-01-04 11:02:45 +05:30
Sagar Sharma
33cb61533e
Merge pull request #302 from DennisAnim/dennisGantt-Repo
chore: `index.html`
2023-01-04 10:34:40 +05:30
s-aga-r
fc08a80d19 chore: use arrow functions in index.html 2023-01-04 10:33:23 +05:30
s-aga-r
781578c803 chore: align heading center in index.html 2023-01-04 10:33:06 +05:30
dependabot[bot]
a4197ac01b
Bump terser from 5.13.0 to 5.16.1
Bumps [terser](https://github.com/terser/terser) from 5.13.0 to 5.16.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/compare/v5.13.0...v5.16.1)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-02 06:52:59 +00:00
dependabot[bot]
3bca39dcca
Bump minimatch from 3.0.4 to 3.1.2
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.0.4 to 3.1.2.
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-02 06:52:54 +00:00
Sagar Sharma
372b1e8524
Merge pull request #238 from frappe/dependabot/npm_and_yarn/tar-2.2.2
Bump tar from 2.2.1 to 2.2.2
2023-01-02 12:22:07 +05:30
Sagar Sharma
da4b313dd8
Merge pull request #239 from frappe/dependabot/npm_and_yarn/path-parse-1.0.7
Bump path-parse from 1.0.5 to 1.0.7
2023-01-02 12:20:27 +05:30
Sagar Sharma
bfadf6598b
Merge pull request #161 from frappe/dependabot/npm_and_yarn/sshpk-1.16.1
Bump sshpk from 1.13.1 to 1.16.1
2023-01-02 12:17:18 +05:30
Sagar Sharma
9ce1e995a2
Merge pull request #241 from frappe/dependabot/npm_and_yarn/tmpl-1.0.5
Bump tmpl from 1.0.4 to 1.0.5
2022-12-30 11:39:03 +05:30
Sagar Sharma
626bf6ab65
Merge pull request #228 from frappe/dependabot/npm_and_yarn/hosted-git-info-2.8.9
Bump hosted-git-info from 2.5.0 to 2.8.9
2022-12-30 11:37:53 +05:30
Sagar Sharma
d12cc0d24e
Merge pull request #227 from frappe/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.5 to 4.17.21
2022-12-30 11:37:30 +05:30
Sagar Sharma
1e1498f394
Merge pull request #218 from frappe/dependabot/npm_and_yarn/y18n-3.2.2
Bump y18n from 3.2.1 to 3.2.2
2022-12-30 11:36:51 +05:30
Sagar Sharma
cd56d6eeb1
Merge pull request #159 from frappe/dependabot/npm_and_yarn/merge-1.2.1
Bump merge from 1.2.0 to 1.2.1
2022-12-30 11:35:32 +05:30
Sagar Sharma
351fce3a41
Merge pull request #158 from frappe/dependabot/npm_and_yarn/extend-3.0.2
Bump extend from 3.0.1 to 3.0.2
2022-12-30 11:34:29 +05:30
Sagar Sharma
469dfa3155
Merge pull request #267 from frappe/dependabot/npm_and_yarn/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8
2022-12-30 11:33:27 +05:30
Sagar Sharma
073bc25df3
Merge pull request #301 from frappe/dependabot/npm_and_yarn/qs-6.4.1
Bump qs from 6.4.0 to 6.4.1
2022-12-29 11:46:36 +05:30
dependabot[bot]
393b00639c
Bump qs from 6.4.0 to 6.4.1
Bumps [qs](https://github.com/ljharb/qs) from 6.4.0 to 6.4.1.
- [Release notes](https://github.com/ljharb/qs/releases)
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.4.0...v6.4.1)

---
updated-dependencies:
- dependency-name: qs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-07 08:35:11 +00:00
hiawui
c6e0b50ff5 fix: wrong thick stick in view mode 'Month' 2022-06-29 12:19:45 +08:00
hiawui
481896e392 fix: exception when moving invalid bar 2022-06-24 20:18:13 +08:00
dependabot[bot]
e095e4c028
Bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/npm/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/npm/ini/releases)
- [Changelog](https://github.com/npm/ini/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/ini/compare/v1.3.5...v1.3.8)

---
updated-dependencies:
- dependency-name: ini
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-28 04:55:36 +00:00
dependabot[bot]
b0384645d6
Bump tar from 2.2.1 to 2.2.2
Bumps [tar](https://github.com/npm/node-tar) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v2.2.1...v2.2.2)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-28 04:55:34 +00:00
dependabot[bot]
9744be6f6f
Bump tmpl from 1.0.4 to 1.0.5
Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/daaku/nodejs-tmpl/releases)
- [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5)

---
updated-dependencies:
- dependency-name: tmpl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-28 04:55:31 +00:00
dependabot[bot]
5b3268737b
Bump lodash from 4.17.5 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.5 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.5...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-28 04:55:31 +00:00
dependabot[bot]
8b396e4d50
Bump extend from 3.0.1 to 3.0.2
Bumps [extend](https://github.com/justmoon/node-extend) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/justmoon/node-extend/releases)
- [Changelog](https://github.com/justmoon/node-extend/blob/master/CHANGELOG.md)
- [Commits](https://github.com/justmoon/node-extend/compare/v3.0.1...v3.0.2)

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-28 04:55:29 +00:00
dependabot[bot]
9cf9ebb967
Bump merge from 1.2.0 to 1.2.1
Bumps [merge](https://github.com/yeikos/js.merge) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/yeikos/js.merge/releases)
- [Commits](https://github.com/yeikos/js.merge/compare/v1.2.0...v1.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-28 04:55:21 +00:00
Faris Ansari
76be9de801 v0.6.1 2022-04-28 10:24:18 +05:30
Faris Ansari
6233b6cd4b chore: update rollup config
- minify css
- sourcemap
- github action to auto-publish on npm
2022-04-28 10:23:58 +05:30
Faris Ansari
aba84bd715 style: prettier formatting 2022-04-28 10:14:39 +05:30
Faris Ansari
9d01fa85b7 chore: add publishing steps in readme 2022-01-17 16:04:43 +05:30
Faris Ansari
4cf30c6630 v0.6.0 2022-01-17 15:50:47 +05:30
Shariq Ansari
3371a57ed0
fix: hide popup while resizing or dragging events (#249) 2022-01-11 11:55:05 +05:30
Aleksei Sergienko
3270f34cf1 (feature): add italian month names 2021-10-14 10:55:23 +03:00
dependabot[bot]
a7ba98810c
Bump path-parse from 1.0.5 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.5 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-10 23:50:05 +00:00
dependabot[bot]
05f4d069a6
Bump hosted-git-info from 2.5.0 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.5.0 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.5.0...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-10 07:32:24 +00:00
dependabot[bot]
3dc56ddd3f
Bump y18n from 3.2.1 to 3.2.2
Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-30 18:03:09 +00:00
Faris Ansari
8f0b83d27d v0.5.0 2020-06-19 18:33:05 +05:30
dependabot[bot]
229a0a5893
Bump sshpk from 1.13.1 to 1.16.1
Bumps [sshpk](https://github.com/joyent/node-sshpk) from 1.13.1 to 1.16.1.
- [Release notes](https://github.com/joyent/node-sshpk/releases)
- [Commits](https://github.com/joyent/node-sshpk/compare/v1.13.1...v1.16.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-19 12:44:48 +00:00
Faris Ansari
9ac0f0257e v0.4.0 2020-06-19 18:13:39 +05:30
Faris Ansari
bb755e6b42 chore: Update rollup-plugin-sass 2020-06-19 18:12:25 +05:30
Faris Ansari
572f463a0b fix: Separate popup trigger and click event 2020-06-19 18:12:23 +05:30
gavin
318ea68107
Revert "feat: Support Japanese (#162)" (#171)
This reverts commit 94f1cd795810ee44b5b04cf48f2209f8487c8bf0.
2020-03-16 13:29:00 +05:30
TakanashiAsuka
94f1cd7958
feat: Support Japanese (#162)
Added support for Japanese language

* Support Japanese
* Update frappe-gantt.js
2020-02-17 11:18:04 +05:30
gavin
d72019bb8a
Merge pull request #164 from zhihuahuang/feat-language-zh-and-view-mode-constant
Feat language zh and view mode constant
2020-02-17 11:15:02 +05:30
huangzhihua
fa22dc7895 feat: Add VIEW_MODE constant 2020-01-09 11:25:40 +08:00
huangzhihua
99781a8274 feat: Add zh month language 2020-01-08 21:58:32 +08:00
Mustafa Hayri ÜNEŞİ
4c130206c4 Add TR language (#156) 2020-01-06 12:16:05 +05:30
Jeroen Rinzema
dd18120d30 fix: broken image link (#153)
updated image source of the frappe gantt logo
2019-11-24 22:06:11 +05:30
diegodurrod
08edaa3d22 Fixed language (#131)
Fixed language when English is not the default language
2019-04-29 12:59:50 +05:30
Anurag Mishra
1f5d842ddb
Merge pull request #116 from St7s/patch-1
add ptBr month
2019-04-26 13:42:36 +05:30
Anurag Mishra
17ccc27a9a
Merge branch 'master' into patch-1 2019-04-26 13:39:47 +05:30
Anurag Mishra
8f6818bc8b
Merge pull request #124 from Antholoj/master
Add option to show months in fr
2019-04-26 12:53:43 +05:30
Anurag Mishra
309c59e6e2
Merge pull request #130 from diegodurrod/patch-1
Added Spanish (es) date language
2019-04-26 12:48:39 +05:30
diegodurrod
6061ae755d
Added Spanish (es) date language 2019-04-22 10:53:54 +02:00
Daoud
211a18666e Add option to show months in fr 2019-03-01 13:52:48 +01:00
St7s
41ea97b4dd
add fr month 2019-01-10 09:01:30 +01:00
Faris Ansari
13f579f4b6
Merge pull request #106 from davidalves1/patch-1
Add option to show months in pt-BR
2018-10-20 23:54:16 +05:30
David Alves de Souza
251e8c25f5
Add option to show months in pt-BR 2018-10-04 17:32:54 -03:00
Faris Ansari
9e3c490196 v0.3.0 2018-09-23 19:49:24 +05:30
Faris Ansari
8447e15225
Merge pull request #96 from vlexz/master
year view mode, localization for month names
2018-09-23 19:45:03 +05:30
Faris Ansari
c5154fc9eb Merge branch 'master' of https://github.com/vlexz/gantt into vlexz-master 2018-09-23 19:44:12 +05:30
Faris Ansari
48ea5ad5a2 Fix formatting 2018-09-23 19:43:12 +05:30
Faris Ansari
50386745ca
Merge branch 'master' into master 2018-09-23 19:41:09 +05:30
Faris Ansari
81622e434b
Merge pull request #99 from kannkyo/master
#98 extend time format to use milliseconds
2018-08-18 21:36:00 +05:30
kannkyo
2712ac3201 #98 extend time format to use milliseconds 2018-08-17 00:11:28 +09:00
Oleksii Voznosymenko
af45438ba9 change default language to english in demo code 2018-08-13 16:29:18 +03:00
Oleksii Voznosymenko
69978e00d2 removed commented code 2018-08-08 23:34:23 +03:00
Oleksii Voznosymenko
1b36ec9464 russian localization for month names 2018-08-08 23:15:43 +03:00
Oleksii Voznosymenko
4995e9ed0e added year display mode 2018-08-08 22:17:02 +03:00
Faris Ansari
4b11841bc1 v0.2.0 2018-07-08 12:25:00 +05:30
Faris Ansari
49e6880252 [fix] element argument should support HTML and SVG element 2018-07-08 12:23:13 +05:30
Rushabh Mehta
a7e61db72f
Merge pull request #79 from PlethoraLabs/master
Adding CSS file and options example in README.md
2018-06-13 14:07:51 +05:30
Kostas Minaidis
e2a9185dac
Adding CSS file and options example 2018-05-29 20:20:18 +03:00
Anto Christopher
0c39cc9b4e Add feature to change popup trigger event (#77)
* Add change-popup-mode

* Add feature to change popup trigger event

* Remove example of change_popup_mode from index

* Revert back to the original build files

* Make changes asked in review

* Remove default to click from bind event as default already set

* code formatting
2018-05-23 12:36:43 +05:30
Faris Ansari
98f38e1684 [fix] custom_html_popup
- fixes #60
2018-05-20 20:27:34 +05:30
Faris Ansari
94409f0bb8 v0.1.1 2018-05-20 20:04:17 +05:30
Faris Ansari
ec5e559b5c Fixes
- end_date calculation
- hide_popup on grid_click
- bar position should also consider hours
- date_change event only if date has changed
2018-05-20 20:03:18 +05:30
Faris Ansari
cd93fbf655 [fix] Incorrect formatted date
- #61
- problem: December being formatted as 01ecember
2018-05-19 21:40:03 +05:30
Faris Ansari
8f4214a926 [fix] Drag events not working on view mode change
- #61
2018-05-19 21:19:57 +05:30
İsmail Demirbilek
45d218994d fix date_utils add params (#65)
Please see https://github.com/frappe/gantt/blob/master/src/date_utils.js#L4
2018-04-01 14:29:47 +05:30
Carlos Jesús Huchim Ahumada
97591595ac Allow change scss variables (#66) 2018-04-01 14:26:15 +05:30
Faris Ansari
e8467917d7 Add overflow: auto to container 2018-03-05 12:04:51 +05:30
Faris Ansari
1680bb7338 Merge branch 'master' of github.com:frappe/gantt 2018-03-04 20:01:44 +05:30
Faris Ansari
ee1062c182 Don't show popup while dragging 2018-03-04 20:00:49 +05:30
Raghavendra Kamath
5a03973e06 Update the readme to add new Gantt logo (#53)
* Update the readme to add new Gantt logo

* Update README.md
2018-02-17 23:55:15 +05:30
Faris Ansari
24cb69dd4e Update popup position on drag 2018-02-17 23:47:31 +05:30
Faris Ansari
8c082e7bc9
[v0.1.0] Major Refactor (#57)
* [v0.1.0] Major Refactor

- Remove moment and Snap dependencies
- Use Rollup as build tool
- Use Prettier for linting/styling
- Use Jest for testing
- Use yarn

* Change Bar hover behaviour, Bar animation

* Corner radius configurable
2018-02-17 23:40:58 +05:30
38 changed files with 5557 additions and 7088 deletions

View File

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

View File

@ -1,7 +0,0 @@
{
"extends": ["plugin:prettier/recommended"],
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
}
}

BIN
.github/gantt-logo.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
.github/hero-image.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

23
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Publish on NPM
on:
push:
branches: [release]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'pnpm'
- run: pnpm install
- run: pnpm prettier-check
- run: pnpm build
- uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}

3
.gitignore vendored
View File

@ -21,11 +21,14 @@ coverage
# Compiled binary addons (http://nodejs.org/api/addons.html) # Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release build/Release
dist/*
# Dependency directory # Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules node_modules
.yarn
.DS_Store .DS_Store
gh-pages gh-pages
feedback*.md

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
dist

View File

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

162
README.md
View File

@ -1,46 +1,158 @@
# Frappé Gantt <div align="center" markdown="1">
A simple, interactive, modern gantt chart library for the web <img src=".github/gantt-logo.jpg" width="80">
<h1>Frappe Gantt</h1>
![image](https://cloud.githubusercontent.com/assets/9355208/21537921/4a38b194-cdbd-11e6-8110-e0da19678a6d.png) **A modern, configurable, Gantt library for the web.**
</div>
#### View the demo [here](https://frappe.github.io/gantt). ![Hero Image](.github/hero-image.png)
### Install ## Frappe Gantt
``` Gantt charts are bar charts that visually illustrate a project's tasks, schedule, and dependencies. With Frappe Gantt, you can build beautiful, customizable, Gantt charts with ease.
You can use it anywhere from hobby projects to tracking the goals of your team at the worksplace.
[ERPNext](https://erpnext.com/) uses Frappe Gantt.
### Motivation
We needed a Gantt View for ERPNext. Surprisingly, we couldn't find a visually appealing Gantt library that was open source - so we decided to build it. Initially, the design was heavily inspired by Google Gantt and DHTMLX.
### Key Features
- **Customizable Views**: customize the timeline based on various time periods - day, hour, or year, you have it. You can also create your own views.
- **Ignore Periods**: exclude weekends and other holidays from your tasks' progress calculation.
- **Configure Anything**: spacing, edit access, labels, you can control it all. Change both the style and functionality to meet your needs.
- **Multi-lingual Support**: suitable for companies with an international base.
## Usage
Install with:
```bash
npm install frappe-gantt npm install frappe-gantt
``` ```
### Usage Include it in your HTML:
Include it in your html:
``` ```html
<script src="frappe-gantt.min.js"></script> <script src="frappe-gantt.umd.js"></script>
<link rel="stylesheet" href="frappe-gantt.css">
``` ```
And start hacking: Or from the CDN:
```html
<script src="https://cdn.jsdelivr.net/npm/frappe-gantt/dist/frappe-gantt.umd.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/frappe-gantt/dist/frappe-gantt.css">
```
Start using Gantt:
```js ```js
var tasks = [ let tasks = [
{ {
id: 'Task 1', id: '1',
name: 'Redesign website', name: 'Redesign website',
start: '2016-12-28', start: '2016-12-28',
end: '2016-12-31', end: '2016-12-31',
progress: 20, progress: 20
dependencies: 'Task 2, Task 3',
custom_class: 'bar-milestone' // optional
}, },
... ...
] ]
var gantt = new Gantt("#gantt", tasks); let gantt = new Gantt("#gantt", tasks);
``` ```
If you want to contribute: ### Configuration
Frappe Gantt offers a wide range of options to customize your chart.
| **Option** | **Description** | **Possible Values** | **Default** |
|---------------------------|---------------------------------------------------------------------------------|----------------------------------------------------|------------------------------------|
| `arrow_curve` | Curve radius of arrows connecting dependencies. | Any positive integer. | `5` |
| `auto_move_label` | Move task labels when user scrolls horizontally. | `true`, `false` | `false` |
| `bar_corner_radius` | Radius of the task bar corners (in pixels). | Any positive integer. | `3` |
| `bar_height` | Height of task bars (in pixels). | Any positive integer. | `30` |
| `container_height` | Height of the container. | `auto` - dynamic container height to fit all tasks - _or_ any positive integer (for pixels). | `auto` |
| `column_width` | Width of each column in the timeline. | Any positive integer. | 45 |
| `date_format` | Format for displaying dates. | Any valid JS date format string. | `YYYY-MM-DD` |
| `upper_header_height` | Height of the upper header in the timeline (in pixels). | Any positive integer. | `45` |
| `lower_header_height` | Height of the lower header in the timeline (in pixels). | Any positive integer. | `30` |
| `snap_at` | Snap tasks at particular intervel while resizing or dragging. | Any _interval_ (see below) | `1d` |
| `infinite_padding` | Whether to extend timeline infinitely when user scrolls. | `true`, `false` | `true` |
| `holidays` | Highlighted holidays on the timeline. | Object mapping CSS colors to holiday types. Types can either be a) 'weekend', or b) array of _strings_ or _date objects_ or _objects_ in the format `{date: ..., label: ...}` | `{ 'var(--g-weekend-highlight-color)': 'weekend' }` |
| `ignore` | Ignored areas in the rendering | `weekend` _or_ Array of strings or date objects (`weekend` can be present to the array also). | `[]` |
| `language` | Language for localization. | ISO 639-1 codes like `en`, `fr`, `es`. | `en` |
| `lines` | Determines which grid lines to display. | `none` for no lines, `vertical` for only vertical lines, `horizontal` for only horizontal lines, `both` for complete grid. | `both` |
| `move_dependencies` | Whether moving a task automatically moves its dependencies. | `true`, `false` | `true` |
| `padding` | Padding around task bars (in pixels). | Any positive integer. | `18` |
| `popup_on` | Event to trigger the popup display. | `click` _or_ `hover` | `click` |
| `readonly_progress` | Disables editing task progress. | `true`, `false` | `false` |
| `readonly_dates` | Disables editing task dates. | `true`, `false` | `false` |
| `readonly` | Disables all editing features. | `true`, `false` | `false` |
| `scroll_to` | Determines the starting point when chart is rendered. | `today`, `start`, `end`, or a date string. | `today` |
| `show_expected_progress` | Shows expected progress for tasks. | `true`, `false` | `false` |
| `today_button` | Adds a button to navigate to todays date. | `true`, `false` | `true` |
| `view_mode` | The initial view mode of the Gantt chart. | `Day`, `Week`, `Month`, `Year`. | `Day` |
| `view_mode_select` | Allows selecting the view mode from a dropdown. | `true`, `false` | `false` |
Apart from these ones, two options - `popup` and `view_modes` (plural, not singular) - are available. They have "sub"-APIs, and thus are listed separately.
#### View Mode Configuration
The `view_modes` option determines all the available view modes for the chart. It should be an array of objects.
Each object can have the following properties:
- `name` (string) - the name of view mode.
- `padding` (interval) - the time above.
- `step` - the interval of each column
- `lower_text` (date format string _or_ function) - the format for text in lower header. Blank string for none. The function takes in `currentDate`, `previousDate`, and `lang`, and should return a string.
- `upper_text` (date format string _or_ function) - the format for text in upper header. Blank string for none. The function takes in `currentDate`, `previousDate`, and `lang`, and should return a string.
- `upper_text_frequency` (number) - how often the upper text has a value. Utilized in internal calculation to improve performance.
- `thick_line` (function) - takes in `currentDate`, returns Boolean determining whether the line for that date should be thicker than the others.
Three other options allow you to override general configuration for this view mode alone:
- `date_format`
- `column_width`
- `snap_at`
For details, see the above table.
#### Popup Configuration
`popup` is a function. If it returns
- `false`, there will be no popup.
- `undefined`, the popup will be rendered based on manipulation within the function
- a HTML string, the popup will be that string.
The function receives one object as an argument, containing:
- `task` - the task as an object
- `chart` - the entire Gantt chart
- `get_title`, `get_subtitle`, `get_details` (functions) - get the relevant section as a HTML node.
- `set_title`, `set_subtitle`, `set_details` (functions) - take in the HTML of the relevant section
- `add_action` (function) - accepts two parameters, `html` and `func` - respectively determining the HTML of the action and the callback when the action is pressed.
### API
Frappe Gantt exposes a few helpful methods for you to interact with the chart:
| **Name** | **Description** | **Parameters** |
|---------------------------|---------------------------------------------------------------------------------|------------------------------------------|
| `.update_options` | Re-renders the chart after updating specific options. | `new_options` - object containing new options. |
| `.change_view_mode` | Updates the view mode. | `view_mode` - Name of view mode _or_ view mode object (see above) and `maintain_pos` - whether to go back to current scroll position after rerendering, defaults to `false`. |
| `.scroll_current` | Scrolls to the current date | No parameters. |
| `.update_task` | Re-renders a specific task bar alone | `task_id` - id of task and `new_details` - object containing the task properties to be updated. |
## Development Setup
If you want to contribute enhancements or fixes:
1. Clone this repo. 1. Clone this repo.
2. `cd` into project directory 2. `cd` into project directory.
3. `npm install` 3. Run `pnpm i` to install dependencies.
4. `npm run dev` 4. `pnpm run build` to build files - or `pnpm run build-dev` to build and watch for changes.
5. Open `index.html` in your browser.
6. Make your code changes and test them.
License: MIT <br />
<br />
------------------ <div align="center" style="padding-top: 0.75rem;">
Project maintained by [frappe](https://github.com/frappe) <a href="https://frappe.io" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/Frappe-white.png">
<img src="https://frappe.io/files/Frappe-black.png" alt="Frappe Technologies" height="28"/>
</picture>
</a>
</div>

115
builder/demo.css Normal file
View File

@ -0,0 +1,115 @@
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 20px;
float: right;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ddd;
-webkit-transition: 0.2s;
transition: 0.2s;
border: 1px solid #37352f;
scale: 0.75;
}
.slider:before {
position: absolute;
content: '';
height: 12px;
width: 12px;
left: 4px;
bottom: 3px;
background-color: white;
-webkit-transition: 0.2s;
transition: 0.2s;
}
input:checked + .slider {
background-color: #7c7c7c;
border-color: #7c7c7c;
}
input:focus + .slider {
box-shadow: none;
}
input:checked + .slider:before {
-webkit-transform: translateX(28px);
-ms-transform: translateX(28px);
transform: translateX(28px);
}
.slider.round {
border-radius: 25px;
}
.slider.round:before {
border-radius: 50%;
}
.viewmode-select {
font-size: 100%;
}
.selected {
border: 1.5px solid black !important;
}
.button {
background: white;
border: 1px dotted black;
border-radius: 3px;
}
.button:hover {
background: #f4f5f6;
border: 1px dotted black;
}
.button div {
color: black;
}
.input-switch {
align-items: center;
width: 45%;
display: flex;
justify-content: space-between;
}
.input-switch label {
padding-right: 30px;
font-size: 14px;
}
.code {
display: block;
background: 0;
white-space: pre;
overflow-x: scroll;
max-width: 100%;
min-width: 100px;
padding: 0;
font-family: monospace;
padding-top: 0.8571429em;
padding-right: 1.1428571em;
padding-bottom: 0.8571429em;
padding-left: 1.1428571em;
background: #1f2937;
color: #e5e7eb;
border-radius: 3px;
}

326
builder/demo.js Normal file
View File

@ -0,0 +1,326 @@
const tasks = [
{
start: daysSince(-7),
end: daysSince(-5),
name: 'Initial brainstorming',
id: 'Task 0',
progress: random(),
},
{
start: daysSince(-3),
end: daysSince(1),
name: 'Develop wireframe',
id: 'Task 1',
progress: random(),
dependencies: 'Task 0',
},
{
start: daysSince(-1),
duration: '4d',
name: 'Client meeting',
id: 'Task 2',
progress: random(),
important: true,
},
{
start: daysSince(1),
duration: '7d',
name: 'Create prototype',
id: 'Task 3',
dependencies: 'Task 2',
progress: random(),
},
{
start: daysSince(3),
duration: '5d',
name: 'Test design with users',
dependencies: 'Task 2',
id: 'Task 4',
progress: random(),
important: true,
},
{
start: daysSince(5),
end: daysSince(10),
name: 'Write technical documentation',
id: 'Task 5',
progress: random(),
},
{
start: daysSince(8),
duration: '3d',
name: 'Prepare demo',
id: 'Task 6',
dependencies: 'Task 5',
progress: random(),
},
{
start: daysSince(10),
end: daysSince(12),
name: 'Final client review',
id: 'Task 7',
progress: 0,
important: true,
},
{
start: daysSince(14),
duration: '6d',
name: 'Implement feedback',
id: 'Task 8',
progress: 0,
},
];
const tasksSmall = [
{
start: daysSince(-2),
end: daysSince(2),
name: 'Redesign website',
id: 'Task 0',
progress: random(),
},
{
start: daysSince(3),
duration: '6d',
name: 'Write new content',
id: 'Task 1',
progress: random(),
important: true,
dependencies: 'Task 0',
},
{
start: daysSince(4),
duration: '2d',
name: 'Apply new styles',
id: 'Task 2',
progress: random(),
},
{
start: daysSince(-4),
end: daysSince(0),
name: 'Review',
id: 'Task 3',
progress: random(),
},
];
const tasksBlank = [
{
start: daysSince(1),
duration: '3d',
name: 'Marketing Strategy Review',
id: 'Task 1',
important: true,
},
{
start: daysSince(-2),
end: daysSince(12),
name: 'Mentor Sooriya',
id: 'Task 0',
},
{
start: daysSince(4),
end: daysSince(5),
name: 'Investors Meetup',
id: 'Task 3',
},
];
const HOLIDAYS = [
{ name: 'New Years Day', date: '2025-01-01' },
{ name: 'Republic Day', date: '2025-01-26' },
{ name: 'Maha Shivratri', date: '2025-02-23' },
{ name: 'Holi', date: '2025-03-11' },
{ name: 'Mahavir Jayanthi', date: '2025-04-07' },
{ name: 'Good Friday', date: '2025-04-10' },
{ name: 'May Day', date: '2025-05-01' },
{ name: 'Buddha Purnima', date: '2025-05-08' },
{ name: 'Krishna Janmastami', date: '2025-08-14' },
{ name: 'Independence Day', date: '2025-08-15' },
{ name: 'Ganesh Chaturthi', date: '2025-08-23' },
{ name: 'Id-Ul-Fitr', date: '2025-09-21' },
{ name: 'Vijaya Dashami', date: '2025-09-28' },
{ name: 'Mahatma Gandhi Jayanti', date: '2025-10-02' },
{ name: 'Diwali', date: '2025-10-17' },
{ name: 'Guru Nanak Jayanthi', date: '2025-11-02' },
{ name: 'Christmas', date: '2025-12-25' },
];
new Gantt('#central-demo', tasks, {
scroll_to: daysSince(-7),
infinite_padding: false,
});
const sideheader = new Gantt('#sideheader', tasksSmall, {
scroll_to: daysSince(-20),
view_mode_select: true,
infinite_padding: false,
});
const popup = new Gantt('#popup', tasksBlank, {
scroll_to: daysSince(-7),
infinite_padding: false,
container_height: 350,
popup: (ctx) => {
ctx.set_title(ctx.task.name);
let title = ctx.get_title();
title.style.border = '0.5px solid black';
title.style.borderRadius = '1.5px';
title.style.padding = '3px 5px ';
title.style.backgroundColor = 'black';
title.style.opacity = '0.85';
title.style.color = 'white';
title.style.width = 'fit-content';
title.onclick = () => {
let ans = prompt('New Title: ');
if (ans) ctx.set_title(ans);
};
if (ctx.task.description) ctx.set_subtitle(ctx.task.description);
else ctx.set_subtitle('');
ctx.set_details(
`<em>Duration</em>: ${ctx.task.actual_duration} days<br/><em>Dates</em>: ${ctx.task._start.toLocaleDateString('en-US')} - ${ctx.task._end.toLocaleDateString('en-US')}`,
);
let details = ctx.get_details();
details.style.lineHeight = '1.75';
details.style.margin = '10px 4px';
if (!ctx.chart.options.readonly) {
if (!ctx.chart.options.readonly_progress) {
ctx.add_action('Set Color', (task, chart) => {
const bar = chart.bars.find(
({ task: t }) => t.id === task.id,
).$bar;
bar.style.fill = `hsla(${~~(360 * Math.random())}, 70%, 72%, 0.8)`;
});
}
}
},
});
const holidays = new Gantt('#holidays', tasks, {
holidays: {
'var(--g-weekend-highlight-color)': [],
'#fffddb': HOLIDAYS,
},
ignore: ['weekend'],
infinite_padding: false,
container_height: 350,
scroll_to: daysSince(-7),
});
SWITCHES = {
'sideheader-form': {
'toggle-today': 'Scroll to today: ',
'toggle-view-mode': 'Change view mode: ',
},
'holiday-subform': {
'toggle-weekends': ['Mark weekends: ', false],
'ignore-weekends': 'Exclude weekends: ',
},
};
for (let form of ['sideheader-form', 'holiday-form']) {
let formNode = document.getElementById(form);
let parent = formNode.parentElement;
parent.appendChild(formNode);
}
for (let form in SWITCHES) {
for (let id in SWITCHES[form]) {
createSwitch(form, id, SWITCHES[form][id]);
}
}
const UPDATES = [
[
sideheader,
{
'toggle-today': 'today_button',
'toggle-view-mode': 'view_mode_select',
},
],
[
holidays,
{
'toggle-weekends': (val, opts) => ({
holidays: {
'#fffddb': opts.holidays['#fffddb'],
'var(--g-weekend-highlight-color)': val ? 'weekend' : [],
},
ignore: [],
}),
'declare-holiday': (val, opts) => ({
holidays: {
'#fffddb': [...HOLIDAYS, { date: val, name: 'Kay' }],
'var(--g-weekend-highlight-color)':
opts.holidays['var(--g-weekend-highlight-color)'],
},
}),
'ignore-weekends': (val, opts) => ({
ignore: [
opts.ignore.filter((k) => k !== 'weekend')[0],
...(val ? ['weekend'] : []),
],
holidays: { '#fffddb': opts.holidays['#fffddb'] },
}),
'declare-ignore': (val, opts) => ({
ignore: [
...(opts.ignore.includes('weekend') ? ['weekend'] : []),
val,
],
}),
},
(id, val) => {
let el = document.getElementById(id);
if (id === 'toggle-weekends' && val) {
document.getElementById('ignore-weekends').checked = false;
}
if (id === 'ignore-weekends' && val) {
document.getElementById('toggle-weekends').checked = false;
}
},
],
];
for (let [chart, details, after] of UPDATES) {
for (let id in details) {
let el = document.getElementById(id);
el.onchange = (e) => {
let label = details[id];
let val;
if (e.currentTarget.type === 'checkbox') {
if (typeof label === 'string') {
let opposite = label.slice(0, 5) === 'opp__';
if (opposite) label = label.slice(5);
val = opposite
? !e.currentTarget.checked
: e.currentTarget.checked;
} else if (typeof label === 'object') {
val = label[e.currentTarget.checked ? 1 : 2];
label = label[0];
} else {
val =
e.currentTarget.type === 'checkbox'
? e.currentTarget.checked
: e.currentTarget.value;
}
} else {
val =
e.currentTarget.type === 'date'
? e.currentTarget.value
: +e.currentTarget.value;
}
if (typeof label === 'function') {
console.log('ha', label(val, chart.options));
chart.update_options(label(val, chart.options));
} else {
chart.update_options({
[label]: val,
});
}
after && after(id, val, chart);
};
}
}

117
dist/frappe-gantt.css vendored
View File

@ -1,117 +0,0 @@
.gantt .grid-background {
fill: none; }
.gantt .grid-header {
fill: #ffffff;
stroke: #e0e0e0;
stroke-width: 1.4; }
.gantt .grid-row {
fill: #ffffff; }
.gantt .grid-row:nth-child(even) {
fill: #f5f5f5; }
.gantt .row-line {
stroke: #ebeff2; }
.gantt .tick {
stroke: #e0e0e0;
stroke-width: 0.2; }
.gantt .tick.thick {
stroke-width: 0.4; }
.gantt .today-highlight {
fill: #fcf8e3;
opacity: 0.5; }
.gantt .arrow {
fill: none;
stroke: #666;
stroke-width: 1.4; }
.gantt .bar {
fill: #b8c2cc;
stroke: #8D99A6;
stroke-width: 0;
transition: stroke-width .3s ease;
user-select: none; }
.gantt .bar-progress {
fill: #a3a3ff; }
.gantt .bar-invalid {
fill: transparent;
stroke: #8D99A6;
stroke-width: 1;
stroke-dasharray: 5; }
.gantt .bar-invalid ~ .bar-label {
fill: #555; }
.gantt .bar-label {
fill: #fff;
dominant-baseline: central;
text-anchor: middle;
font-size: 12px;
font-weight: lighter; }
.gantt .bar-label.big {
fill: #555;
text-anchor: start; }
.gantt .handle {
fill: #ddd;
cursor: ew-resize;
opacity: 0;
visibility: hidden;
transition: opacity .3s ease; }
.gantt .bar-wrapper {
cursor: pointer; }
.gantt .bar-wrapper:hover .bar {
fill: #a9b5c1; }
.gantt .bar-wrapper:hover .bar-progress {
fill: #8a8aff; }
.gantt .bar-wrapper:hover .handle {
visibility: visible;
opacity: 1; }
.gantt .bar-wrapper.active .bar {
fill: #a9b5c1; }
.gantt .bar-wrapper.active .bar-progress {
fill: #8a8aff; }
.gantt .lower-text, .gantt .upper-text {
font-size: 12px;
text-anchor: middle; }
.gantt .upper-text {
fill: #555; }
.gantt .lower-text {
fill: #333; }
.gantt .hide {
display: none; }
.gantt-container {
position: relative;
font-size: 12px; }
.gantt-container .popup-wrapper {
position: absolute;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.8);
padding: 0;
color: #959da5;
border-radius: 3px; }
.gantt-container .popup-wrapper .title {
border-bottom: 3px solid #a3a3ff;
padding: 10px; }
.gantt-container .popup-wrapper .subtitle {
padding: 10px;
color: #dfe2e5; }
.gantt-container .popup-wrapper .pointer {
position: absolute;
height: 5px;
margin: 0 0 0 -5px;
border: 5px solid transparent;
border-top-color: rgba(0, 0, 0, 0.8); }

1766
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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

19
eslint.config.mjs Normal file
View File

@ -0,0 +1,19 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default [...compat.extends("plugin:prettier/recommended"), {
languageOptions: {
ecmaVersion: 6,
sourceType: "module",
},
}];

View File

@ -1,80 +1,785 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>Simple Gantt</title> <title>Simple Gantt</title>
<style> <link rel="stylesheet" href="dist/frappe-gantt.css" />
body { <link
font-family: sans-serif; href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
background: #ccc; rel="stylesheet"
} integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
.container { crossorigin="anonymous"
width: 80%; />
margin: 0 auto; <style>
} .container {
.gantt-container { width: 90%;
overflow: scroll; margin: 0 auto;
} }
/* custom class */
.gantt .bar-milestone .bar-progress {
fill: tomato;
}
</style>
<link rel="stylesheet" href="dist/frappe-gantt.css" />
<script src="dist/frappe-gantt.js"></script>
</head>
<body>
<div class="container">
<h2>Interactive Gantt Chart entirely made in SVG!</h2>
<div class="gantt-target"></div>
</div>
<script>
var names = [
["Redesign website", [0, 7]],
["Write new content", [1, 4]],
["Apply new styles", [3, 6]],
["Review", [7, 7]],
["Deploy", [8, 9]],
["Go Live!", [10, 10]]
];
var tasks = names.map(function(name, i) { .chart {
var today = new Date(); border: 1px dotted black;
var start = new Date(today.getFullYear(), today.getMonth(), today.getDate()); border-radius: 4px;
var end = new Date(today.getFullYear(), today.getMonth(), today.getDate()); height: fit-content;
start.setDate(today.getDate() + name[1][0]); }
end.setDate(today.getDate() + name[1][1]);
return {
start: start,
end: end,
name: name[0],
id: "Task " + i,
progress: parseInt(Math.random() * 100, 10)
}
});
tasks[1].progress = 0;
tasks[1].dependencies = "Task 0"
tasks[2].dependencies = "Task 1"
tasks[3].dependencies = "Task 2"
tasks[5].dependencies = "Task 4"
tasks[5].custom_class = "bar-milestone";
var gantt_chart = new Gantt(".gantt-target", tasks, { .chart.active {
bar_corner_radius: 5, filter: drop-shadow(1px 1px 4px rgba(0, 0, 0, 0.6));
on_click: function (task) { border: unset;
console.log(task); }
},
on_date_change: function(task, start, end) { small {
console.log(task, start, end); font-size: 0.775em;
}, }
on_progress_change: function(task, progress) { </style>
console.log(task, progress); <script src="dist/frappe-gantt.umd.js"></script>
}, </head>
on_view_change: function(mode) { <body>
console.log(mode); <div class="container">
} <h1 class="text-center pt-3 pb-2 font-serif">Frappe Gantt</h1>
}); <hr />
console.log(gantt_chart); <div class="row my-5">
</script> <div class="col-md-3 px-5 py-1">
</body> <h3 class="text-center">Set edit access</h3>
<p>
Easy make sure your employees change <em>only</em> what
they need to.
</p>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="mutable-general"
checked
/>
<label class="form-check-label" for="mutable-general"
>Editable</label
>
</div>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="mutable-progress"
checked
/>
<label class="form-check-label" for="mutable-general"
>Progress editable</label
>
</div>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="mutable-dates"
checked
/>
<label class="form-check-label" for="mutable-general"
>Dates editable</label
>
</div>
</div>
<div class="chart col-md-9" id="mutability"></div>
</div>
<div class="row my-5">
<div class="chart col-md-9" id="sideheader"></div>
<div class="col-md-3 px-5 py-1">
<h3 class="text-center">Versatile Actions</h3>
<p>
Change the view mode, or scroll to today, or add
anything you like <sup>β</sup>.
</p>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="toggle-today"
checked
/>
<label class="form-check-label" for="mutable-general"
>Scroll to Today</label
>
</div>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="toggle-view-mode"
checked
/>
<label class="form-check-label" for="mutable-general"
>Change View Mode</label
>
</div>
</div>
</div>
<div class="row my-5">
<div class="col-md-3 px-5 py-1">
<h3 class="text-center">Mark Holidays</h3>
<p>
Be it public holidays, company milestones, or just
weekends, you can see it all.
</p>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="toggle-weekends"
/>
<label class="form-check-label" for="toggle-weekends"
>Show weekends</label
>
</div>
</div>
<div class="chart col-md-9" id="holidays"></div>
</div>
<div class="row my-5">
<div class="col-md-3 px-5 py-1">
<h3 class="text-center">...or <em>ignore</em> them</h3>
<p>
Remove time periods from your Gantt - they're now
completely ignored.
</p>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="ignore-weekends"
checked
/>
<label class="form-check-label" for="toggle-weekends"
>Ignore weekends</label
>
</div>
</div>
<div class="chart col-md-9" id="ignore"></div>
</div>
<div class="row my-4">
<div class="col-md-9 chart" id="styling"></div>
<div class="col-md-3 px-4">
<h3 class="text-center">Control the styles completely.</h3>
<strong>Modify Grid</strong>
<div class="input-group row">
<label
for="grid-height"
class="form-label col-sm-5 col-form-label"
><small>Grid Height:</small></label
>
<div class="col-sm-7">
<input
id="grid-height"
class="form-range align-items-end"
type="range"
min="150"
max="600"
value="300"
/>
</div>
</div>
<div class="input-group row">
<label
for="padding"
class="form-label col-sm-5 col-form-label"
><small>Padding:</small></label
>
<div class="col-sm-7">
<input
id="padding"
class="form-range align-items-end"
type="range"
min="3"
max="50"
value="18"
/>
</div>
</div>
<div class="input-group row">
<label
for="column-width"
class="form-label col-sm-5 col-form-label"
><small>Column Width:</small></label
>
<div class="col-sm-7">
<input
id="column-width"
class="form-range align-items-end"
type="range"
min="30"
max="70"
value="30"
/>
</div>
</div>
<div class="pt-3">
<strong>Modify Bar</strong>
</div>
<div class="input-group row">
<label
for="bar-height"
class="form-label col-sm-5 col-form-label"
><small>Height:</small></label
>
<div class="col-sm-7">
<input
id="bar-height"
class="form-range align-items-end"
type="range"
min="10"
max="100"
value="30"
/>
</div>
</div>
<div class="input-group row">
<label
for="bar-radius"
class="form-label col-sm-5 col-form-label"
><small>Radius:</small></label
>
<div class="col-sm-7">
<input
id="bar-radius"
class="form-range align-items-end"
type="range"
min="1"
max="50"
value="3"
/>
</div>
</div>
<div class="input-group row">
<label
for="arrow-curve"
class="form-label col-sm-5 col-form-label"
><small>Arrow curving:</small></label
>
<div class="col-sm-7">
<input
id="arrow-curve"
class="form-range align-items-end"
type="range"
min="1"
max="50"
value="3"
/>
</div>
</div>
</div>
</div>
<div class="row my-4">
<div class="col-md-3">
<h2>Frappe Gantt - <em>for you</em>.</h2>
<p>
Insane levels of customizability - change anything,
everything.
</p>
<div class="input-group">
<label class="input-group-text">Snap By: </label>
<input
class="form-control"
id="snap-at-qty"
type="number"
value="1"
/>
<select class="form-select" id="snap-at-scale">
<option value="s">Second</option>
<option value="min">Minute</option>
<option value="h">Hour</option>
<option value="d" selected>Day</option>
<option value="m">Month</option>
<option value="y">Year</option>
</select>
</div>
<div class="form-check form-switch my-2">
<label class="form-check-label" for="auto-move-label"
>Toggle auto-moving label</label
>
<input
class="form-check-input"
type="checkbox"
role="switch"
id="auto-move-label"
/>
</div>
</div>
<div class="col-md-9 chart" id="advanced"></div>
</div>
</div>
<script type="module">
const rawToday = new Date();
const today =
Date.UTC(
rawToday.getFullYear(),
rawToday.getMonth(),
rawToday.getDate(),
) +
new Date().getTimezoneOffset() * 60000;
function random(begin = 10, end = 90, multiple = 10) {
let k;
do {
k = Math.floor(Math.random() * 100);
} while (k < begin || k > end || k % multiple !== 0);
return k;
}
const daysSince = (dx) => new Date(today + dx * 86400000);
let tasks = [
{
start: daysSince(-2),
end: daysSince(2),
name: 'Redesign website',
id: 'Task 0',
progress: random(),
},
{
start: daysSince(3),
duration: '6d',
name: 'Write new content',
id: 'Task 1',
progress: random(),
important: true,
dependencies: 'Task 0',
},
{
start: daysSince(4),
duration: '2d',
name: 'Apply new styles',
id: 'Task 2',
progress: random(),
},
{
start: daysSince(-4),
end: daysSince(0),
name: 'Review',
id: 'Task 3',
progress: random(),
},
];
const tasksSpread = [
{
start: daysSince(-30),
end: daysSince(-10),
name: 'Redesign website',
id: 'Task 0',
progress: random(),
},
{
start: daysSince(-15),
duration: '21d',
name: 'Write new content',
id: 'Task 1',
progress: random(),
important: true,
},
{
start: daysSince(10),
duration: '14d',
name: 'Review',
id: 'Task 3',
progress: random(),
},
{
start: daysSince(-3),
duration: '4d',
name: 'Publish',
id: 'Task 4',
progress: random(),
},
];
const tasksDependencies = [
{
start: daysSince(-2),
end: daysSince(2),
name: 'Redesign website',
id: 'Task 0',
progress: random(),
},
{
start: daysSince(3),
duration: '6d',
name: 'Write new content',
id: 'Task 1',
progress: random(),
dependencies: 'Task 0',
important: true,
},
{
start: daysSince(4),
duration: '2d',
name: 'Apply new styles',
id: 'Task 2',
progress: random(),
},
{
start: daysSince(-4),
end: daysSince(0),
name: 'Review',
id: 'Task 3',
custom_class: 'readonly',
progress: random(),
},
];
let tasksMany = [
{
start: daysSince(-7),
end: daysSince(-5),
name: 'Initial brainstorming',
id: 'Task 0',
progress: random(),
},
{
start: daysSince(-3),
end: daysSince(1),
name: 'Develop wireframe',
id: 'Task 1',
progress: random(),
dependencies: 'Task 0',
},
{
start: daysSince(-1),
duration: '4d',
name: 'Client meeting',
id: 'Task 2',
progress: random(),
important: true,
},
{
start: daysSince(1),
duration: '7d',
name: 'Create prototype',
id: 'Task 3',
dependencies: 'Task 2',
progress: random(),
},
{
start: daysSince(3),
duration: '5d',
name: 'Test design with users',
dependencies: 'Task 2',
id: 'Task 4',
progress: random(),
important: true,
},
{
start: daysSince(5),
end: daysSince(10),
name: 'Write technical documentation',
id: 'Task 5',
progress: random(),
},
{
start: daysSince(8),
duration: '3d',
name: 'Prepare demo',
id: 'Task 6',
progress: random(),
},
{
start: daysSince(10),
end: daysSince(12),
name: 'Final client review',
id: 'Task 7',
progress: random(),
important: true,
},
{
start: daysSince(14),
duration: '6d',
name: 'Implement feedback',
id: 'Task 8',
progress: random(),
},
{
start: daysSince(16),
duration: '4d',
name: 'Launch website',
id: 'Task 9',
progress: random(),
important: true,
},
];
const HOLIDAYS = [
{ name: 'Republic Day', date: '2024-01-26' },
{ name: 'Maha Shivratri', date: '2024-02-23' },
{ name: 'Holi', date: '2024-03-11' },
{ name: 'Mahavir Jayanthi', date: '2024-04-07' },
{ name: 'Good Friday', date: '2024-04-10' },
{ name: 'May Day', date: '2024-05-01' },
{ name: 'Buddha Purnima', date: '2024-05-08' },
{ name: 'Krishna Janmastami', date: '2024-08-14' },
{ name: 'Independence Day', date: '2024-08-15' },
{ name: 'Ganesh Chaturthi', date: '2024-08-23' },
{ name: 'Id-Ul-Fitr', date: '2024-09-21' },
{ name: 'Vijaya Dashami', date: '2024-09-28' },
{ name: 'Mahatma Gandhi Jayanti', date: '2024-10-02' },
{ name: 'Diwali', date: '2024-10-17' },
{ name: 'Guru Nanak Jayanthi', date: '2024-11-02' },
{ name: 'Christmas', date: '2024-12-25' },
];
const mutablity = new Gantt('#mutability', tasks);
const sideheader = new Gantt('#sideheader', tasksSpread, {
today_button: true,
view_mode_select: true,
holidays: null,
});
const holidays = new Gantt('#holidays', tasksSpread, {
holidays: {
'#bfdbfe': [],
'#a3e635': HOLIDAYS,
},
});
const ignore = new Gantt('#ignore', tasks, {
ignore: ['weekend', ...HOLIDAYS.map((k) => k.date)],
holidays: null,
scroll_to: daysSince(-10),
});
const styling = new Gantt('#styling', tasksMany, {
holidays: null,
scroll_to: daysSince(-10),
});
const advanced = new Gantt('#advanced', tasksSpread, {
holidays: null,
view_mode_select: true,
snap_at: '1d',
auto_move_label: false,
scroll_to: 'today',
});
const UPDATES = [
[
mutablity,
{
'mutable-general': 'opp__readonly',
'mutable-dates': 'opp__readonly_dates',
'mutable-progress': 'opp__readonly_progress',
},
(id, val) => {
if (id === 'mutable-general') {
document.getElementById('mutable-dates').checked =
!val;
document.getElementById(
'mutable-progress',
).checked = !val;
}
},
],
[
sideheader,
{
'toggle-today': 'today_button',
'toggle-view-mode': 'view_mode_select',
},
],
[
holidays,
{
'toggle-weekends': [
'holidays',
{ '#a3e635': HOLIDAYS, '#bfdbfe': 'weekend' },
{ '#a3e635': HOLIDAYS, '#bfdbfe': [] },
],
},
],
[
ignore,
{
'ignore-weekends': ['ignore', ['weekend'], []],
},
],
[
styling,
{
'bar-radius': 'bar_corner_radius',
'bar-height': 'bar_height',
'arrow-curve': 'arrow_curve',
'column-width': 'column_width',
'grid-height': 'container_height',
padding: 'padding',
},
],
[
advanced,
{
'auto-move-label': 'auto_move_label',
'snap-at-qty': (val) => ({
snap_at:
val +
document.getElementById('snap-at-scale').value,
}),
'snap-at-scale': (val) => ({
snap_at:
document.getElementById('snap-at-qty').value +
val,
}),
},
],
];
for (let [chart, details, after] of UPDATES) {
for (let id in details) {
let el = document.getElementById(id);
el.onchange = (e) => {
let label = details[id];
let val;
if (e.currentTarget.type === 'checkbox') {
if (typeof label === 'string') {
let opposite = label.slice(0, 5) === 'opp__';
if (opposite) label = label.slice(5);
val = opposite
? !e.currentTarget.checked
: e.currentTarget.checked;
} else {
val = label[e.currentTarget.checked ? 1 : 2];
label = label[0];
}
} else {
val = +e.currentTarget.value;
}
let store = chart.options.scroll_to;
let scroll = chart.$container.scrollLeft;
if (typeof label === 'function') {
chart.update_options({
...label(val),
scroll_to: null,
});
} else {
chart.update_options({
[label]: val,
scroll_to: null,
});
}
chart.options.scroll_to = store;
chart.$container.scrollLeft = scroll;
after && after(id, val, chart);
};
}
}
// const OPTIONS_UPDATE = [
// // [
// // styling,
// // {
// // 'bar-spacing': {
// // bar_corner_radius: [
// // 'config',
// // () =>
// // +document.getElementById('bar-radius')
// // .value,
// // ,
// // ],
// // bar_height: [
// // 'config',
// // () =>
// // +document.getElementById('bar-height')
// // .value,
// // ],
// // arrow_curve: [
// // 'config',
// // () =>
// // +document.getElementById('arrow-curve')
// // .value,
// // ],
// // },
// // },
// // ],
// [
// advanced,
// {
// 'snap-by': {
// BEFORE: (chart) => chart.$container.scrollLeft,
// snap_at: [
// 'config',
// (scale) => {
// return (
// document.getElementById('snap-at-qty')
// .value +
// document.getElementById('snap-at-scale')
// .value
// );
// },
// ],
// view_mode: ['config', (k) => k],
// scroll_to: ['config', (_) => false],
// AFTER: (before, chart) =>
// (chart.$container.scrollLeft = before),
// },
// 'auto-move-label': {
// BEFORE: (chart) =>
// chart.change_view_mode('Day') ||
// chart.$container.scrollLeft,
// view_mode: ['config', (k) => k],
// auto_move_label: 'opp',
// scroll_to: ['config', (_) => false],
// AFTER: (before, chart) =>
// (chart.$container.scrollLeft = before),
// },
// },
// ],
// ];
// for (let [chart, options] of OPTIONS_UPDATE) {
// for (let id in options) {
// let el = document.getElementById(id);
// el.onclick = () => {
// const before =
// options[id].BEFORE && options[id].BEFORE(chart);
// let newOptions = {};
// for (let k in options[id]) {
// if (k === 'AFTER' || k === 'BEFORE') continue;
// if (options[id][k] === 'opp') {
// newOptions[k] = !chart.options[k];
// if (chart.options[k]) {
// el.innerHTML = el.innerHTML.replace(
// 'Hide',
// 'Show',
// );
// } else {
// el.innerHTML = el.innerHTML.replace(
// 'Show',
// 'Hide',
// );
// }
// } else if (options[id][k][0] === 'config') {
// newOptions[k] = options[id][k][1](
// chart.options[k],
// chart,
// );
// } else {
// newOptions[k] = options[id][k];
// }
// }
// chart.update_options(newOptions);
// options[id].AFTER && options[id].AFTER(before, chart);
// };
// }
// }
</script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"
></script>
</body>
</html> </html>

View File

@ -1,6 +1,6 @@
The MIT License (MIT) 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: 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:

99
package.json Executable file → Normal file
View File

@ -1,44 +1,59 @@
{ {
"name": "frappe-gantt", "name": "frappe-gantt",
"version": "0.1.0", "version": "1.0.3",
"description": "A simple, modern, interactive gantt library for the web", "description": "A simple, modern, interactive gantt library for the web",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "type": "module",
"build": "rollup -c", "scripts": {
"dev": "rollup -c -w", "dev": "vite",
"test": "jest", "build-dev": "vite build --watch",
"test:watch": "jest --watch", "build": "vite build",
"prettier": "prettier es6 --write \"{src/*,tests/*,rollup.config}.js\"" "lint": "eslint src/**/*.js",
}, "prettier": "prettier --write \"{src/*,tests/*,rollup.config}.js\"",
"repository": { "prettier-check": "prettier --check \"{src/*,tests/*,rollup.config}.js\""
"type": "git", },
"url": "https://github.com/frappe/gantt.git" "repository": {
}, "type": "git",
"keywords": [ "url": "git+https://github.com/frappe/gantt.git"
"gantt", },
"svg", "files": [
"simple gantt", "src",
"project timeline", "dist",
"interactive gantt", "README.md"
"project management" ],
], "exports": {
"author": "Faris Ansari", ".": {
"license": "MIT", "require": "./dist/frappe-gantt.umd.js",
"bugs": { "import": "./dist/frappe-gantt.es.js",
"url": "https://github.com/frappe/gantt/issues" "style": "./dist/frappe-gantt.css"
}, }
"homepage": "https://github.com/frappe/gantt", },
"devDependencies": { "keywords": [
"babel-preset-env": "^1.6.1", "gantt",
"deepmerge": "^2.0.1", "svg",
"eslint": "^4.17.0", "simple gantt",
"eslint-config-prettier": "^2.9.0", "project timeline",
"eslint-plugin-prettier": "^2.6.0", "interactive gantt",
"jest": "^22.2.1", "project management"
"prettier": "1.10.2", ],
"rollup": "^0.55.3", "author": "Faris Ansari",
"rollup-plugin-sass": "^0.5.3", "license": "MIT",
"rollup-plugin-uglify": "^3.0.0" "bugs": {
}, "url": "https://github.com/frappe/gantt/issues"
"eslintIgnore": ["dist"] },
"homepage": "https://github.com/frappe/gantt",
"devDependencies": {
"eslint": "^9.15.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-prettier": "^2.6.0",
"postcss-nesting": "^12.1.2",
"prettier": "3.2.5",
"vite": "^5.2.10"
},
"eslintIgnore": [
"dist"
],
"sideEffects": [
"*.css"
]
} }

1291
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

4
postcss.config.cjs Normal file
View File

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

View File

@ -1,25 +0,0 @@
import sass from 'rollup-plugin-sass';
import uglify from 'rollup-plugin-uglify';
import merge from 'deepmerge';
const dev = {
input: 'src/index.js',
output: {
name: 'Gantt',
file: 'dist/frappe-gantt.js',
format: 'iife'
},
plugins: [
sass({
output: 'dist/frappe-gantt.css'
})
]
};
const prod = merge(dev, {
output: {
file: 'dist/frappe-gantt.min.js'
},
plugins: [uglify()]
});
export default [dev, prod];

View File

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

View File

@ -21,63 +21,70 @@ export default class Arrow {
while (condition()) { while (condition()) {
start_x -= 10; start_x -= 10;
} }
start_x -= 10;
const start_y = let start_y =
this.gantt.options.header_height + this.gantt.config.header_height +
this.gantt.options.bar_height + this.gantt.options.bar_height +
(this.gantt.options.padding + this.gantt.options.bar_height) * (this.gantt.options.padding + this.gantt.options.bar_height) *
this.from_task.task._index + this.from_task.task._index +
this.gantt.options.padding; this.gantt.options.padding / 2;
const end_x = this.to_task.$bar.getX() - this.gantt.options.padding / 2; let end_x = this.to_task.$bar.getX() - 13;
const end_y = let end_y =
this.gantt.options.header_height + this.gantt.config.header_height +
this.gantt.options.bar_height / 2 + this.gantt.options.bar_height / 2 +
(this.gantt.options.padding + this.gantt.options.bar_height) * (this.gantt.options.padding + this.gantt.options.bar_height) *
this.to_task.task._index + this.to_task.task._index +
this.gantt.options.padding; this.gantt.options.padding / 2;
const from_is_below_to = const from_is_below_to =
this.from_task.task._index > this.to_task.task._index; 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 = ` let curve = this.gantt.options.arrow_curve;
M ${start_x} ${start_y} const clockwise = from_is_below_to ? 1 : 0;
V ${offset} let curve_y = from_is_below_to ? -curve : curve;
a ${curve} ${curve} 0 0 ${clockwise} ${curve} ${curve_y}
L ${end_x} ${end_y}
m -5 -5
l 5 5
l -5 5`;
if ( if (
this.to_task.$bar.getX() < this.to_task.$bar.getX() <=
this.from_task.$bar.getX() + this.gantt.options.padding this.from_task.$bar.getX() + this.gantt.options.padding
) { ) {
const down_1 = this.gantt.options.padding / 2 - curve; let down_1 = this.gantt.options.padding / 2 - curve;
if (down_1 < 0) {
down_1 = 0;
curve = this.gantt.options.padding / 2;
curve_y = from_is_below_to ? -curve : curve;
}
const down_2 = const down_2 =
this.to_task.$bar.getY() + this.to_task.$bar.getY() +
this.to_task.$bar.getHeight() / 2 - this.to_task.$bar.getHeight() / 2 -
curve_y; curve_y;
const left = this.to_task.$bar.getX() - this.gantt.options.padding; const left = this.to_task.$bar.getX() - this.gantt.options.padding;
this.path = ` this.path = `
M ${start_x} ${start_y} M ${start_x} ${start_y}
v ${down_1} v ${down_1}
a ${curve} ${curve} 0 0 1 -${curve} ${curve} a ${curve} ${curve} 0 0 1 ${-curve} ${curve}
H ${left} H ${left}
a ${curve} ${curve} 0 0 ${clockwise} -${curve} ${curve_y} a ${curve} ${curve} 0 0 ${clockwise} ${-curve} ${curve_y}
V ${down_2} V ${down_2}
a ${curve} ${curve} 0 0 ${clockwise} ${curve} ${curve_y} a ${curve} ${curve} 0 0 ${clockwise} ${curve} ${curve_y}
L ${end_x} ${end_y} L ${end_x} ${end_y}
m -5 -5 m -5 -5
l 5 5 l 5 5
l -5 5`; l -5 5`;
} else {
if (end_x < start_x + curve) curve = end_x - start_x;
let offset = from_is_below_to ? end_y + curve : end_y - curve;
this.path = `
M ${start_x} ${start_y}
V ${offset}
a ${curve} ${curve} 0 0 ${clockwise} ${curve} ${curve}
L ${end_x} ${end_y}
m -5 -5
l 5 5
l -5 5`;
} }
} }
@ -85,7 +92,7 @@ export default class Arrow {
this.element = createSVG('path', { this.element = createSVG('path', {
d: this.path, d: this.path,
'data-from': this.from_task.task.id, 'data-from': this.from_task.task.id,
'data-to': this.to_task.task.id 'data-to': this.to_task.task.id,
}); });
} }

737
src/bar.js Normal file
View File

@ -0,0 +1,737 @@
import date_utils from './date_utils';
import { $, createSVG, animateSVG } from './svg_utils';
export default class Bar {
constructor(gantt, task) {
this.set_defaults(gantt, task);
this.prepare_wrappers();
this.prepare_helpers();
this.refresh();
}
refresh() {
this.bar_group.innerHTML = '';
this.handle_group.innerHTML = '';
if (this.task.custom_class) {
this.group.classList.add(this.task.custom_class);
} else {
this.group.classList = ['bar-wrapper'];
}
this.prepare_values();
this.draw();
this.bind();
}
set_defaults(gantt, task) {
this.action_completed = false;
this.gantt = gantt;
this.task = task;
this.name = this.name || '';
}
prepare_wrappers() {
this.group = createSVG('g', {
class:
'bar-wrapper' +
(this.task.custom_class ? ' ' + this.task.custom_class : ''),
'data-id': this.task.id,
});
this.bar_group = createSVG('g', {
class: 'bar-group',
append_to: this.group,
});
this.handle_group = createSVG('g', {
class: 'handle-group',
append_to: this.group,
});
}
prepare_values() {
this.invalid = this.task.invalid;
this.height = this.gantt.options.bar_height;
this.image_size = this.height - 5;
this.task._start = new Date(this.task.start);
this.task._end = new Date(this.task.end);
this.compute_x();
this.compute_y();
this.compute_duration();
this.corner_radius = this.gantt.options.bar_corner_radius;
this.width = this.gantt.config.column_width * this.duration;
if (!this.task.progress || this.task.progress < 0)
this.task.progress = 0;
if (this.task.progress > 100) this.task.progress = 100;
}
prepare_helpers() {
SVGElement.prototype.getX = function () {
return +this.getAttribute('x');
};
SVGElement.prototype.getY = function () {
return +this.getAttribute('y');
};
SVGElement.prototype.getWidth = function () {
return +this.getAttribute('width');
};
SVGElement.prototype.getHeight = function () {
return +this.getAttribute('height');
};
SVGElement.prototype.getEndX = function () {
return this.getX() + this.getWidth();
};
}
prepare_expected_progress_values() {
this.compute_expected_progress();
this.expected_progress_width =
this.gantt.options.column_width *
this.duration *
(this.expected_progress / 100) || 0;
}
draw() {
this.draw_bar();
this.draw_progress_bar();
if (this.gantt.options.show_expected_progress) {
this.prepare_expected_progress_values();
this.draw_expected_progress_bar();
}
this.draw_label();
this.draw_resize_handles();
if (this.task.thumbnail) {
this.draw_thumbnail();
}
}
draw_bar() {
this.$bar = createSVG('rect', {
x: this.x,
y: this.y,
width: this.width,
height: this.height,
rx: this.corner_radius,
ry: this.corner_radius,
class: 'bar',
append_to: this.bar_group,
});
if (this.task.color) this.$bar.style.fill = this.task.color;
animateSVG(this.$bar, 'width', 0, this.width);
if (this.invalid) {
this.$bar.classList.add('bar-invalid');
}
}
draw_expected_progress_bar() {
if (this.invalid) return;
this.$expected_bar_progress = createSVG('rect', {
x: this.x,
y: this.y,
width: this.expected_progress_width,
height: this.height,
rx: this.corner_radius,
ry: this.corner_radius,
class: 'bar-expected-progress',
append_to: this.bar_group,
});
animateSVG(
this.$expected_bar_progress,
'width',
0,
this.expected_progress_width,
);
}
draw_progress_bar() {
if (this.invalid) return;
this.progress_width = this.calculate_progress_width();
let r = this.corner_radius;
if (!/^((?!chrome|android).)*safari/i.test(navigator.userAgent))
r = this.corner_radius + 2;
this.$bar_progress = createSVG('rect', {
x: this.x,
y: this.y,
width: this.progress_width,
height: this.height,
rx: r,
ry: r,
class: 'bar-progress',
append_to: this.bar_group,
});
if (this.task.color_progress)
this.$bar_progress.style.fill = this.task.color_progress;
const x =
(date_utils.diff(
this.task._start,
this.gantt.gantt_start,
this.gantt.config.unit,
) /
this.gantt.config.step) *
this.gantt.config.column_width;
let $date_highlight = this.gantt.create_el({
classes: `date-range-highlight hide highlight-${this.task.id}`,
width: this.width,
left: x,
});
this.$date_highlight = $date_highlight;
this.gantt.$lower_header.prepend(this.$date_highlight);
animateSVG(this.$bar_progress, 'width', 0, this.progress_width);
}
calculate_progress_width() {
const width = this.$bar.getWidth();
const ignored_end = this.x + width;
const total_ignored_area =
this.gantt.config.ignored_positions.reduce((acc, val) => {
return acc + (val >= this.x && val < ignored_end);
}, 0) * this.gantt.config.column_width;
let progress_width =
((width - total_ignored_area) * this.task.progress) / 100;
const progress_end = this.x + progress_width;
const total_ignored_progress =
this.gantt.config.ignored_positions.reduce((acc, val) => {
return acc + (val >= this.x && val < progress_end);
}, 0) * this.gantt.config.column_width;
progress_width += total_ignored_progress;
let ignored_regions = this.gantt.get_ignored_region(
this.x + progress_width,
);
while (ignored_regions.length) {
progress_width += this.gantt.config.column_width;
ignored_regions = this.gantt.get_ignored_region(
this.x + progress_width,
);
}
this.progress_width = progress_width;
return progress_width;
}
draw_label() {
let x_coord = this.x + this.$bar.getWidth() / 2;
if (this.task.thumbnail) {
x_coord = this.x + this.image_size + 5;
}
createSVG('text', {
x: x_coord,
y: this.y + this.height / 2,
innerHTML: this.task.name,
class: 'bar-label',
append_to: this.bar_group,
});
// labels get BBox in the next tick
requestAnimationFrame(() => this.update_label_position());
}
draw_thumbnail() {
let x_offset = 10,
y_offset = 2;
let defs, clipPath;
defs = createSVG('defs', {
append_to: this.bar_group,
});
createSVG('rect', {
id: 'rect_' + this.task.id,
x: this.x + x_offset,
y: this.y + y_offset,
width: this.image_size,
height: this.image_size,
rx: '15',
class: 'img_mask',
append_to: defs,
});
clipPath = createSVG('clipPath', {
id: 'clip_' + this.task.id,
append_to: defs,
});
createSVG('use', {
href: '#rect_' + this.task.id,
append_to: clipPath,
});
createSVG('image', {
x: this.x + x_offset,
y: this.y + y_offset,
width: this.image_size,
height: this.image_size,
class: 'bar-img',
href: this.task.thumbnail,
clipPath: 'clip_' + this.task.id,
append_to: this.bar_group,
});
}
draw_resize_handles() {
if (this.invalid || this.gantt.options.readonly) return;
const bar = this.$bar;
const handle_width = 3;
this.handles = [];
if (!this.gantt.options.readonly_dates) {
this.handles.push(
createSVG('rect', {
x: bar.getEndX() - handle_width / 2,
y: bar.getY() + this.height / 4,
width: handle_width,
height: this.height / 2,
rx: 2,
ry: 2,
class: 'handle right',
append_to: this.handle_group,
}),
);
this.handles.push(
createSVG('rect', {
x: bar.getX() - handle_width / 2,
y: bar.getY() + this.height / 4,
width: handle_width,
height: this.height / 2,
rx: 2,
ry: 2,
class: 'handle left',
append_to: this.handle_group,
}),
);
}
if (!this.gantt.options.readonly_progress) {
const bar_progress = this.$bar_progress;
this.$handle_progress = createSVG('circle', {
cx: bar_progress.getEndX(),
cy: bar_progress.getY() + bar_progress.getHeight() / 2,
r: 4.5,
class: 'handle progress',
append_to: this.handle_group,
});
this.handles.push(this.$handle_progress);
}
for (let handle of this.handles) {
$.on(handle, 'mouseenter', () => handle.classList.add('active'));
$.on(handle, 'mouseleave', () => handle.classList.remove('active'));
}
}
bind() {
if (this.invalid) return;
this.setup_click_event();
}
setup_click_event() {
let task_id = this.task.id;
$.on(this.group, 'mouseover', (e) => {
this.gantt.trigger_event('hover', [
this.task,
e.screenX,
e.screenY,
e,
]);
});
if (this.gantt.options.popup_on === 'click') {
$.on(this.group, 'mouseup', (e) => {
const posX = e.offsetX || e.layerX;
if (this.$handle_progress) {
const cx = +this.$handle_progress.getAttribute('cx');
if (cx > posX - 1 && cx < posX + 1) return;
if (this.gantt.bar_being_dragged) return;
}
this.gantt.show_popup({
x: e.offsetX || e.layerX,
y: e.offsetY || e.layerY,
task: this.task,
target: this.$bar,
});
});
}
let timeout;
$.on(this.group, 'mouseenter', (e) => {
timeout = setTimeout(() => {
if (this.gantt.options.popup_on === 'hover')
this.gantt.show_popup({
x: e.offsetX || e.layerX,
y: e.offsetY || e.layerY,
task: this.task,
target: this.$bar,
});
this.gantt.$container
.querySelector(`.highlight-${task_id}`)
.classList.remove('hide');
}, 200);
});
$.on(this.group, 'mouseleave', () => {
clearTimeout(timeout);
if (this.gantt.options.popup_on === 'hover')
this.gantt.popup?.hide?.();
this.gantt.$container
.querySelector(`.highlight-${task_id}`)
.classList.add('hide');
});
$.on(this.group, 'click', () => {
this.gantt.trigger_event('click', [this.task]);
});
$.on(this.group, 'dblclick', (e) => {
if (this.action_completed) {
// just finished a move action, wait for a few seconds
return;
}
this.group.classList.remove('active');
if (this.gantt.popup)
this.gantt.popup.parent.classList.remove('hide');
this.gantt.trigger_event('double_click', [this.task]);
});
let tapedTwice = false;
$.on(this.group, 'touchstart', (e) => {
if (!tapedTwice) {
tapedTwice = true;
setTimeout(function () { tapedTwice = false; }, 300);
return false;
}
e.preventDefault();
//action on double tap goes below
if (this.action_completed) {
// just finished a move action, wait for a few seconds
return;
}
this.group.classList.remove('active');
if (this.gantt.popup)
this.gantt.popup.parent.classList.remove('hide');
this.gantt.trigger_event('double_click', [this.task]);
});
}
update_bar_position({ x = null, width = null }) {
const bar = this.$bar;
if (x) {
const xs = this.task.dependencies.map((dep) => {
return this.gantt.get_bar(dep).$bar.getX();
});
const valid_x = xs.reduce((prev, curr) => {
return prev && x >= curr;
}, true);
if (!valid_x) return;
this.update_attr(bar, 'x', x);
this.x = x;
this.$date_highlight.style.left = x + 'px';
}
if (width > 0) {
this.update_attr(bar, 'width', width);
this.$date_highlight.style.width = width + 'px';
}
this.update_label_position();
this.update_handle_position();
this.date_changed();
this.compute_duration();
if (this.gantt.options.show_expected_progress) {
this.update_expected_progressbar_position();
}
this.update_progressbar_position();
this.update_arrow_position();
}
update_label_position_on_horizontal_scroll({ x, sx }) {
const container =
this.gantt.$container.querySelector('.gantt-container');
const label = this.group.querySelector('.bar-label');
const img = this.group.querySelector('.bar-img') || '';
const img_mask = this.bar_group.querySelector('.img_mask') || '';
let barWidthLimit = this.$bar.getX() + this.$bar.getWidth();
let newLabelX = label.getX() + x;
let newImgX = (img && img.getX() + x) || 0;
let imgWidth = (img && img.getBBox().width + 7) || 7;
let labelEndX = newLabelX + label.getBBox().width + 7;
let viewportCentral = sx + container.clientWidth / 2;
if (label.classList.contains('big')) return;
if (labelEndX < barWidthLimit && x > 0 && labelEndX < viewportCentral) {
label.setAttribute('x', newLabelX);
if (img) {
img.setAttribute('x', newImgX);
img_mask.setAttribute('x', newImgX);
}
} else if (
newLabelX - imgWidth > this.$bar.getX() &&
x < 0 &&
labelEndX > viewportCentral
) {
label.setAttribute('x', newLabelX);
if (img) {
img.setAttribute('x', newImgX);
img_mask.setAttribute('x', newImgX);
}
}
}
date_changed() {
let changed = false;
const { new_start_date, new_end_date } = this.compute_start_end_date();
if (Number(this.task._start) !== Number(new_start_date)) {
changed = true;
this.task._start = new_start_date;
}
if (Number(this.task._end) !== Number(new_end_date)) {
changed = true;
this.task._end = new_end_date;
}
if (!changed) return;
this.gantt.trigger_event('date_change', [
this.task,
new_start_date,
date_utils.add(new_end_date, -1, 'second'),
]);
}
progress_changed() {
this.task.progress = this.compute_progress();
this.gantt.trigger_event('progress_change', [
this.task,
this.task.progress,
]);
}
set_action_completed() {
this.action_completed = true;
setTimeout(() => (this.action_completed = false), 1000);
}
compute_start_end_date() {
const bar = this.$bar;
const x_in_units = bar.getX() / this.gantt.config.column_width;
let new_start_date = date_utils.add(
this.gantt.gantt_start,
x_in_units * this.gantt.config.step,
this.gantt.config.unit,
);
const width_in_units = bar.getWidth() / this.gantt.config.column_width;
const new_end_date = date_utils.add(
new_start_date,
width_in_units * this.gantt.config.step,
this.gantt.config.unit,
);
return { new_start_date, new_end_date };
}
compute_progress() {
this.progress_width = this.$bar_progress.getWidth();
this.x = this.$bar_progress.getBBox().x;
const progress_area = this.x + this.progress_width;
const progress =
this.progress_width -
this.gantt.config.ignored_positions.reduce((acc, val) => {
return acc + (val >= this.x && val <= progress_area);
}, 0) *
this.gantt.config.column_width;
if (progress < 0) return 0;
const total =
this.$bar.getWidth() -
this.ignored_duration_raw * this.gantt.config.column_width;
return parseInt((progress / total) * 100, 10);
}
compute_expected_progress() {
this.expected_progress =
date_utils.diff(date_utils.today(), this.task._start, 'hour') /
this.gantt.config.step;
this.expected_progress =
((this.expected_progress < this.duration
? this.expected_progress
: this.duration) *
100) /
this.duration;
}
compute_x() {
const { column_width } = this.gantt.config;
const task_start = this.task._start;
const gantt_start = this.gantt.gantt_start;
const diff =
date_utils.diff(task_start, gantt_start, this.gantt.config.unit) /
this.gantt.config.step;
let x = diff * column_width;
/* Since the column width is based on 30,
we count the month-difference, multiply it by 30 for a "pseudo-month"
and then add the days in the month, making sure the number does not exceed 29
so it is within the column */
// if (this.gantt.view_is('Month')) {
// const diffDaysBasedOn30DayMonths =
// date_utils.diff(task_start, gantt_start, 'month') * 30;
// const dayInMonth = Math.min(
// 29,
// date_utils.format(
// task_start,
// 'DD',
// this.gantt.options.language,
// ),
// );
// const diff = diffDaysBasedOn30DayMonths + dayInMonth;
// x = (diff * column_width) / 30;
// }
this.x = x;
}
compute_y() {
this.y =
this.gantt.config.header_height +
this.gantt.options.padding / 2 +
this.task._index * (this.height + this.gantt.options.padding);
}
compute_duration() {
let actual_duration_in_days = 0,
duration_in_days = 0;
for (
let d = new Date(this.task._start);
d < this.task._end;
d.setDate(d.getDate() + 1)
) {
duration_in_days++;
if (
!this.gantt.config.ignored_dates.find(
(k) => k.getTime() === d.getTime(),
) &&
(!this.gantt.config.ignored_function ||
!this.gantt.config.ignored_function(d))
) {
actual_duration_in_days++;
}
}
this.task.actual_duration = actual_duration_in_days;
this.task.ignored_duration = duration_in_days - actual_duration_in_days;
this.duration =
date_utils.convert_scales(
duration_in_days + 'd',
this.gantt.config.unit,
) / this.gantt.config.step;
this.actual_duration_raw =
date_utils.convert_scales(
actual_duration_in_days + 'd',
this.gantt.config.unit,
) / this.gantt.config.step;
this.ignored_duration_raw = this.duration - this.actual_duration_raw;
}
update_attr(element, attr, value) {
value = +value;
if (!isNaN(value)) {
element.setAttribute(attr, value);
}
return element;
}
update_expected_progressbar_position() {
if (this.invalid) return;
this.$expected_bar_progress.setAttribute('x', this.$bar.getX());
this.compute_expected_progress();
this.$expected_bar_progress.setAttribute(
'width',
this.gantt.config.column_width *
this.actual_duration_raw *
(this.expected_progress / 100) || 0,
);
}
update_progressbar_position() {
if (this.invalid || this.gantt.options.readonly) return;
this.$bar_progress.setAttribute('x', this.$bar.getX());
this.$bar_progress.setAttribute(
'width',
this.calculate_progress_width(),
);
}
update_label_position() {
const img_mask = this.bar_group.querySelector('.img_mask') || '';
const bar = this.$bar,
label = this.group.querySelector('.bar-label'),
img = this.group.querySelector('.bar-img');
let padding = 5;
let x_offset_label_img = this.image_size + 10;
const labelWidth = label.getBBox().width;
const barWidth = bar.getWidth();
if (labelWidth > barWidth) {
label.classList.add('big');
if (img) {
img.setAttribute('x', bar.getEndX() + padding);
img_mask.setAttribute('x', bar.getEndX() + padding);
label.setAttribute('x', bar.getEndX() + x_offset_label_img);
} else {
label.setAttribute('x', bar.getEndX() + padding);
}
} else {
label.classList.remove('big');
if (img) {
img.setAttribute('x', bar.getX() + padding);
img_mask.setAttribute('x', bar.getX() + padding);
label.setAttribute(
'x',
bar.getX() + barWidth / 2 + x_offset_label_img,
);
} else {
label.setAttribute(
'x',
bar.getX() + barWidth / 2 - labelWidth / 2,
);
}
}
}
update_handle_position() {
if (this.invalid || this.gantt.options.readonly) return;
const bar = this.$bar;
this.handle_group
.querySelector('.handle.left')
.setAttribute('x', bar.getX());
this.handle_group
.querySelector('.handle.right')
.setAttribute('x', bar.getEndX());
const handle = this.group.querySelector('.handle.progress');
handle && handle.setAttribute('cx', this.$bar_progress.getEndX());
}
update_arrow_position() {
this.arrows = this.arrows || [];
for (let arrow of this.arrows) {
arrow.update();
}
}
}

View File

@ -6,44 +6,52 @@ const MINUTE = 'minute';
const SECOND = 'second'; const SECOND = 'second';
const MILLISECOND = 'millisecond'; const MILLISECOND = 'millisecond';
const month_names = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
export default { export default {
parse(date, date_separator = '-', time_separator = ':') { 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) { if (date instanceof Date) {
return date; return date;
} }
if (typeof date === 'string') { if (typeof date === 'string') {
let date_parts, time_parts; let date_parts, time_parts;
const parts = date.split(' '); const parts = date.split(' ');
date_parts = parts[0] date_parts = parts[0]
.split(date_separator) .split(date_separator)
.map(val => parseInt(val, 10)); .map((val) => parseInt(val, 10));
time_parts = parts[1] && parts[1].split(time_separator); time_parts = parts[1] && parts[1].split(time_separator);
// month is 0 indexed // month is 0 indexed
date_parts[1] = date_parts[1] - 1; date_parts[1] = date_parts[1] ? date_parts[1] - 1 : 0;
let vals = date_parts; let vals = date_parts;
if (time_parts && time_parts.length) { 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); vals = vals.concat(time_parts);
} }
return new Date(...vals); return new Date(...vals);
} }
}, },
@ -58,16 +66,30 @@ export default {
val = val + 1; val = val + 1;
} }
if (i === 6) {
return padStart(val + '', 3, '0');
}
return padStart(val + '', 2, '0'); return padStart(val + '', 2, '0');
}); });
const date_string = `${vals[0]}-${vals[1]}-${vals[2]}`; const date_string = `${vals[0]}-${vals[1]}-${vals[2]}`;
const time_string = `${vals[3]}:${vals[4]}:${vals[5]}`; const time_string = `${vals[3]}:${vals[4]}:${vals[5]}.${vals[6]}`;
return date_string + (with_time ? ' ' + time_string : ''); return date_string + (with_time ? ' ' + time_string : '');
}, },
format(date, format_string = 'YYYY-MM-DD HH:mm:ss') { format(date, date_format = 'YYYY-MM-DD HH:mm:ss.SSS', lang = 'en') {
const values = this.get_date_values(date).map(d => padStart(d, 2, 0)); const dateTimeFormat = new Intl.DateTimeFormat(lang, {
month: 'long',
});
const dateTimeFormatShort = new Intl.DateTimeFormat(lang, {
month: 'short',
});
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 = { const format_map = {
YYYY: values[0], YYYY: values[0],
MM: padStart(+values[1] + 1, 2, 0), MM: padStart(+values[1] + 1, 2, 0),
@ -75,47 +97,76 @@ export default {
HH: values[3], HH: values[3],
mm: values[4], mm: values[4],
ss: values[5], ss: values[5],
SSS: values[6],
D: values[2], D: values[2],
MMMM: month_names[+values[1]], MMMM: month_name_capitalized,
MMM: month_names[+values[1]] MMM: dateTimeFormatShort.format(date),
}; };
let str = format_string; let str = date_format;
const formatted_values = [];
Object.keys(format_map) Object.keys(format_map)
.sort((a, b) => b.length - a.length) // big string first .sort((a, b) => b.length - a.length) // big string first
.forEach(key => { .forEach((key) => {
str = str.replace(key, format_map[key]); if (str.includes(key)) {
str = str.replaceAll(key, `$${formatted_values.length}`);
formatted_values.push(format_map[key]);
}
}); });
formatted_values.forEach((value, i) => {
str = str.replaceAll(`$${i}`, value);
});
return str; return str;
}, },
diff(date_a, date_b, scale = DAY) { diff(date_a, date_b, scale = 'day') {
let milliseconds, seconds, hours, minutes, days, months, years; let milliseconds, seconds, hours, minutes, days, months, years;
milliseconds = date_a - date_b; milliseconds =
date_a -
date_b +
(date_b.getTimezoneOffset() - date_a.getTimezoneOffset()) * 60000;
seconds = milliseconds / 1000; seconds = milliseconds / 1000;
minutes = seconds / 60; minutes = seconds / 60;
hours = minutes / 60; hours = minutes / 60;
days = hours / 24; days = hours / 24;
months = days / 30; // Calculate months across years
let yearDiff = date_a.getFullYear() - date_b.getFullYear();
let monthDiff = date_a.getMonth() - date_b.getMonth();
// calculate extra
monthDiff += (days % 30) / 30;
/* If monthDiff is negative, date_b is in an earlier month than
date_a and thus subtracted from the year difference in months */
months = yearDiff * 12 + monthDiff;
/* If date_a's (e.g. march 1st) day of the month is smaller than date_b (e.g. february 28th),
adjust the month difference */
if (date_a.getDate() < date_b.getDate()) {
months--;
}
// Calculate years based on actual months
years = months / 12; years = months / 12;
if (!scale.endsWith('s')) { if (!scale.endsWith('s')) {
scale += 's'; scale += 's';
} }
return Math.floor( return (
{ Math.round(
milliseconds, {
seconds, milliseconds,
minutes, seconds,
hours, minutes,
days, hours,
months, days,
years months,
}[scale] years,
}[scale] * 100,
) / 100
); );
}, },
@ -137,7 +188,7 @@ export default {
date.getHours() + (scale === HOUR ? qty : 0), date.getHours() + (scale === HOUR ? qty : 0),
date.getMinutes() + (scale === MINUTE ? qty : 0), date.getMinutes() + (scale === MINUTE ? qty : 0),
date.getSeconds() + (scale === SECOND ? qty : 0), date.getSeconds() + (scale === SECOND ? qty : 0),
date.getMilliseconds() + (scale === MILLISECOND ? qty : 0) date.getMilliseconds() + (scale === MILLISECOND ? qty : 0),
]; ];
return new Date(...vals); return new Date(...vals);
}, },
@ -150,7 +201,7 @@ export default {
[HOUR]: 3, [HOUR]: 3,
[MINUTE]: 2, [MINUTE]: 2,
[SECOND]: 1, [SECOND]: 1,
[MILLISECOND]: 0 [MILLISECOND]: 0,
}; };
function should_reset(_scale) { function should_reset(_scale) {
@ -165,7 +216,7 @@ export default {
should_reset(DAY) ? 0 : date.getHours(), should_reset(DAY) ? 0 : date.getHours(),
should_reset(HOUR) ? 0 : date.getMinutes(), should_reset(HOUR) ? 0 : date.getMinutes(),
should_reset(MINUTE) ? 0 : date.getSeconds(), should_reset(MINUTE) ? 0 : date.getSeconds(),
should_reset(SECOND) ? 0 : date.getMilliseconds() should_reset(SECOND) ? 0 : date.getMilliseconds(),
]; ];
return new Date(...vals); return new Date(...vals);
@ -183,10 +234,25 @@ export default {
date.getHours(), date.getHours(),
date.getMinutes(), date.getMinutes(),
date.getSeconds(), date.getSeconds(),
date.getMilliseconds() date.getMilliseconds(),
]; ];
}, },
convert_scales(period, to_scale) {
const TO_DAYS = {
millisecond: 1 / 60 / 60 / 24 / 1000,
second: 1 / 60 / 60 / 24,
minute: 1 / 60 / 24,
hour: 1 / 24,
day: 1,
month: 30,
year: 365,
};
const { duration, scale } = this.parse_duration(period);
let in_days = duration * TO_DAYS[scale];
return in_days / TO_DAYS[to_scale];
},
get_days_in_month(date) { get_days_in_month(date) {
const no_of_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; const no_of_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
@ -198,11 +264,15 @@ export default {
// Feb // Feb
const year = date.getFullYear(); const year = date.getFullYear();
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { if ((year % 4 === 0 && year % 100 != 0) || year % 400 === 0) {
return 29; return 29;
} }
return 28; return 28;
} },
get_days_in_year(date) {
return date.getFullYear() % 4 ? 365 : 366;
},
}; };
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart

160
src/defaults.js Normal file
View File

@ -0,0 +1,160 @@
import date_utils from './date_utils';
function getDecade(d) {
const year = d.getFullYear();
return year - (year % 10) + '';
}
function formatWeek(d, ld, lang) {
let endOfWeek = date_utils.add(d, 6, 'day');
let endFormat = endOfWeek.getMonth() !== d.getMonth() ? 'D MMM' : 'D';
let beginFormat = !ld || d.getMonth() !== ld.getMonth() ? 'D MMM' : 'D';
return `${date_utils.format(d, beginFormat, lang)} - ${date_utils.format(endOfWeek, endFormat, lang)}`;
}
const DEFAULT_VIEW_MODES = [
{
name: 'Hour',
padding: '7d',
step: '1h',
date_format: 'YYYY-MM-DD HH:',
lower_text: 'HH',
upper_text: (d, ld, lang) =>
!ld || d.getDate() !== ld.getDate()
? date_utils.format(d, 'D MMMM', lang)
: '',
upper_text_frequency: 24,
},
{
name: 'Quarter Day',
padding: '7d',
step: '6h',
date_format: 'YYYY-MM-DD HH:',
lower_text: 'HH',
upper_text: (d, ld, lang) =>
!ld || d.getDate() !== ld.getDate()
? date_utils.format(d, 'D MMM', lang)
: '',
upper_text_frequency: 4,
},
{
name: 'Half Day',
padding: '14d',
step: '12h',
date_format: 'YYYY-MM-DD HH:',
lower_text: 'HH',
upper_text: (d, ld, lang) =>
!ld || d.getDate() !== ld.getDate()
? d.getMonth() !== d.getMonth()
? date_utils.format(d, 'D MMM', lang)
: date_utils.format(d, 'D', lang)
: '',
upper_text_frequency: 2,
},
{
name: 'Day',
padding: '7d',
date_format: 'YYYY-MM-DD',
step: '1d',
lower_text: (d, ld, lang) =>
!ld || d.getDate() !== ld.getDate()
? date_utils.format(d, 'D', lang)
: '',
upper_text: (d, ld, lang) =>
!ld || d.getMonth() !== ld.getMonth()
? date_utils.format(d, 'MMMM', lang)
: '',
thick_line: (d) => d.getDay() === 1,
},
{
name: 'Week',
padding: '1m',
step: '7d',
date_format: 'YYYY-MM-DD',
column_width: 140,
lower_text: formatWeek,
upper_text: (d, ld, lang) =>
!ld || d.getMonth() !== ld.getMonth()
? date_utils.format(d, 'MMMM', lang)
: '',
thick_line: (d) => d.getDate() >= 1 && d.getDate() <= 7,
upper_text_frequency: 4,
},
{
name: 'Month',
padding: '2m',
step: '1m',
column_width: 120,
date_format: 'YYYY-MM',
lower_text: 'MMMM',
upper_text: (d, ld, lang) =>
!ld || d.getFullYear() !== ld.getFullYear()
? date_utils.format(d, 'YYYY', lang)
: '',
thick_line: (d) => d.getMonth() % 3 === 0,
snap_at: '7d',
},
{
name: 'Year',
padding: '2y',
step: '1y',
column_width: 120,
date_format: 'YYYY',
upper_text: (d, ld, lang) =>
!ld || getDecade(d) !== getDecade(ld) ? getDecade(d) : '',
lower_text: 'YYYY',
snap_at: '30d',
},
];
const DEFAULT_OPTIONS = {
arrow_curve: 5,
auto_move_label: false,
bar_corner_radius: 3,
bar_height: 30,
container_height: 'auto',
column_width: null,
date_format: 'YYYY-MM-DD HH:mm',
upper_header_height: 45,
lower_header_height: 30,
snap_at: null,
infinite_padding: true,
holidays: { 'var(--g-weekend-highlight-color)': 'weekend' },
ignore: [],
language: 'en',
lines: 'both',
move_dependencies: true,
padding: 18,
popup: (ctx) => {
ctx.set_title(ctx.task.name);
if (ctx.task.description) ctx.set_subtitle(ctx.task.description);
else ctx.set_subtitle('');
const start_date = date_utils.format(
ctx.task._start,
'MMM D',
ctx.chart.options.language,
);
const end_date = date_utils.format(
date_utils.add(ctx.task._end, -1, 'second'),
'MMM D',
ctx.chart.options.language,
);
ctx.set_details(
`${start_date} - ${end_date} (${ctx.task.actual_duration} days${ctx.task.ignored_duration ? ' + ' + ctx.task.ignored_duration + ' excluded' : ''})<br/>Progress: ${Math.floor(ctx.task.progress * 100) / 100}%`,
);
},
popup_on: 'click',
readonly_progress: false,
readonly_dates: false,
readonly: false,
scroll_to: 'today',
show_expected_progress: false,
today_button: true,
view_mode: 'Day',
view_mode_select: false,
view_modes: DEFAULT_VIEW_MODES,
};
export { DEFAULT_OPTIONS, DEFAULT_VIEW_MODES };

View File

@ -1,166 +0,0 @@
$bar-color: #b8c2cc;
$bar-stroke: #8D99A6;
$border-color: #e0e0e0;
$light-bg: #f5f5f5;
$light-border-color: #ebeff2;
$light-yellow: #fcf8e3;
$text-muted: #666;
$text-light: #555;
$text-color: #333;
$blue: #a3a3ff;
$handle-color: #ddd;
.gantt {
.grid-background {
fill: none;
}
.grid-header {
fill: #ffffff;
stroke: $border-color;
stroke-width: 1.4;
}
.grid-row {
fill: #ffffff;
}
.grid-row:nth-child(even) {
fill: $light-bg;
}
.row-line {
stroke: $light-border-color;
}
.tick {
stroke: $border-color;
stroke-width: 0.2;
&.thick {
stroke-width: 0.4;
}
}
.today-highlight {
fill: $light-yellow;
opacity: 0.5;
}
.arrow {
fill: none;
stroke: $text-muted;
stroke-width: 1.4;
}
.bar {
fill: $bar-color;
stroke: $bar-stroke;
stroke-width: 0;
transition: stroke-width .3s ease;
user-select: none;
}
.bar-progress {
fill: $blue;
}
.bar-invalid {
fill: transparent;
stroke: $bar-stroke;
stroke-width: 1;
stroke-dasharray: 5;
&~.bar-label {
fill: $text-light;
}
}
.bar-label {
fill: #fff;
dominant-baseline: central;
text-anchor: middle;
font-size: 12px;
font-weight: lighter;
&.big {
fill: $text-light;
text-anchor: start;
}
}
.handle {
fill: $handle-color;
cursor: ew-resize;
opacity: 0;
visibility: hidden;
transition: opacity .3s ease;
}
.bar-wrapper {
cursor: pointer;
&:hover {
.bar {
fill: darken($bar-color, 5);
}
.bar-progress {
fill: darken($blue, 5);
}
.handle {
visibility: visible;
opacity: 1;
}
}
&.active {
.bar {
fill: darken($bar-color, 5);
}
.bar-progress {
fill: darken($blue, 5);
}
}
}
.lower-text, .upper-text {
font-size: 12px;
text-anchor: middle;
}
.upper-text {
fill: $text-light;
}
.lower-text {
fill: $text-color;
}
.hide {
display: none;
}
}
.gantt-container {
position: relative;
font-size: 12px;
.popup-wrapper {
position: absolute;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.8);
padding: 0;
color: #959da5;
border-radius: 3px;
.title {
border-bottom: 3px solid $blue;
padding: 10px;
}
.subtitle {
padding: 10px;
color: #dfe2e5;
}
.pointer {
position: absolute;
height: 5px;
margin: 0 0 0 -5px;
border: 5px solid transparent;
border-top-color: rgba(0, 0, 0, 0.8);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,9 @@
export default class Popup { export default class Popup {
constructor(parent) { constructor(parent, popup_func, gantt) {
this.parent = parent; this.parent = parent;
this.popup_func = popup_func;
this.gantt = gantt;
this.make(); this.make();
} }
@ -8,62 +11,51 @@ export default class Popup {
this.parent.innerHTML = ` this.parent.innerHTML = `
<div class="title"></div> <div class="title"></div>
<div class="subtitle"></div> <div class="subtitle"></div>
<div class="pointer"></div> <div class="details"></div>
<div class="actions"></div>
`; `;
this.hide(); this.hide();
this.title = this.parent.querySelector('.title'); this.title = this.parent.querySelector('.title');
this.subtitle = this.parent.querySelector('.subtitle'); this.subtitle = this.parent.querySelector('.subtitle');
this.pointer = this.parent.querySelector('.pointer'); this.details = this.parent.querySelector('.details');
this.actions = this.parent.querySelector('.actions');
} }
show(options) { show({ x, y, task, target }) {
if (!options.target_element) { this.actions.innerHTML = '';
throw new Error('target_element is required to show popup'); let html = this.popup_func({
} task,
if (!options.position) { chart: this.gantt,
options.position = 'left'; get_title: () => this.title,
} set_title: (title) => (this.title.innerHTML = title),
const target_element = options.target_element; get_subtitle: () => this.subtitle,
set_subtitle: (subtitle) => (this.subtitle.innerHTML = subtitle),
get_details: () => this.details,
set_details: (details) => (this.details.innerHTML = details),
add_action: (html, func) => {
let action = this.gantt.create_el({
classes: 'action-btn',
type: 'button',
append_to: this.actions,
});
if (typeof html === 'function') html = html(task);
action.innerHTML = html;
action.onclick = (e) => func(task, this.gantt, e);
},
});
if (html === false) return;
if (html) this.parent.innerHTML = html;
// set data if (this.actions.innerHTML === '') this.actions.remove();
this.title.innerHTML = options.title; else this.parent.appendChild(this.actions);
this.subtitle.innerHTML = options.subtitle;
this.parent.style.width = this.parent.clientWidth + 'px'; this.parent.style.left = x + 10 + 'px';
this.parent.style.top = y - 10 + 'px';
// set position this.parent.classList.remove('hide');
let position_meta;
if (target_element instanceof HTMLElement) {
position_meta = target_element.getBoundingClientRect();
} else if (target_element instanceof SVGElement) {
position_meta = options.target_element.getBBox();
}
if (options.position === 'left') {
this.parent.style.left =
position_meta.x + (position_meta.width + 10) + 'px';
this.parent.style.top =
position_meta.y -
this.title.clientHeight / 2 +
position_meta.height / 2 +
'px';
this.pointer.style.transform = 'rotateZ(90deg)';
this.pointer.style.left = '-7px';
this.pointer.style.top =
this.title.clientHeight / 2 -
this.pointer.getBoundingClientRect().height +
2 +
'px';
}
// show
this.parent.style.opacity = 1;
} }
hide() { hide() {
this.parent.style.opacity = 0; this.parent.classList.add('hide');
} }
} }

87
src/styles/dark.css Normal file
View File

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

343
src/styles/gantt.css Normal file
View File

@ -0,0 +1,343 @@
@import './light.css';
.gantt-container {
line-height: 14.5px;
position: relative;
overflow: auto;
font-size: 12px;
height: var(--gv-grid-height);
width: 100%;
border-radius: 8px;
& .popup-wrapper {
position: absolute;
top: 0;
left: 0;
background: #fff;
box-shadow: 0px 10px 24px -3px rgba(0, 0, 0, 0.2);
padding: 10px;
border-radius: 5px;
width: max-content;
z-index: 1000;
& .title {
margin-bottom: 2px;
color: var(--g-text-dark);
font-size: 0.85rem;
font-weight: 650;
line-height: 15px;
}
& .subtitle {
color: var(--g-text-dark);
font-size: 0.8rem;
margin-bottom: 5px;
}
& .details {
color: var(--g-text-muted);
font-size: 0.7rem;
}
& .actions {
margin-top: 10px;
margin-left: 3px;
}
& .action-btn {
border: none;
padding: 5px 8px;
background-color: var(--g-popup-actions);
border-right: 1px solid var(--g-text-light);
&:hover {
background-color: brightness(97%);
}
&:first-child {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
&:last-child {
border-right: none;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
}
& .grid-header {
height: calc(
var(--gv-lower-header-height) + var(--gv-upper-header-height) + 10px
);
background-color: var(--g-header-background);
position: sticky;
top: 0;
left: 0;
border-bottom: 1px solid var(--g-row-border-color);
z-index: 1000;
}
& .lower-text,
& .upper-text {
text-anchor: middle;
}
& .upper-header {
height: var(--gv-upper-header-height);
}
& .lower-header {
height: var(--gv-lower-header-height);
}
& .lower-text {
font-size: 12px;
position: absolute;
width: calc(var(--gv-column-width) * 0.8);
height: calc(var(--gv-lower-header-height) * 0.8);
margin: 0 calc(var(--gv-column-width) * 0.1);
align-content: center;
text-align: center;
color: var(--g-text-muted);
}
& .upper-text {
position: absolute;
width: fit-content;
font-weight: 500;
font-size: 14px;
color: var(--g-text-dark);
height: calc(var(--gv-lower-header-height) * 0.66);
}
& .current-upper {
position: sticky;
left: 0 !important;
padding-left: 17px;
background: white;
}
& .side-header {
position: sticky;
top: 0;
right: 0;
float: right;
z-index: 1000;
line-height: 20px;
font-weight: 400;
width: max-content;
margin-left: auto;
padding-right: 10px;
padding-top: 10px;
background: var(--g-header-background);
display: flex;
}
& .side-header * {
transition-property: background-color;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
background-color: var(--g-actions-background);
border-radius: 0.5rem;
border: none;
padding: 5px 8px;
color: var(--g-text-dark);
font-size: 14px;
letter-spacing: 0.02em;
font-weight: 420;
box-sizing: content-box;
margin-right: 5px;
&:last-child {
margin-right: 0;
}
&:hover {
filter: brightness(97.5%);
}
}
& .side-header select {
width: 60px;
padding-top: 2px;
padding-bottom: 2px;
}
& .side-header select:focus {
outline: none;
}
& .date-range-highlight {
background-color: var(--g-progress-color);
border-radius: 12px;
height: calc(var(--gv-lower-header-height) - 6px);
top: calc(var(--gv-upper-header-height) + 5px);
position: absolute;
}
& .current-highlight {
position: absolute;
background: var(--g-today-highlight);
width: 1px;
z-index: 999;
}
& .current-ball-highlight {
position: absolute;
background: var(--g-today-highlight);
z-index: 1001;
border-radius: 50%;
}
& .current-date-highlight {
background: var(--g-today-highlight);
color: var(--g-text-light);
border-radius: 5px;
}
& .holiday-label {
position: absolute;
top: 0;
left: 0;
opacity: 0;
z-index: 1000;
background: --g-weekend-label-color;
border-radius: 5px;
padding: 2px 5px;
&.show {
opacity: 100;
}
}
& .extras {
position: sticky;
left: 0px;
& .adjust {
position: absolute;
left: 8px;
top: calc(var(--gv-grid-height) - 60px);
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: none;
padding: 8px;
border-radius: 3px;
}
}
.hide {
display: none;
}
}
.gantt {
user-select: none;
-webkit-user-select: none;
position: absolute;
& .grid-background {
fill: none;
}
& .grid-row {
fill: var(--g-row-color);
}
& .row-line {
stroke: var(--g-border-color);
}
& .tick {
stroke: var(--g-tick-color);
stroke-width: 0.4;
&.thick {
stroke: var(--g-tick-color-thick);
stroke-width: 0.7;
}
}
& .arrow {
fill: none;
stroke: var(--g-arrow-color);
stroke-width: 1.5;
}
& .bar-wrapper .bar {
fill: var(--g-bar-color);
stroke: var(--g-bar-border);
stroke-width: 0;
transition: stroke-width 0.3s ease;
}
& .bar-progress {
fill: var(--g-progress-color);
border-radius: 4px;
}
& .bar-expected-progress {
fill: var(--g-expected-progress);
}
& .bar-invalid {
fill: transparent;
stroke: var(--g-bar-border);
stroke-width: 1;
stroke-dasharray: 5;
& ~ .bar-label {
fill: var(--g-text-light);
}
}
& .bar-label {
fill: var(--g-text-dark);
dominant-baseline: central;
font-family: Helvetica;
font-size: 13px;
font-weight: 400;
&.big {
fill: var(--g-text-dark);
text-anchor: start;
}
}
& .handle {
fill: var(--g-handle-color);
opacity: 0;
transition: opacity 0.3s ease;
&.active,
&.visible {
cursor: ew-resize;
opacity: 1;
}
}
& .handle.progress {
fill: var(--g-text-muted);
}
& .bar-wrapper {
cursor: pointer;
& .bar {
outline: 1px solid var(--g-row-border-color);
border-radius: 3px;
}
&:hover {
.bar {
transition: transform 0.3s ease;
}
.date-range-highlight {
display: block;
}
}
}
}

22
src/styles/light.css Normal file
View File

@ -0,0 +1,22 @@
:root {
--g-arrow-color: #1f2937;
--g-bar-color: #fff;
--g-bar-border: #fff;
--g-tick-color-thick: #ededed;
--g-tick-color: #f3f3f3;
--g-actions-background: #f3f3f3;
--g-border-color: #ebeff2;
--g-text-muted: #7c7c7c;
--g-text-light: #fff;
--g-text-dark: #171717;
--g-progress-color: #dbdbdb;
--g-handle-color: #37352f;
--g-weekend-label-color: #dcdce4;
--g-expected-progress: #c4c4e9;
--g-header-background: #fff;
--g-row-color: #fdfdfd;
--g-row-border-color: #c7c7c7;
--g-today-highlight: #37352f;
--g-popup-actions: #ebeff2;
--g-weekend-highlight-color: #f7f7f7;
}

View File

@ -12,6 +12,8 @@ export function createSVG(tag, attrs) {
parent.appendChild(elem); parent.appendChild(elem);
} else if (attr === 'innerHTML') { } else if (attr === 'innerHTML') {
elem.innerHTML = attrs.innerHTML; elem.innerHTML = attrs.innerHTML;
} else if (attr === 'clipPath') {
elem.setAttribute('clip-path', 'url(#' + attrs[attr] + ')');
} else { } else {
elem.setAttribute(attr, attrs[attr]); elem.setAttribute(attr, attrs[attr]);
} }
@ -38,7 +40,7 @@ function getAnimationElement(
from, from,
to, to,
dur = '0.4s', dur = '0.4s',
begin = '0.1s' begin = '0.1s',
) { ) {
const animEl = svgElement.querySelector('animate'); const animEl = svgElement.querySelector('animate');
if (animEl) { if (animEl) {
@ -47,7 +49,7 @@ function getAnimationElement(
from, from,
to, to,
dur, dur,
begin: 'click + ' + begin // artificial click begin: 'click + ' + begin, // artificial click
}); });
return svgElement; return svgElement;
} }
@ -61,7 +63,7 @@ function getAnimationElement(
calcMode: 'spline', calcMode: 'spline',
values: from + ';' + to, values: from + ';' + to,
keyTimes: '0; 1', keyTimes: '0; 1',
keySplines: cubic_bezier('ease-out') keySplines: cubic_bezier('ease-out'),
}); });
svgElement.appendChild(animateElement); svgElement.appendChild(animateElement);
@ -74,7 +76,7 @@ function cubic_bezier(name) {
linear: '0 0 1 1', linear: '0 0 1 1',
'ease-in': '.42 0 1 1', 'ease-in': '.42 0 1 1',
'ease-out': '0 0 .58 1', 'ease-out': '0 0 .58 1',
'ease-in-out': '.42 0 .58 1' 'ease-in-out': '.42 0 .58 1',
}[name]; }[name];
} }
@ -92,13 +94,13 @@ $.off = (element, event, handler) => {
}; };
$.bind = (element, event, callback) => { $.bind = (element, event, callback) => {
event.split(/\s+/).forEach(function(event) { event.split(/\s+/).forEach(function (event) {
element.addEventListener(event, callback); element.addEventListener(event, callback);
}); });
}; };
$.delegate = (element, event, selector, callback) => { $.delegate = (element, event, selector, callback) => {
element.addEventListener(event, function(e) { element.addEventListener(event, function (e) {
const delegatedTarget = e.target.closest(selector); const delegatedTarget = e.target.closest(selector);
if (delegatedTarget) { if (delegatedTarget) {
e.delegatedTarget = delegatedTarget; e.delegatedTarget = delegatedTarget;

View File

@ -19,11 +19,45 @@ test('Parse: parses string datetime', () => {
expect(date.getSeconds()).toBe(34); expect(date.getSeconds()).toBe(34);
}); });
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);
});
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);
});
test('Format: converts date object to string', () => { test('Format: converts date object to string', () => {
const date = new Date('2017-09-18'); const date = new Date('2017-09-18');
expect(date_utils.to_string(date)).toBe('2017-09-18'); expect(date_utils.to_string(date)).toBe('2017-09-18');
}); });
test('Format: converts date object to string', () => {
const date = new Date('2016-02-29 16:08:34.3');
expect(date_utils.to_string(date, true)).toBe('2016-02-29 16:08:34.300');
});
test('Format: converts date object to string', () => {
const date = new Date('2016-02-29 16:08:34.3');
expect(date_utils.to_string(date, true)).toBe('2016-02-29 16:08:34.300');
});
test('Parse: returns Date Object as is', () => { test('Parse: returns Date Object as is', () => {
const d = new Date(); const d = new Date();
const date = date_utils.parse(d); const date = date_utils.parse(d);
@ -41,31 +75,41 @@ test('Diff: returns diff between 2 date objects', () => {
}); });
test('StartOf', () => { test('StartOf', () => {
const date = date_utils.parse('2017-08-12 15:07:34'); 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_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'); const start_of_minute = date_utils.start_of(date, 'minute');
expect(date_utils.to_string(start_of_minute, true)).toBe( expect(date_utils.to_string(start_of_minute, true)).toBe(
'2017-08-12 15:07:00' '2017-08-12 15:07:00.000',
); );
const start_of_hour = date_utils.start_of(date, 'hour'); const start_of_hour = date_utils.start_of(date, 'hour');
expect(date_utils.to_string(start_of_hour, true)).toBe( expect(date_utils.to_string(start_of_hour, true)).toBe(
'2017-08-12 15:00:00' '2017-08-12 15:00:00.000',
); );
const start_of_day = date_utils.start_of(date, 'day'); const start_of_day = date_utils.start_of(date, 'day');
expect(date_utils.to_string(start_of_day, true)).toBe( expect(date_utils.to_string(start_of_day, true)).toBe(
'2017-08-12 00:00:00' '2017-08-12 00:00:00.000',
); );
const start_of_month = date_utils.start_of(date, 'month'); const start_of_month = date_utils.start_of(date, 'month');
expect(date_utils.to_string(start_of_month, true)).toBe( expect(date_utils.to_string(start_of_month, true)).toBe(
'2017-08-01 00:00:00' '2017-08-01 00:00:00.000',
); );
const start_of_year = date_utils.start_of(date, 'year'); const start_of_year = date_utils.start_of(date, 'year');
expect(date_utils.to_string(start_of_year, true)).toBe( expect(date_utils.to_string(start_of_year, true)).toBe(
'2017-01-01 00:00:00' '2017-01-01 00:00:00.000',
); );
}); });
@ -73,3 +117,8 @@ test('format', () => {
const date = date_utils.parse('2017-08-12 15:07:23'); const date = date_utils.parse('2017-08-12 15:07:23');
expect(date_utils.format(date, 'YYYY-MM-DD')).toBe('2017-08-12'); expect(date_utils.format(date, 'YYYY-MM-DD')).toBe('2017-08-12');
}); });
test('format', () => {
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');
});

21
vite.config.js Normal file
View File

@ -0,0 +1,21 @@
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: {
output: {
format: 'cjs',
assetFileNames: 'frappe-gantt[extname]',
entryFileNames: 'frappe-gantt.[format].js'
},
},
},
output: { interop: 'auto' },
server: { watch: { include: ['dist/*', 'src/*'] } }
});

3869
yarn.lock

File diff suppressed because it is too large Load Diff