Save as PNG functionality

This commit is contained in:
Lukas Zenick
2024-02-17 15:40:42 -06:00
parent 7132bcf572
commit a363efb2d2
3 changed files with 63 additions and 290 deletions

View File

@@ -24,6 +24,7 @@
"clsx": "^2.1.0", "clsx": "^2.1.0",
"highcharts": "^11.3.0", "highcharts": "^11.3.0",
"highcharts-react-official": "^3.2.1", "highcharts-react-official": "^3.2.1",
"html2canvas": "^1.4.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-devtools-core": "^5.0.0", "react-devtools-core": "^5.0.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

327
pnpm-lock.yaml generated
View File

@@ -34,6 +34,9 @@ dependencies:
highcharts-react-official: highcharts-react-official:
specifier: ^3.2.1 specifier: ^3.2.1
version: 3.2.1(highcharts@11.3.0)(react@18.2.0) version: 3.2.1(highcharts@11.3.0)(react@18.2.0)
html2canvas:
specifier: ^1.4.1
version: 1.4.1
react: react:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0 version: 18.2.0
@@ -65,7 +68,7 @@ devDependencies:
version: 1.1.72 version: 1.1.72
'@storybook/addon-designs': '@storybook/addon-designs':
specifier: ^7.0.9 specifier: ^7.0.9
version: 7.0.9(@storybook/addon-docs@7.6.12)(@storybook/addons@7.6.11)(@storybook/components@7.6.12)(@storybook/manager-api@7.6.12)(@storybook/preview-api@7.6.12)(@storybook/theming@7.6.12)(react-dom@18.2.0)(react@18.2.0) version: 7.0.9(@storybook/addon-docs@7.6.15)(@storybook/addons@7.6.11)(@storybook/components@7.6.15)(@storybook/manager-api@7.6.15)(@storybook/preview-api@7.6.15)(@storybook/theming@7.6.15)(react-dom@18.2.0)(react@18.2.0)
'@storybook/addon-essentials': '@storybook/addon-essentials':
specifier: ^7.6.13 specifier: ^7.6.13
version: 7.6.15(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) version: 7.6.15(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0)
@@ -3202,7 +3205,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@storybook/addon-designs@7.0.9(@storybook/addon-docs@7.6.12)(@storybook/addons@7.6.11)(@storybook/components@7.6.12)(@storybook/manager-api@7.6.12)(@storybook/preview-api@7.6.12)(@storybook/theming@7.6.12)(react-dom@18.2.0)(react@18.2.0): /@storybook/addon-designs@7.0.9(@storybook/addon-docs@7.6.15)(@storybook/addons@7.6.11)(@storybook/components@7.6.15)(@storybook/manager-api@7.6.15)(@storybook/preview-api@7.6.15)(@storybook/theming@7.6.15)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-xJdw1/FgkC8ovTdRIL5FyEJaXtF1XPxsb6rsl2jByG+8tXyM0PJ/yFEkBrqn35Dei2i4N7x8EHXFd8DnzriBRg==} resolution: {integrity: sha512-xJdw1/FgkC8ovTdRIL5FyEJaXtF1XPxsb6rsl2jByG+8tXyM0PJ/yFEkBrqn35Dei2i4N7x8EHXFd8DnzriBRg==}
peerDependencies: peerDependencies:
'@storybook/addon-docs': ^7.0.0 '@storybook/addon-docs': ^7.0.0
@@ -3220,50 +3223,16 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@figspec/react': 1.0.3(react@18.2.0) '@figspec/react': 1.0.3(react@18.2.0)
'@storybook/addon-docs': 7.6.12(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-docs': 7.6.15(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0)
'@storybook/addons': 7.6.11(react-dom@18.2.0)(react@18.2.0) '@storybook/addons': 7.6.11(react-dom@18.2.0)(react@18.2.0)
'@storybook/components': 7.6.12(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) '@storybook/components': 7.6.15(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0)
'@storybook/manager-api': 7.6.12(react-dom@18.2.0)(react@18.2.0) '@storybook/manager-api': 7.6.15(react-dom@18.2.0)(react@18.2.0)
'@storybook/preview-api': 7.6.12 '@storybook/preview-api': 7.6.15
'@storybook/theming': 7.6.12(react-dom@18.2.0)(react@18.2.0) '@storybook/theming': 7.6.15(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: true dev: true
/@storybook/addon-docs@7.6.12(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-AzMgnGYfEg+Z1ycJh8MEp44x1DfjRijKCVYNaPFT6o+TjN/9GBaAkV4ydxmQzMEMnnnh/0E9YeHO+ivBVSkNog==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@jest/transform': 29.7.0
'@mdx-js/react': 2.3.0(react@18.2.0)
'@storybook/blocks': 7.6.12(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0)
'@storybook/client-logger': 7.6.12
'@storybook/components': 7.6.12(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0)
'@storybook/csf-plugin': 7.6.12
'@storybook/csf-tools': 7.6.12
'@storybook/global': 5.0.0
'@storybook/mdx2-csf': 1.1.0
'@storybook/node-logger': 7.6.12
'@storybook/postinstall': 7.6.12
'@storybook/preview-api': 7.6.12
'@storybook/react-dom-shim': 7.6.12(react-dom@18.2.0)(react@18.2.0)
'@storybook/theming': 7.6.12(react-dom@18.2.0)(react@18.2.0)
'@storybook/types': 7.6.12
fs-extra: 11.2.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
remark-external-links: 8.0.0
remark-slug: 6.1.0
ts-dedent: 2.2.0
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
- encoding
- supports-color
dev: true
/@storybook/addon-docs@7.6.15(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): /@storybook/addon-docs@7.6.15(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UPODqO+mrYaKyTSAtfRslxOFgSP/v/5vfDx896pbNTC4Sf8xLytoudw4I14hzkHmRdXiOnd21FqXJfmF/Onsvw==} resolution: {integrity: sha512-UPODqO+mrYaKyTSAtfRslxOFgSP/v/5vfDx896pbNTC4Sf8xLytoudw4I14hzkHmRdXiOnd21FqXJfmF/Onsvw==}
peerDependencies: peerDependencies:
@@ -3382,44 +3351,6 @@ packages:
- react-dom - react-dom
dev: true dev: true
/@storybook/blocks@7.6.12(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-T47KOAjgZmhV+Ov59A70inE5edInh1Jh5w/5J5cjpk9a2p4uhd337SnK4B8J5YLhcM2lbKRWJjzIJ0nDZQTdnQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@storybook/channels': 7.6.12
'@storybook/client-logger': 7.6.12
'@storybook/components': 7.6.12(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0)
'@storybook/core-events': 7.6.12
'@storybook/csf': 0.1.2
'@storybook/docs-tools': 7.6.12
'@storybook/global': 5.0.0
'@storybook/manager-api': 7.6.12(react-dom@18.2.0)(react@18.2.0)
'@storybook/preview-api': 7.6.12
'@storybook/theming': 7.6.12(react-dom@18.2.0)(react@18.2.0)
'@storybook/types': 7.6.12
'@types/lodash': 4.14.202
color-convert: 2.0.1
dequal: 2.0.3
lodash: 4.17.21
markdown-to-jsx: 7.4.1(react@18.2.0)
memoizerific: 1.11.3
polished: 4.2.2
react: 18.2.0
react-colorful: 5.6.1(react-dom@18.2.0)(react@18.2.0)
react-dom: 18.2.0(react@18.2.0)
telejson: 7.2.0
tocbot: 4.25.0
ts-dedent: 2.2.0
util-deprecate: 1.0.2
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
- encoding
- supports-color
dev: true
/@storybook/blocks@7.6.15(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): /@storybook/blocks@7.6.15(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-ODP7AVh2iIGblI5WKGokWSHbp9YQHc+Uce7JCGcnDbNavoy64Z6R6G+wXzF5jfl7xQlbhQ8yQCuSSL4GNdYTeA==} resolution: {integrity: sha512-ODP7AVh2iIGblI5WKGokWSHbp9YQHc+Uce7JCGcnDbNavoy64Z6R6G+wXzF5jfl7xQlbhQ8yQCuSSL4GNdYTeA==}
peerDependencies: peerDependencies:
@@ -3531,17 +3462,6 @@ packages:
tiny-invariant: 1.3.1 tiny-invariant: 1.3.1
dev: true dev: true
/@storybook/channels@7.6.12:
resolution: {integrity: sha512-TaPl5Y3lOoVi5kTLgKNRX8xh2sUPekH0Id1l4Ymw+lpgriEY6r60bmkZLysLG1GhlskpQ/da2+S2ap2ht8P2TQ==}
dependencies:
'@storybook/client-logger': 7.6.12
'@storybook/core-events': 7.6.12
'@storybook/global': 5.0.0
qs: 6.11.2
telejson: 7.2.0
tiny-invariant: 1.3.1
dev: true
/@storybook/channels@7.6.15: /@storybook/channels@7.6.15:
resolution: {integrity: sha512-UPDYRzGkygYFa8QUpEiumWrvZm4u4RKVzgiBt9C4RmHORqkkZzL9LXhaZJp2SmIz1ND5gx6KR5ze8ZnAdwxxoQ==} resolution: {integrity: sha512-UPDYRzGkygYFa8QUpEiumWrvZm4u4RKVzgiBt9C4RmHORqkkZzL9LXhaZJp2SmIz1ND5gx6KR5ze8ZnAdwxxoQ==}
dependencies: dependencies:
@@ -3610,12 +3530,6 @@ packages:
'@storybook/global': 5.0.0 '@storybook/global': 5.0.0
dev: true dev: true
/@storybook/client-logger@7.6.12:
resolution: {integrity: sha512-hiRv6dXsOttMPqm9SxEuFoAtDe9rs7TUS8XcO5rmJ9BgfwBJsYlHzAxXkazxmvlyZtKL7gMx6m8OYbCdZgUqtA==}
dependencies:
'@storybook/global': 5.0.0
dev: true
/@storybook/client-logger@7.6.15: /@storybook/client-logger@7.6.15:
resolution: {integrity: sha512-n+K8IqnombqiQNnywVovS+lK61tvv/XSfgPt0cgvoF/hJZB0VDOMRjWsV+v9qQpj1TQEl1lLWeJwZMthTWupJA==} resolution: {integrity: sha512-n+K8IqnombqiQNnywVovS+lK61tvv/XSfgPt0cgvoF/hJZB0VDOMRjWsV+v9qQpj1TQEl1lLWeJwZMthTWupJA==}
dependencies: dependencies:
@@ -3643,29 +3557,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@storybook/components@7.6.12(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-PCijPqmlZd7qyTzNr+vD0Kf8sAI9vWJIaxbSjXwn/De3e63m4fsEcIf8FaUT8cMZ46AWZvaxaxX5km2u0UISJQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@radix-ui/react-select': 1.2.2(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-toolbar': 1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0)
'@storybook/client-logger': 7.6.12
'@storybook/csf': 0.1.2
'@storybook/global': 5.0.0
'@storybook/theming': 7.6.12(react-dom@18.2.0)(react@18.2.0)
'@storybook/types': 7.6.12
memoizerific: 1.11.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
use-resize-observer: 9.1.0(react-dom@18.2.0)(react@18.2.0)
util-deprecate: 1.0.2
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
dev: true
/@storybook/components@7.6.15(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): /@storybook/components@7.6.15(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-xD+maP7+C9HeZXi2vJ+uK9hXN4S4spP4uDj9pyZ9yViKb+ztEO6WpovUMT8WRQ0mMegWyLXkx3zqu43hZvXM1g==} resolution: {integrity: sha512-xD+maP7+C9HeZXi2vJ+uK9hXN4S4spP4uDj9pyZ9yViKb+ztEO6WpovUMT8WRQ0mMegWyLXkx3zqu43hZvXM1g==}
peerDependencies: peerDependencies:
@@ -3696,37 +3587,6 @@ packages:
'@storybook/preview-api': 7.6.15 '@storybook/preview-api': 7.6.15
dev: true dev: true
/@storybook/core-common@7.6.12:
resolution: {integrity: sha512-kM9YiBBMM2x5v/oylL7gdO1PS4oehgJC21MivS9p5QZ8uuXKtCQ6UQvI3rzaV+1ZzUA4n+I8MyaMrNIQk8KDbw==}
dependencies:
'@storybook/core-events': 7.6.12
'@storybook/node-logger': 7.6.12
'@storybook/types': 7.6.12
'@types/find-cache-dir': 3.2.1
'@types/node': 18.19.11
'@types/node-fetch': 2.6.11
'@types/pretty-hrtime': 1.0.3
chalk: 4.1.2
esbuild: 0.18.20
esbuild-register: 3.5.0(esbuild@0.18.20)
file-system-cache: 2.3.0
find-cache-dir: 3.3.2
find-up: 5.0.0
fs-extra: 11.2.0
glob: 10.3.10
handlebars: 4.7.8
lazy-universal-dotenv: 4.0.0
node-fetch: 2.7.0
picomatch: 2.3.1
pkg-dir: 5.0.0
pretty-hrtime: 1.0.3
resolve-from: 5.0.0
ts-dedent: 2.2.0
transitivePeerDependencies:
- encoding
- supports-color
dev: true
/@storybook/core-common@7.6.15: /@storybook/core-common@7.6.15:
resolution: {integrity: sha512-VGmcLJ5U1r1s8/YnLbKcyB4GnNL+/sZIPqwlcSKzDXO76HoVFv1kywf7PbASote7P3gdhLSxBdg95LH2bdIbmw==} resolution: {integrity: sha512-VGmcLJ5U1r1s8/YnLbKcyB4GnNL+/sZIPqwlcSKzDXO76HoVFv1kywf7PbASote7P3gdhLSxBdg95LH2bdIbmw==}
dependencies: dependencies:
@@ -3764,12 +3624,6 @@ packages:
ts-dedent: 2.2.0 ts-dedent: 2.2.0
dev: true dev: true
/@storybook/core-events@7.6.12:
resolution: {integrity: sha512-IO4cwk7bBCKH6lLnnIlHO9FwQXt/9CzLUAoZSY9msWsdPppCdKlw8ynJI5YarSNKDBUn8ArIfnRf0Mve0KQr9Q==}
dependencies:
ts-dedent: 2.2.0
dev: true
/@storybook/core-events@7.6.15: /@storybook/core-events@7.6.15:
resolution: {integrity: sha512-i4YnjGecbpGyrFe0340sPhQ9QjZZEBqvMy6kF4XWt6DYLHxZmsTj1HEdvxVl4Ej7V49Vw0Dm8MepJ1d4Y8MKrQ==} resolution: {integrity: sha512-i4YnjGecbpGyrFe0340sPhQ9QjZZEBqvMy6kF4XWt6DYLHxZmsTj1HEdvxVl4Ej7V49Vw0Dm8MepJ1d4Y8MKrQ==}
dependencies: dependencies:
@@ -3827,15 +3681,6 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/@storybook/csf-plugin@7.6.12:
resolution: {integrity: sha512-fe/84AyctJcrpH1F/tTBxKrbjv0ilmG3ZTwVcufEiAzupZuYjQ/0P+Pxs8m8VxiGJZZ1pWofFFDbYi+wERjamQ==}
dependencies:
'@storybook/csf-tools': 7.6.12
unplugin: 1.6.0
transitivePeerDependencies:
- supports-color
dev: true
/@storybook/csf-plugin@7.6.15: /@storybook/csf-plugin@7.6.15:
resolution: {integrity: sha512-5Pm2B8XKNdG3fHyItWKbWnXHSRDFSvetlML+sMWGWYIjwOsnvPqt+gAvLksWhv/uJgDujGxNcPEh+/Y5C8ZAjQ==} resolution: {integrity: sha512-5Pm2B8XKNdG3fHyItWKbWnXHSRDFSvetlML+sMWGWYIjwOsnvPqt+gAvLksWhv/uJgDujGxNcPEh+/Y5C8ZAjQ==}
dependencies: dependencies:
@@ -3845,22 +3690,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@storybook/csf-tools@7.6.12:
resolution: {integrity: sha512-MdhkYYxSW5I6Jpk34gTkAZsuj9sxe0xdyeUQpNa8CgJxG43F+ehZ6scW/IPjoSG9gCXBUJMekq26UrmbVfsLCQ==}
dependencies:
'@babel/generator': 7.23.6
'@babel/parser': 7.23.9
'@babel/traverse': 7.23.9
'@babel/types': 7.23.9
'@storybook/csf': 0.1.2
'@storybook/types': 7.6.12
fs-extra: 11.2.0
recast: 0.23.4
ts-dedent: 2.2.0
transitivePeerDependencies:
- supports-color
dev: true
/@storybook/csf-tools@7.6.15: /@storybook/csf-tools@7.6.15:
resolution: {integrity: sha512-8iKgg2cmbFTpVhRRJOqouhPcEh0c8ywabG4S8ICZvnJooSXUI9mD9p3tYCS7MYuSiHj0epa1Kkn9DtXJRo9o6g==} resolution: {integrity: sha512-8iKgg2cmbFTpVhRRJOqouhPcEh0c8ywabG4S8ICZvnJooSXUI9mD9p3tYCS7MYuSiHj0epa1Kkn9DtXJRo9o6g==}
dependencies: dependencies:
@@ -3893,21 +3722,6 @@ packages:
resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==} resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==}
dev: true dev: true
/@storybook/docs-tools@7.6.12:
resolution: {integrity: sha512-nY2lqEDTd/fR/D91ZLlIp+boSuJtkb8DqHW7pECy61rJqzGq4QpepRaWjQDKnGTgPItrsPfTPOu6iXvXNK07Ow==}
dependencies:
'@storybook/core-common': 7.6.12
'@storybook/preview-api': 7.6.12
'@storybook/types': 7.6.12
'@types/doctrine': 0.0.3
assert: 2.1.0
doctrine: 3.0.0
lodash: 4.17.21
transitivePeerDependencies:
- encoding
- supports-color
dev: true
/@storybook/docs-tools@7.6.15: /@storybook/docs-tools@7.6.15:
resolution: {integrity: sha512-npZEaI9Wpn9uJcRXFElqyiRw8bSxt95mLywPiEEGMT2kE5FfXM8d5Uj5O64kzoXdRI9IhRPEEZZidOtA/UInfQ==} resolution: {integrity: sha512-npZEaI9Wpn9uJcRXFElqyiRw8bSxt95mLywPiEEGMT2kE5FfXM8d5Uj5O64kzoXdRI9IhRPEEZZidOtA/UInfQ==}
dependencies: dependencies:
@@ -3961,28 +3775,6 @@ packages:
- react-dom - react-dom
dev: true dev: true
/@storybook/manager-api@7.6.12(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-XA5KQpY44d6mlqt0AlesZ7fsPpm1PCpoV+nRGFBR0YtF6RdPFvrPyHhlGgLkJC4xSyb2YJmLKn8cERSluAcEgQ==}
dependencies:
'@storybook/channels': 7.6.12
'@storybook/client-logger': 7.6.12
'@storybook/core-events': 7.6.12
'@storybook/csf': 0.1.2
'@storybook/global': 5.0.0
'@storybook/router': 7.6.12
'@storybook/theming': 7.6.12(react-dom@18.2.0)(react@18.2.0)
'@storybook/types': 7.6.12
dequal: 2.0.3
lodash: 4.17.21
memoizerific: 1.11.3
store2: 2.14.2
telejson: 7.2.0
ts-dedent: 2.2.0
transitivePeerDependencies:
- react
- react-dom
dev: true
/@storybook/manager-api@7.6.15(react-dom@18.2.0)(react@18.2.0): /@storybook/manager-api@7.6.15(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-cPBsXcnJiaO3QyaEum2JgdihYea3cI03FeV35JdrBYLIelT4oqbYFnzjznsFg9+Ia9iAbz7aOBNyyRsWnC/UKw==} resolution: {integrity: sha512-cPBsXcnJiaO3QyaEum2JgdihYea3cI03FeV35JdrBYLIelT4oqbYFnzjznsFg9+Ia9iAbz7aOBNyyRsWnC/UKw==}
dependencies: dependencies:
@@ -4013,18 +3805,10 @@ packages:
resolution: {integrity: sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw==} resolution: {integrity: sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw==}
dev: true dev: true
/@storybook/node-logger@7.6.12:
resolution: {integrity: sha512-iS44/EjfF6hLecKzICmcpQoB9bmVi4tXx5gVXnbI5ZyziBibRQcg/U191Njl7wY2ScN/RCQOr8lh5k57rI3Prg==}
dev: true
/@storybook/node-logger@7.6.15: /@storybook/node-logger@7.6.15:
resolution: {integrity: sha512-C+sCvRjR+5uVU3VTrfyv7/RlPBxesAjIucUAK0keGyIZ7sFQYCPdkm4m/C4s+TcubgAzVvuoUHlRrSppdA7WzQ==} resolution: {integrity: sha512-C+sCvRjR+5uVU3VTrfyv7/RlPBxesAjIucUAK0keGyIZ7sFQYCPdkm4m/C4s+TcubgAzVvuoUHlRrSppdA7WzQ==}
dev: true dev: true
/@storybook/postinstall@7.6.12:
resolution: {integrity: sha512-uR0mDPxLzPaouCNrLp8vID8lATVTOtG7HB6lfjjzMdE3sN6MLmK9n2z2nXjb5DRRxOFWMeE1/4Age1/Ml2tnmA==}
dev: true
/@storybook/postinstall@7.6.15: /@storybook/postinstall@7.6.15:
resolution: {integrity: sha512-DXQQ4kjAbQ7BSd9M4lDI/12vEEciYMP8uYFDlrPFjwD9LezsxtRiORkazjNRRX4730faO5zZsnWhXxCVkxck0g==} resolution: {integrity: sha512-DXQQ4kjAbQ7BSd9M4lDI/12vEEciYMP8uYFDlrPFjwD9LezsxtRiORkazjNRRX4730faO5zZsnWhXxCVkxck0g==}
dev: true dev: true
@@ -4048,25 +3832,6 @@ packages:
util-deprecate: 1.0.2 util-deprecate: 1.0.2
dev: true dev: true
/@storybook/preview-api@7.6.12:
resolution: {integrity: sha512-uSzeMSLnCRROjiofJP0F0niLWL+sboQ5ktHW6BAYoPwprumXduPxKBUVEZNxMbVYoAz9v/kEZmaLauh8LRP2Hg==}
dependencies:
'@storybook/channels': 7.6.12
'@storybook/client-logger': 7.6.12
'@storybook/core-events': 7.6.12
'@storybook/csf': 0.1.2
'@storybook/global': 5.0.0
'@storybook/types': 7.6.12
'@types/qs': 6.9.11
dequal: 2.0.3
lodash: 4.17.21
memoizerific: 1.11.3
qs: 6.11.2
synchronous-promise: 2.0.17
ts-dedent: 2.2.0
util-deprecate: 1.0.2
dev: true
/@storybook/preview-api@7.6.15: /@storybook/preview-api@7.6.15:
resolution: {integrity: sha512-2KN9vlizF6sFlYsJEGnFqcQaJXs4TTdawC1VazVdtaMSHANDxxDu8F1cP+u7lpPH3DkNZUmTGQDBYfYY9xR0eQ==} resolution: {integrity: sha512-2KN9vlizF6sFlYsJEGnFqcQaJXs4TTdawC1VazVdtaMSHANDxxDu8F1cP+u7lpPH3DkNZUmTGQDBYfYY9xR0eQ==}
dependencies: dependencies:
@@ -4090,16 +3855,6 @@ packages:
resolution: {integrity: sha512-q8d9v0+Bo/DHLV68OyV3Klep4knf2GAbrlHhLW1X4jlPccuEDUojIfqfK7m48ayeIxJzO48fcO0JdKM1XABx7g==} resolution: {integrity: sha512-q8d9v0+Bo/DHLV68OyV3Klep4knf2GAbrlHhLW1X4jlPccuEDUojIfqfK7m48ayeIxJzO48fcO0JdKM1XABx7g==}
dev: true dev: true
/@storybook/react-dom-shim@7.6.12(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-P8eu/s/RQlc/7Yvr260lqNa6rttxIYiPUuHQBu9oCacwkpB3Xep2R/PUY2CifRHqlDhaOINO/Z79oGZl4EBQRQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: true
/@storybook/react-dom-shim@7.6.15(react-dom@18.2.0)(react@18.2.0): /@storybook/react-dom-shim@7.6.15(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-2+X0HIxIyvjfSKVyGGjSJJLEFJ2ox7Rr8FjlMiRo5QfoOJhohZuWH7p4Lw7JMwm5PotnjrwlfsZI3cCilYJeYA==} resolution: {integrity: sha512-2+X0HIxIyvjfSKVyGGjSJJLEFJ2ox7Rr8FjlMiRo5QfoOJhohZuWH7p4Lw7JMwm5PotnjrwlfsZI3cCilYJeYA==}
peerDependencies: peerDependencies:
@@ -4185,14 +3940,6 @@ packages:
qs: 6.11.2 qs: 6.11.2
dev: true dev: true
/@storybook/router@7.6.12:
resolution: {integrity: sha512-1fqscJbePFJXhapqiv7fAIIqAvouSsdPnqWjJGJrUMR6JBtRYMcrb3MnDeqi9OYnU73r65BrQBPtSzWM8nP0LQ==}
dependencies:
'@storybook/client-logger': 7.6.12
memoizerific: 1.11.3
qs: 6.11.2
dev: true
/@storybook/router@7.6.15: /@storybook/router@7.6.15:
resolution: {integrity: sha512-5yhXXoVZ1iKUgeZoO8PGqBclrLgoJisxIYVK/Y1iJMXZ2ZvwUiTswLALT6lu97tSrcoBVxmqSghg0+U0YEU4Fg==} resolution: {integrity: sha512-5yhXXoVZ1iKUgeZoO8PGqBclrLgoJisxIYVK/Y1iJMXZ2ZvwUiTswLALT6lu97tSrcoBVxmqSghg0+U0YEU4Fg==}
dependencies: dependencies:
@@ -4254,20 +4001,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: true dev: true
/@storybook/theming@7.6.12(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-P4zoMKlSYbNrWJjQROuz+DZSDEpdf3TUvk203EqBRdElqw2EMHcqZ8+0HGPFfVHpqEj05+B9Mr6R/Z/BURj0lw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
'@storybook/client-logger': 7.6.12
'@storybook/global': 5.0.0
memoizerific: 1.11.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: true
/@storybook/theming@7.6.15(react-dom@18.2.0)(react@18.2.0): /@storybook/theming@7.6.15(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-9PpsHAbUf6o0w33/P3mnb7QheTmfGlTYCismj5HMM1O2/zY0kQK9XcG9W+Cyvu56D/lFC19fz9YHQY8W4AbfnQ==} resolution: {integrity: sha512-9PpsHAbUf6o0w33/P3mnb7QheTmfGlTYCismj5HMM1O2/zY0kQK9XcG9W+Cyvu56D/lFC19fz9YHQY8W4AbfnQ==}
peerDependencies: peerDependencies:
@@ -4291,15 +4024,6 @@ packages:
file-system-cache: 2.3.0 file-system-cache: 2.3.0
dev: true dev: true
/@storybook/types@7.6.12:
resolution: {integrity: sha512-Wsbd+NS10/2yMHQ/26rXHflXam0hm2qufTFiHOX6VXZWxij3slRU88Fnwzp+1QSyjXb0qkEr8dOx7aG00+ItVw==}
dependencies:
'@storybook/channels': 7.6.12
'@types/babel__core': 7.20.5
'@types/express': 4.17.21
file-system-cache: 2.3.0
dev: true
/@storybook/types@7.6.15: /@storybook/types@7.6.15:
resolution: {integrity: sha512-tLH0lK6SXECSfMpKin9bge+7XiHZII17n6jc9ZI1TfSBZJyq3M6VzWh2r1C2lC97FlkcKXjIwM3n8h1xNjnI+A==} resolution: {integrity: sha512-tLH0lK6SXECSfMpKin9bge+7XiHZII17n6jc9ZI1TfSBZJyq3M6VzWh2r1C2lC97FlkcKXjIwM3n8h1xNjnI+A==}
dependencies: dependencies:
@@ -6111,6 +5835,11 @@ packages:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true dev: true
/base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
dev: false
/base64-js@1.5.1: /base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: true dev: true
@@ -6794,6 +6523,12 @@ packages:
postcss: 8.4.35 postcss: 8.4.35
dev: true dev: true
/css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
dependencies:
utrie: 1.0.2
dev: false
/css-select@5.1.0: /css-select@5.1.0:
resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
dependencies: dependencies:
@@ -8886,6 +8621,14 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
dev: false
/htmlparser2@8.0.2: /htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
dependencies: dependencies:
@@ -12609,6 +12352,12 @@ packages:
minimatch: 3.1.2 minimatch: 3.1.2
dev: true dev: true
/text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
dependencies:
utrie: 1.0.2
dev: false
/text-table@0.2.0: /text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true dev: true
@@ -13147,6 +12896,12 @@ packages:
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
dev: true dev: true
/utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
dependencies:
base64-arraybuffer: 1.0.2
dev: false
/uuid@9.0.1: /uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true hasBin: true

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React, {useRef} from 'react';
import html2canvas from 'html2canvas';
import { DAY_MAP } from 'src/shared/types/CourseMeeting'; import { DAY_MAP } from 'src/shared/types/CourseMeeting';
import CalendarCell from '../CalendarGridCell/CalendarGridCell'; import CalendarCell from '../CalendarGridCell/CalendarGridCell';
import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule'; import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule';
@@ -35,11 +36,25 @@ interface Props {
* @param props * @param props
*/ */
function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Props> ): JSX.Element { function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Props> ): JSX.Element {
const calendarRef = useRef(null); // Create a ref for the calendar grid
const saveAsPNG = () => {
if (calendarRef.current) {
html2canvas(calendarRef.current).then((canvas) => {
// Create an a element to trigger download
const a = document.createElement('a');
a.href = canvas.toDataURL('image/png');
a.download = 'calendar.png';
a.click();
});
}
};
return ( return (
<div className={styles.calendar}> <div className={styles.calendar}>
<div className={styles.dayLabelContainer} /> <div className={styles.dayLabelContainer} />
{/* Displaying the rest of the calendar */} {/* Displaying the rest of the calendar */}
<div className={styles.timeAndGrid}> <div ref={calendarRef} className={styles.timeAndGrid}>
{/* <div className={styles.timeColumn}> {/* <div className={styles.timeColumn}>
<div className={styles.timeBlock}></div> <div className={styles.timeBlock}></div>
{hoursOfDay.map((hour) => ( {hoursOfDay.map((hour) => (
@@ -58,7 +73,9 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Pr
{day} {day}
</div> </div>
))} ))}
{grid.map(row => row)} {grid.map((row, index) => (
<React.Fragment key={index}>{row}</React.Fragment>
))}
</div> </div>
</div> </div>
{/* {courseCells.map((Block: typeof CalendarCourseCell) => ( {/* {courseCells.map((Block: typeof CalendarCourseCell) => (
@@ -79,7 +96,7 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Pr
Save as .CAL Save as .CAL
</button> </button>
<div className={styles.divider}></div> {/* Second divider */} <div className={styles.divider}></div> {/* Second divider */}
<button className={styles.calendarButton}> <button onClick={saveAsPNG} className={styles.calendarButton}>
<img src={pngIcon} className={styles.buttonIcon} alt="PNG" /> <img src={pngIcon} className={styles.buttonIcon} alt="PNG" />
Save as .PNG Save as .PNG
</button> </button>