Compare commits

..

No commits in common. 'main' and 'compressed-week-view' have entirely different histories.

@ -1,46 +0,0 @@
kind: pipeline
name: default
steps:
- name: deploy
image: node:latest
volumes:
- name: host-orario-dist
path: /drone/src/dist
environment:
BASE_URL:
from_secret: base_url
commands:
- pwd
- npm install
- npm run build
volumes:
- name: host-orario-dist
host:
path: /var/www/orario
trigger:
branch:
- main
event:
- push
---
kind: pipeline
type: exec
name: caddy-permissions
depends_on:
- default
steps:
- name: chown
commands:
- chown -R caddy:caddy /var/www/orario
trigger:
branch:
- main
event:
- push

2
.gitignore vendored

@ -6,5 +6,3 @@ node_modules/
.env .env
.env.production .env.production
.vscode/

@ -1,9 +0,0 @@
{
"printWidth": 90,
"singleQuote": true,
"quoteProps": "consistent",
"tabWidth": 4,
"semi": false,
"arrowParens": "avoid",
"proseWrap": "always"
}

@ -10,22 +10,6 @@ You need to have installed `node` and `npm` (or `pnpm`). To setup the project ju
To start the development server run `npm run dev`. To start the development server run `npm run dev`.
### Editor
È meglio se prima di fare commit il codice sia formattato con Prettier, nel caso di VSCode c'è un'estensione omonima e le opzioni da aggiungere al proprio JSON di impostazioni sono
```json
...
"prettier.printWidth": 100,
"prettier.singleQuote": true,
"prettier.quoteProps": "consistent",
"prettier.tabWidth": 4,
"prettier.semi": false,
"prettier.arrowParens": "avoid",
"editor.formatOnSave": true,
...
```
### Production ### Production
To build the ViteJS project run `npm run build`, for deployment a `.env` files can be used to set the `BASE_URL` variable. To build the ViteJS project run `npm run build`, for deployment a `.env` files can be used to set the `BASE_URL` variable.

@ -1,28 +1,24 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Orario DM Unipi</title> <title>Orario DM Unipi</title>
<meta property="og:title" content="PHC - Orario" /> <meta property="og:title" content="PHC - Orario" />
<meta property="og:description" content="Sito per visualizzare l'orario delle lezioni per i vari anni di corso" /> <meta property="og:description" content="Sito per visualizzare l'orario delle lezioni per i vari anni di corso" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://lab.phc.dm.unipi.it/orario" /> <meta property="og:url" content="https://lab.phc.dm.unipi.it/orario" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,1,0" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,1,0" />
<link rel="icon" type="image/png" href="icon.png" /> <link rel="stylesheet" href="/src/styles/main.scss">
</head>
<link rel="stylesheet" href="/src/styles/main.scss" /> <body>
<script type="module" src="/src/main.jsx"></script>
<script data-goatcounter="https://analytics.phc.dm.unipi.it/count" async src="//analytics.phc.dm.unipi.it/count.js"></script> </body>
</head>
<body>
<script type="module" src="/src/main.jsx"></script>
</body>
</html> </html>

@ -1,40 +1,30 @@
lockfileVersion: '6.0' lockfileVersion: 5.4
settings: specifiers:
autoInstallPeers: true '@babel/core': ^7.18.13
excludeLinksFromLockfile: false '@preact/preset-vite': ^2.3.0
date-fns: ^2.29.2
lodash: ^4.17.21
lodash-es: ^4.17.21
preact: ^10.10.6
sass: ^1.54.8
vite: ^3.0.9
dependencies: dependencies:
date-fns: date-fns: 2.29.2
specifier: ^2.29.2 lodash: 4.17.21
version: 2.29.2 lodash-es: 4.17.21
lodash: preact: 10.10.6
specifier: ^4.17.21 sass: 1.54.8
version: 4.17.21 vite: 3.0.9_sass@1.54.8
lodash-es:
specifier: ^4.17.21
version: 4.17.21
preact:
specifier: ^10.10.6
version: 10.10.6
sass:
specifier: ^1.54.8
version: 1.54.8
vite:
specifier: ^3.0.9
version: 3.0.9(sass@1.54.8)
devDependencies: devDependencies:
'@babel/core': '@babel/core': 7.18.13
specifier: ^7.18.13 '@preact/preset-vite': 2.3.0_wibdudaz52xr4lhcmpvnys3x3u
version: 7.18.13
'@preact/preset-vite':
specifier: ^2.3.0
version: 2.3.0(@babel/core@7.18.13)(preact@10.10.6)(vite@3.0.9)
packages: packages:
/@ampproject/remapping@2.2.0: /@ampproject/remapping/2.2.0:
resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
@ -42,26 +32,26 @@ packages:
'@jridgewell/trace-mapping': 0.3.15 '@jridgewell/trace-mapping': 0.3.15
dev: true dev: true
/@babel/code-frame@7.18.6: /@babel/code-frame/7.18.6:
resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
'@babel/highlight': 7.18.6 '@babel/highlight': 7.18.6
dev: true dev: true
/@babel/compat-data@7.18.13: /@babel/compat-data/7.18.13:
resolution: {integrity: sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==} resolution: {integrity: sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dev: true dev: true
/@babel/core@7.18.13: /@babel/core/7.18.13:
resolution: {integrity: sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==} resolution: {integrity: sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
'@ampproject/remapping': 2.2.0 '@ampproject/remapping': 2.2.0
'@babel/code-frame': 7.18.6 '@babel/code-frame': 7.18.6
'@babel/generator': 7.18.13 '@babel/generator': 7.18.13
'@babel/helper-compilation-targets': 7.18.9(@babel/core@7.18.13) '@babel/helper-compilation-targets': 7.18.9_@babel+core@7.18.13
'@babel/helper-module-transforms': 7.18.9 '@babel/helper-module-transforms': 7.18.9
'@babel/helpers': 7.18.9 '@babel/helpers': 7.18.9
'@babel/parser': 7.18.13 '@babel/parser': 7.18.13
@ -77,7 +67,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@babel/generator@7.18.13: /@babel/generator/7.18.13:
resolution: {integrity: sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==} resolution: {integrity: sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
@ -86,14 +76,14 @@ packages:
jsesc: 2.5.2 jsesc: 2.5.2
dev: true dev: true
/@babel/helper-annotate-as-pure@7.18.6: /@babel/helper-annotate-as-pure/7.18.6:
resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
'@babel/types': 7.18.13 '@babel/types': 7.18.13
dev: true dev: true
/@babel/helper-compilation-targets@7.18.9(@babel/core@7.18.13): /@babel/helper-compilation-targets/7.18.9_@babel+core@7.18.13:
resolution: {integrity: sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==} resolution: {integrity: sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
peerDependencies: peerDependencies:
@ -106,12 +96,12 @@ packages:
semver: 6.3.0 semver: 6.3.0
dev: true dev: true
/@babel/helper-environment-visitor@7.18.9: /@babel/helper-environment-visitor/7.18.9:
resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dev: true dev: true
/@babel/helper-function-name@7.18.9: /@babel/helper-function-name/7.18.9:
resolution: {integrity: sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==} resolution: {integrity: sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
@ -119,21 +109,21 @@ packages:
'@babel/types': 7.18.13 '@babel/types': 7.18.13
dev: true dev: true
/@babel/helper-hoist-variables@7.18.6: /@babel/helper-hoist-variables/7.18.6:
resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
'@babel/types': 7.18.13 '@babel/types': 7.18.13
dev: true dev: true
/@babel/helper-module-imports@7.18.6: /@babel/helper-module-imports/7.18.6:
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
'@babel/types': 7.18.13 '@babel/types': 7.18.13
dev: true dev: true
/@babel/helper-module-transforms@7.18.9: /@babel/helper-module-transforms/7.18.9:
resolution: {integrity: sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==} resolution: {integrity: sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
@ -149,41 +139,41 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@babel/helper-plugin-utils@7.18.9: /@babel/helper-plugin-utils/7.18.9:
resolution: {integrity: sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==} resolution: {integrity: sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dev: true dev: true
/@babel/helper-simple-access@7.18.6: /@babel/helper-simple-access/7.18.6:
resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==} resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
'@babel/types': 7.18.13 '@babel/types': 7.18.13
dev: true dev: true
/@babel/helper-split-export-declaration@7.18.6: /@babel/helper-split-export-declaration/7.18.6:
resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
'@babel/types': 7.18.13 '@babel/types': 7.18.13
dev: true dev: true
/@babel/helper-string-parser@7.18.10: /@babel/helper-string-parser/7.18.10:
resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==} resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dev: true dev: true
/@babel/helper-validator-identifier@7.18.6: /@babel/helper-validator-identifier/7.18.6:
resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==} resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dev: true dev: true
/@babel/helper-validator-option@7.18.6: /@babel/helper-validator-option/7.18.6:
resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dev: true dev: true
/@babel/helpers@7.18.9: /@babel/helpers/7.18.9:
resolution: {integrity: sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==} resolution: {integrity: sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
@ -194,7 +184,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@babel/highlight@7.18.6: /@babel/highlight/7.18.6:
resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
@ -203,7 +193,7 @@ packages:
js-tokens: 4.0.0 js-tokens: 4.0.0
dev: true dev: true
/@babel/parser@7.18.13: /@babel/parser/7.18.13:
resolution: {integrity: sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==} resolution: {integrity: sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
@ -211,7 +201,7 @@ packages:
'@babel/types': 7.18.13 '@babel/types': 7.18.13
dev: true dev: true
/@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.18.13): /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.18.13:
resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
peerDependencies: peerDependencies:
@ -221,17 +211,17 @@ packages:
'@babel/helper-plugin-utils': 7.18.9 '@babel/helper-plugin-utils': 7.18.9
dev: true dev: true
/@babel/plugin-transform-react-jsx-development@7.18.6(@babel/core@7.18.13): /@babel/plugin-transform-react-jsx-development/7.18.6_@babel+core@7.18.13:
resolution: {integrity: sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==} resolution: {integrity: sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
'@babel/core': 7.18.13 '@babel/core': 7.18.13
'@babel/plugin-transform-react-jsx': 7.18.10(@babel/core@7.18.13) '@babel/plugin-transform-react-jsx': 7.18.10_@babel+core@7.18.13
dev: true dev: true
/@babel/plugin-transform-react-jsx@7.18.10(@babel/core@7.18.13): /@babel/plugin-transform-react-jsx/7.18.10_@babel+core@7.18.13:
resolution: {integrity: sha512-gCy7Iikrpu3IZjYZolFE4M1Sm+nrh1/6za2Ewj77Z+XirT4TsbJcvOFOyF+fRPwU6AKKK136CZxx6L8AbSFG6A==} resolution: {integrity: sha512-gCy7Iikrpu3IZjYZolFE4M1Sm+nrh1/6za2Ewj77Z+XirT4TsbJcvOFOyF+fRPwU6AKKK136CZxx6L8AbSFG6A==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
peerDependencies: peerDependencies:
@ -241,11 +231,11 @@ packages:
'@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-module-imports': 7.18.6 '@babel/helper-module-imports': 7.18.6
'@babel/helper-plugin-utils': 7.18.9 '@babel/helper-plugin-utils': 7.18.9
'@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.18.13) '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.18.13
'@babel/types': 7.18.13 '@babel/types': 7.18.13
dev: true dev: true
/@babel/template@7.18.10: /@babel/template/7.18.10:
resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
@ -254,7 +244,7 @@ packages:
'@babel/types': 7.18.13 '@babel/types': 7.18.13
dev: true dev: true
/@babel/traverse@7.18.13: /@babel/traverse/7.18.13:
resolution: {integrity: sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==} resolution: {integrity: sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
@ -272,7 +262,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@babel/types@7.18.13: /@babel/types/7.18.13:
resolution: {integrity: sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==} resolution: {integrity: sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
@ -281,7 +271,7 @@ packages:
to-fast-properties: 2.0.0 to-fast-properties: 2.0.0
dev: true dev: true
/@esbuild/linux-loong64@0.14.54: /@esbuild/linux-loong64/0.14.54:
resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [loong64] cpu: [loong64]
@ -289,7 +279,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@jridgewell/gen-mapping@0.1.1: /@jridgewell/gen-mapping/0.1.1:
resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
@ -297,7 +287,7 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14 '@jridgewell/sourcemap-codec': 1.4.14
dev: true dev: true
/@jridgewell/gen-mapping@0.3.2: /@jridgewell/gen-mapping/0.3.2:
resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
@ -306,53 +296,53 @@ packages:
'@jridgewell/trace-mapping': 0.3.15 '@jridgewell/trace-mapping': 0.3.15
dev: true dev: true
/@jridgewell/resolve-uri@3.1.0: /@jridgewell/resolve-uri/3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dev: true dev: true
/@jridgewell/set-array@1.1.2: /@jridgewell/set-array/1.1.2:
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dev: true dev: true
/@jridgewell/sourcemap-codec@1.4.14: /@jridgewell/sourcemap-codec/1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
dev: true dev: true
/@jridgewell/trace-mapping@0.3.15: /@jridgewell/trace-mapping/0.3.15:
resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==} resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==}
dependencies: dependencies:
'@jridgewell/resolve-uri': 3.1.0 '@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14 '@jridgewell/sourcemap-codec': 1.4.14
dev: true dev: true
/@preact/preset-vite@2.3.0(@babel/core@7.18.13)(preact@10.10.6)(vite@3.0.9): /@preact/preset-vite/2.3.0_wibdudaz52xr4lhcmpvnys3x3u:
resolution: {integrity: sha512-0kOuz7wdrQLqrPlyI/Ypw9IWDF2++GGcOHMRBYO5T2w2+dheelaBH+XrIN/okqdsGIflzFIFNyIGubo5BC8wbQ==} resolution: {integrity: sha512-0kOuz7wdrQLqrPlyI/Ypw9IWDF2++GGcOHMRBYO5T2w2+dheelaBH+XrIN/okqdsGIflzFIFNyIGubo5BC8wbQ==}
peerDependencies: peerDependencies:
'@babel/core': 7.x '@babel/core': 7.x
vite: 2.x || 3.x vite: 2.x || 3.x
dependencies: dependencies:
'@babel/core': 7.18.13 '@babel/core': 7.18.13
'@babel/plugin-transform-react-jsx': 7.18.10(@babel/core@7.18.13) '@babel/plugin-transform-react-jsx': 7.18.10_@babel+core@7.18.13
'@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.18.13) '@babel/plugin-transform-react-jsx-development': 7.18.6_@babel+core@7.18.13
'@prefresh/vite': 2.2.8(preact@10.10.6)(vite@3.0.9) '@prefresh/vite': 2.2.8_preact@10.10.6+vite@3.0.9
'@rollup/pluginutils': 4.2.1 '@rollup/pluginutils': 4.2.1
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.18.13) babel-plugin-transform-hook-names: 1.0.2_@babel+core@7.18.13
debug: 4.3.4 debug: 4.3.4
kolorist: 1.5.1 kolorist: 1.5.1
resolve: 1.22.1 resolve: 1.22.1
vite: 3.0.9(sass@1.54.8) vite: 3.0.9_sass@1.54.8
transitivePeerDependencies: transitivePeerDependencies:
- preact - preact
- supports-color - supports-color
dev: true dev: true
/@prefresh/babel-plugin@0.4.3: /@prefresh/babel-plugin/0.4.3:
resolution: {integrity: sha512-fYAWbU1WDSLn108kKY4eDaaeUcnszFqXjgaGKYXNZ5NLulpRTpsrY+Sbfo9q8LDpWrBpqIgzjrwNnvglWI1xNQ==} resolution: {integrity: sha512-fYAWbU1WDSLn108kKY4eDaaeUcnszFqXjgaGKYXNZ5NLulpRTpsrY+Sbfo9q8LDpWrBpqIgzjrwNnvglWI1xNQ==}
dev: true dev: true
/@prefresh/core@1.3.4(preact@10.10.6): /@prefresh/core/1.3.4_preact@10.10.6:
resolution: {integrity: sha512-s7iNsnyJ3lZEUrYIgmVIB/hKtp4U6mdD91a31Zg7Q8M49O0x2KThrbrMQYraoDDrs4STdFB8Zv6bceUguOoX1A==} resolution: {integrity: sha512-s7iNsnyJ3lZEUrYIgmVIB/hKtp4U6mdD91a31Zg7Q8M49O0x2KThrbrMQYraoDDrs4STdFB8Zv6bceUguOoX1A==}
peerDependencies: peerDependencies:
preact: ^10.0.0 preact: ^10.0.0
@ -360,11 +350,11 @@ packages:
preact: 10.10.6 preact: 10.10.6
dev: true dev: true
/@prefresh/utils@1.1.3: /@prefresh/utils/1.1.3:
resolution: {integrity: sha512-Mb9abhJTOV4yCfkXrMrcgFiFT7MfNOw8sDa+XyZBdq/Ai2p4Zyxqsb3EgHLOEdHpMj6J9aiZ54W8H6FTam1u+A==} resolution: {integrity: sha512-Mb9abhJTOV4yCfkXrMrcgFiFT7MfNOw8sDa+XyZBdq/Ai2p4Zyxqsb3EgHLOEdHpMj6J9aiZ54W8H6FTam1u+A==}
dev: true dev: true
/@prefresh/vite@2.2.8(preact@10.10.6)(vite@3.0.9): /@prefresh/vite/2.2.8_preact@10.10.6+vite@3.0.9:
resolution: {integrity: sha512-yGGa+PKPYPTzMlxgQ8aBgxw9K69I8X4iQ0E6KOcIvls96WKqKLLOYZW9SUgCve446jpUXvc9udviPBZjCeZIIQ==} resolution: {integrity: sha512-yGGa+PKPYPTzMlxgQ8aBgxw9K69I8X4iQ0E6KOcIvls96WKqKLLOYZW9SUgCve446jpUXvc9udviPBZjCeZIIQ==}
peerDependencies: peerDependencies:
preact: ^10.4.0 preact: ^10.4.0
@ -372,16 +362,16 @@ packages:
dependencies: dependencies:
'@babel/core': 7.18.13 '@babel/core': 7.18.13
'@prefresh/babel-plugin': 0.4.3 '@prefresh/babel-plugin': 0.4.3
'@prefresh/core': 1.3.4(preact@10.10.6) '@prefresh/core': 1.3.4_preact@10.10.6
'@prefresh/utils': 1.1.3 '@prefresh/utils': 1.1.3
'@rollup/pluginutils': 4.2.1 '@rollup/pluginutils': 4.2.1
preact: 10.10.6 preact: 10.10.6
vite: 3.0.9(sass@1.54.8) vite: 3.0.9_sass@1.54.8
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@rollup/pluginutils@4.2.1: /@rollup/pluginutils/4.2.1:
resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
engines: {node: '>= 8.0.0'} engines: {node: '>= 8.0.0'}
dependencies: dependencies:
@ -389,21 +379,21 @@ packages:
picomatch: 2.3.1 picomatch: 2.3.1
dev: true dev: true
/ansi-styles@3.2.1: /ansi-styles/3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'} engines: {node: '>=4'}
dependencies: dependencies:
color-convert: 1.9.3 color-convert: 1.9.3
dev: true dev: true
/anymatch@3.1.2: /anymatch/3.1.2:
resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
dependencies: dependencies:
normalize-path: 3.0.0 normalize-path: 3.0.0
picomatch: 2.3.1 picomatch: 2.3.1
/babel-plugin-transform-hook-names@1.0.2(@babel/core@7.18.13): /babel-plugin-transform-hook-names/1.0.2_@babel+core@7.18.13:
resolution: {integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==} resolution: {integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==}
peerDependencies: peerDependencies:
'@babel/core': ^7.12.10 '@babel/core': ^7.12.10
@ -411,17 +401,17 @@ packages:
'@babel/core': 7.18.13 '@babel/core': 7.18.13
dev: true dev: true
/binary-extensions@2.2.0: /binary-extensions/2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'} engines: {node: '>=8'}
/braces@3.0.2: /braces/3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
fill-range: 7.0.1 fill-range: 7.0.1
/browserslist@4.21.3: /browserslist/4.21.3:
resolution: {integrity: sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==} resolution: {integrity: sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true hasBin: true
@ -429,14 +419,14 @@ packages:
caniuse-lite: 1.0.30001388 caniuse-lite: 1.0.30001388
electron-to-chromium: 1.4.241 electron-to-chromium: 1.4.241
node-releases: 2.0.6 node-releases: 2.0.6
update-browserslist-db: 1.0.7(browserslist@4.21.3) update-browserslist-db: 1.0.7_browserslist@4.21.3
dev: true dev: true
/caniuse-lite@1.0.30001388: /caniuse-lite/1.0.30001388:
resolution: {integrity: sha512-znVbq4OUjqgLxMxoNX2ZeeLR0d7lcDiE5uJ4eUiWdml1J1EkxbnQq6opT9jb9SMfJxB0XA16/ziHwni4u1I3GQ==} resolution: {integrity: sha512-znVbq4OUjqgLxMxoNX2ZeeLR0d7lcDiE5uJ4eUiWdml1J1EkxbnQq6opT9jb9SMfJxB0XA16/ziHwni4u1I3GQ==}
dev: true dev: true
/chalk@2.4.2: /chalk/2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'} engines: {node: '>=4'}
dependencies: dependencies:
@ -445,7 +435,7 @@ packages:
supports-color: 5.5.0 supports-color: 5.5.0
dev: true dev: true
/chokidar@3.5.3: /chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
dependencies: dependencies:
@ -459,28 +449,28 @@ packages:
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2
/color-convert@1.9.3: /color-convert/1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies: dependencies:
color-name: 1.1.3 color-name: 1.1.3
dev: true dev: true
/color-name@1.1.3: /color-name/1.1.3:
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
dev: true dev: true
/convert-source-map@1.8.0: /convert-source-map/1.8.0:
resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}
dependencies: dependencies:
safe-buffer: 5.1.2 safe-buffer: 5.1.2
dev: true dev: true
/date-fns@2.29.2: /date-fns/2.29.2:
resolution: {integrity: sha512-0VNbwmWJDS/G3ySwFSJA3ayhbURMTJLtwM2DTxf9CWondCnh6DTNlO9JgRSq6ibf4eD0lfMJNBxUdEAHHix+bA==} resolution: {integrity: sha512-0VNbwmWJDS/G3ySwFSJA3ayhbURMTJLtwM2DTxf9CWondCnh6DTNlO9JgRSq6ibf4eD0lfMJNBxUdEAHHix+bA==}
engines: {node: '>=0.11'} engines: {node: '>=0.11'}
dev: false dev: false
/debug@4.3.4: /debug/4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'} engines: {node: '>=6.0'}
peerDependencies: peerDependencies:
@ -492,11 +482,11 @@ packages:
ms: 2.1.2 ms: 2.1.2
dev: true dev: true
/electron-to-chromium@1.4.241: /electron-to-chromium/1.4.241:
resolution: {integrity: sha512-e7Wsh4ilaioBZ5bMm6+F4V5c11dh56/5Jwz7Hl5Tu1J7cnB+Pqx5qIF2iC7HPpfyQMqGSvvLP5bBAIDd2gAtGw==} resolution: {integrity: sha512-e7Wsh4ilaioBZ5bMm6+F4V5c11dh56/5Jwz7Hl5Tu1J7cnB+Pqx5qIF2iC7HPpfyQMqGSvvLP5bBAIDd2gAtGw==}
dev: true dev: true
/esbuild-android-64@0.14.54: /esbuild-android-64/0.14.54:
resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
@ -504,7 +494,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-android-arm64@0.14.54: /esbuild-android-arm64/0.14.54:
resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm64] cpu: [arm64]
@ -512,7 +502,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-darwin-64@0.14.54: /esbuild-darwin-64/0.14.54:
resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
@ -520,7 +510,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-darwin-arm64@0.14.54: /esbuild-darwin-arm64/0.14.54:
resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm64] cpu: [arm64]
@ -528,7 +518,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-freebsd-64@0.14.54: /esbuild-freebsd-64/0.14.54:
resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
@ -536,7 +526,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-freebsd-arm64@0.14.54: /esbuild-freebsd-arm64/0.14.54:
resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm64] cpu: [arm64]
@ -544,7 +534,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-linux-32@0.14.54: /esbuild-linux-32/0.14.54:
resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [ia32] cpu: [ia32]
@ -552,7 +542,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-linux-64@0.14.54: /esbuild-linux-64/0.14.54:
resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
@ -560,23 +550,23 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-linux-arm64@0.14.54: /esbuild-linux-arm/0.14.54:
resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm64] cpu: [arm]
os: [linux] os: [linux]
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-linux-arm@0.14.54: /esbuild-linux-arm64/0.14.54:
resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm] cpu: [arm64]
os: [linux] os: [linux]
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-linux-mips64le@0.14.54: /esbuild-linux-mips64le/0.14.54:
resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [mips64el] cpu: [mips64el]
@ -584,7 +574,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-linux-ppc64le@0.14.54: /esbuild-linux-ppc64le/0.14.54:
resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [ppc64] cpu: [ppc64]
@ -592,7 +582,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-linux-riscv64@0.14.54: /esbuild-linux-riscv64/0.14.54:
resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [riscv64] cpu: [riscv64]
@ -600,7 +590,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-linux-s390x@0.14.54: /esbuild-linux-s390x/0.14.54:
resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [s390x] cpu: [s390x]
@ -608,7 +598,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-netbsd-64@0.14.54: /esbuild-netbsd-64/0.14.54:
resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
@ -616,7 +606,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-openbsd-64@0.14.54: /esbuild-openbsd-64/0.14.54:
resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
@ -624,7 +614,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-sunos-64@0.14.54: /esbuild-sunos-64/0.14.54:
resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
@ -632,7 +622,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-windows-32@0.14.54: /esbuild-windows-32/0.14.54:
resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [ia32] cpu: [ia32]
@ -640,7 +630,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-windows-64@0.14.54: /esbuild-windows-64/0.14.54:
resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
@ -648,7 +638,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild-windows-arm64@0.14.54: /esbuild-windows-arm64/0.14.54:
resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm64] cpu: [arm64]
@ -656,7 +646,7 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/esbuild@0.14.54: /esbuild/0.14.54:
resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==}
engines: {node: '>=12'} engines: {node: '>=12'}
hasBin: true hasBin: true
@ -684,147 +674,147 @@ packages:
esbuild-windows-64: 0.14.54 esbuild-windows-64: 0.14.54
esbuild-windows-arm64: 0.14.54 esbuild-windows-arm64: 0.14.54
/escalade@3.1.1: /escalade/3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/escape-string-regexp@1.0.5: /escape-string-regexp/1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
dev: true dev: true
/estree-walker@2.0.2: /estree-walker/2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true dev: true
/fill-range@7.0.1: /fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
/fsevents@2.3.2: /fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin] os: [darwin]
requiresBuild: true requiresBuild: true
optional: true optional: true
/function-bind@1.1.1: /function-bind/1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
/gensync@1.0.0-beta.2: /gensync/1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dev: true dev: true
/glob-parent@5.1.2: /glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
dependencies: dependencies:
is-glob: 4.0.3 is-glob: 4.0.3
/globals@11.12.0: /globals/11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'} engines: {node: '>=4'}
dev: true dev: true
/has-flag@3.0.0: /has-flag/3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'} engines: {node: '>=4'}
dev: true dev: true
/has@1.0.3: /has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
dependencies: dependencies:
function-bind: 1.1.1 function-bind: 1.1.1
/immutable@4.1.0: /immutable/4.1.0:
resolution: {integrity: sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==} resolution: {integrity: sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==}
/is-binary-path@2.1.0: /is-binary-path/2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
binary-extensions: 2.2.0 binary-extensions: 2.2.0
/is-core-module@2.10.0: /is-core-module/2.10.0:
resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==}
dependencies: dependencies:
has: 1.0.3 has: 1.0.3
/is-extglob@2.1.1: /is-extglob/2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
/is-glob@4.0.3: /is-glob/4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dependencies: dependencies:
is-extglob: 2.1.1 is-extglob: 2.1.1
/is-number@7.0.0: /is-number/7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'} engines: {node: '>=0.12.0'}
/js-tokens@4.0.0: /js-tokens/4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: true dev: true
/jsesc@2.5.2: /jsesc/2.5.2:
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
engines: {node: '>=4'} engines: {node: '>=4'}
hasBin: true hasBin: true
dev: true dev: true
/json5@2.2.1: /json5/2.2.1:
resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==}
engines: {node: '>=6'} engines: {node: '>=6'}
hasBin: true hasBin: true
dev: true dev: true
/kolorist@1.5.1: /kolorist/1.5.1:
resolution: {integrity: sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ==} resolution: {integrity: sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ==}
dev: true dev: true
/lodash-es@4.17.21: /lodash-es/4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
dev: false dev: false
/lodash@4.17.21: /lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: false dev: false
/ms@2.1.2: /ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true dev: true
/nanoid@3.3.4: /nanoid/3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
/node-releases@2.0.6: /node-releases/2.0.6:
resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
dev: true dev: true
/normalize-path@3.0.0: /normalize-path/3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
/path-parse@1.0.7: /path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
/picocolors@1.0.0: /picocolors/1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
/picomatch@2.3.1: /picomatch/2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
/postcss@8.4.16: /postcss/8.4.16:
resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==} resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
dependencies: dependencies:
@ -832,16 +822,16 @@ packages:
picocolors: 1.0.0 picocolors: 1.0.0
source-map-js: 1.0.2 source-map-js: 1.0.2
/preact@10.10.6: /preact/10.10.6:
resolution: {integrity: sha512-w0mCL5vICUAZrh1DuHEdOWBjxdO62lvcO++jbzr8UhhYcTbFkpegLH9XX+7MadjTl/y0feoqwQ/zAnzkc/EGog==} resolution: {integrity: sha512-w0mCL5vICUAZrh1DuHEdOWBjxdO62lvcO++jbzr8UhhYcTbFkpegLH9XX+7MadjTl/y0feoqwQ/zAnzkc/EGog==}
/readdirp@3.6.0: /readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'} engines: {node: '>=8.10.0'}
dependencies: dependencies:
picomatch: 2.3.1 picomatch: 2.3.1
/resolve@1.22.1: /resolve/1.22.1:
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
hasBin: true hasBin: true
dependencies: dependencies:
@ -849,18 +839,18 @@ packages:
path-parse: 1.0.7 path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0 supports-preserve-symlinks-flag: 1.0.0
/rollup@2.77.3: /rollup/2.77.3:
resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==} resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
hasBin: true hasBin: true
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2
/safe-buffer@5.1.2: /safe-buffer/5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: true dev: true
/sass@1.54.8: /sass/1.54.8:
resolution: {integrity: sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==} resolution: {integrity: sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
hasBin: true hasBin: true
@ -869,38 +859,38 @@ packages:
immutable: 4.1.0 immutable: 4.1.0
source-map-js: 1.0.2 source-map-js: 1.0.2
/semver@6.3.0: /semver/6.3.0:
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
hasBin: true hasBin: true
dev: true dev: true
/source-map-js@1.0.2: /source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
/supports-color@5.5.0: /supports-color/5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'} engines: {node: '>=4'}
dependencies: dependencies:
has-flag: 3.0.0 has-flag: 3.0.0
dev: true dev: true
/supports-preserve-symlinks-flag@1.0.0: /supports-preserve-symlinks-flag/1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
/to-fast-properties@2.0.0: /to-fast-properties/2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'} engines: {node: '>=4'}
dev: true dev: true
/to-regex-range@5.0.1: /to-regex-range/5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
/update-browserslist-db@1.0.7(browserslist@4.21.3): /update-browserslist-db/1.0.7_browserslist@4.21.3:
resolution: {integrity: sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==} resolution: {integrity: sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -911,7 +901,7 @@ packages:
picocolors: 1.0.0 picocolors: 1.0.0
dev: true dev: true
/vite@3.0.9(sass@1.54.8): /vite/3.0.9_sass@1.54.8:
resolution: {integrity: sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==} resolution: {integrity: sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true hasBin: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

@ -5,11 +5,7 @@ export const CompoundButton = ({ options, value, setValue }) => {
<div class="compound"> <div class="compound">
{options.map(option => ( {options.map(option => (
<button <button
class={withClasses([ class={withClasses(['radio', option.value === value && 'selected'])}
'radio',
option.value === value && 'selected',
option.icon && 'icon',
])}
onClick={() => setValue(option.value)} onClick={() => setValue(option.value)}
> >
{option.label} {option.label}

@ -0,0 +1,12 @@
import { Icon } from './Icon.jsx'
export const ToolOverlay = ({ visibility, toggleVisibility, onClose }) => (
<div class="overlay">
<button class="icon primary" onClick={toggleVisibility}>
<Icon name={visibility ? 'visibility_off' : 'visibility'} />
</button>
<button class="icon" onClick={onClose}>
<Icon name="close" />
</button>
</div>
)

@ -1,33 +0,0 @@
import { useRef } from 'preact/hooks'
import { Icon } from './Icon.jsx'
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
export const DatePicker = ({ date, setDate }) => {
const input = useRef()
const [year, month, day] = date.split('T')[0].split('-')
return (
<div
class="date-picker"
onClick={() =>
isSafari ? input.current.focus() : input.current.showPicker()
}
>
<input
ref={input}
type="date"
value={`${year}-${month}-${day}`}
onChange={e => setDate(new Date(e.target.value).toISOString())}
/>
<div class="date-picker-render">
<div class="date">
{day}/{month}/{year}
</div>
<div class="calendar">
<Icon name="calendar_month" />
</div>
</div>
</div>
)
}

@ -1,41 +1,29 @@
import { Courses } from './view/Courses.jsx' import { Course } from './view/Course.jsx'
import { Schedule } from './view/Schedule.jsx' import { Schedule } from './view/Schedule.jsx'
import { WorkWeek } from './view/WorkWeek.jsx'
import { WorkWeekGrid } from './view/WorkWeekGrid.jsx'
import { WorkWeekTranspose } from './view/WorkWeekTranspose.jsx'
export const MODE_COURSES = 'course' export const MODE_COURSE = 'course'
export const MODE_WORKWEEK = 'work-week' export const MODE_WORKWEEK = 'work-week'
export const MODE_SCHEDULE = 'schedule' export const MODE_SCHEDULE = 'schedule'
export const MODE_WORKWEEK_GRID = 'work-week-grid' export const MODE_WORKWEEK_GRID = 'work-week-grid'
export const MODE_WORKWEEK_TRANSPOSE = 'work-week-transpose' export const MODE_WORKWEEK_TRANSPOSE = 'work-week-transpose'
const viewModeMap = { const viewModeMap = {
[MODE_COURSES]: Courses, [MODE_COURSE]: Course,
[MODE_WORKWEEK]: WorkWeek,
[MODE_WORKWEEK_GRID]: WorkWeekGrid,
[MODE_WORKWEEK_TRANSPOSE]: WorkWeekTranspose,
[MODE_SCHEDULE]: Schedule, [MODE_SCHEDULE]: Schedule,
} }
// export const EventsView = ({ mode, ...viewProps }) => { export const EventsView = ({ mode, ...viewProps }) => {
// const Mode = viewModeMap[mode] const Mode = viewModeMap[mode]
//
// return (
// <div class="events-view">
// <Mode {...viewProps} />
// </div>
// )
// }
export const EventsView = ({ mode, source, ...viewProps }) => { return (
// const Mode = viewModeMap[mode] <div class="events-view">
<Mode {...viewProps} />
if (source === 'orario') { </div>
return ( )
<div class="events-view">
<Schedule source={source} {...viewProps} />
</div>
)
} else {
return (
<div class="events-view">
<Courses source={source} {...viewProps} />
</div>
)
}
} }

@ -1,17 +1,53 @@
import { ComboBox } from './ComboBox.jsx' import { ComboBox } from './ComboBox.jsx'
import { import { MODE_COURSE, MODE_SCHEDULE, MODE_WORKWEEK, MODE_WORKWEEK_GRID } from './EventsView.jsx'
MODE_COURSES,
MODE_SCHEDULE,
MODE_WORKWEEK,
MODE_WORKWEEK_GRID,
} from './EventsView.jsx'
import { DatePicker } from './DatePicker.jsx'
import { Help } from './Help.jsx' import { Help } from './Help.jsx'
import { Icon } from './Icon.jsx' import { Icon } from './Icon.jsx'
export const HamburgerMenu = ({ date, setDate, onClose, theme, setTheme }) => { export const HamburgerMenu = ({ onClose, mode, setMode, source, setSource, theme, setTheme }) => {
return ( return (
<div class="menu"> <div class="menu">
<div class="header">
<div class="option-group">
<button class="flat icon" onClick={onClose}>
<Icon name="close" />
</button>
<button
class="icon"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
<Icon name={theme === 'dark' ? 'dark_mode' : 'light_mode'} />
</button>
</div>
<div class="item logo">
<img src="logo-circuit-board.svg" alt="logo" /> / <span>Orario</span>
</div>
</div>
<div class="options">
<div class="label">Gruppo Corsi</div>
<ComboBox
value={source}
setValue={setSource}
options={[
{ value: 'anno-1', label: 'I' },
{ value: 'anno-2', label: 'II' },
{ value: 'anno-3', label: 'III' },
{ value: 'magistrale', label: 'Magistrale' },
{ value: 'tutti', label: 'Tutti' },
]}
/>
<div class="label">Visualizzazione</div>
<ComboBox
value={mode}
setValue={setMode}
options={[
{ value: MODE_COURSE, label: 'Corsi' },
{ value: MODE_SCHEDULE, label: 'Giornaliera' },
{ value: MODE_WORKWEEK, label: 'Settimana' },
{ value: MODE_WORKWEEK_GRID, label: 'Schema' },
]}
/>
</div>
<hr />
<div class="help"> <div class="help">
<h2> <h2>
<Icon name="info" /> <Icon name="info" />

@ -2,45 +2,38 @@ import { Icon } from './Icon.jsx'
export const Help = ({}) => ( export const Help = ({}) => (
<> <>
<h3>Visualizzazione Corsi</h3> <h3>Selezione Corsi</h3>
<p> <p>
Per visualizzare i corsi che ti interessano nella tabella dell'orario, devi Per visualizzare solo una selezione dei corsi disponibili, clicca su quelli che ti
prima selezionarli. Puoi selezionare dei corsi cliccandoli, cercandoli nelle interessano e poi nascondi gli altri con il tasto <Icon name="visibility" /> che
sezioni Primo anno (<b>I</b>), Secondo anno (<b>II</b>), Terzo anno ( comparirà in basso a destra.
<b>III</b>), Magistrale (<b>M</b>), o Tutti.
</p> </p>
<p> <p>
Una volta compiuta la selezione, è possibile vedere la tabella delle lezioni Per vedere l'orario settimanale, clicca su <b>Settimana</b>, che mostrerà un calendario
andando nella visualizzazione Orario ( con i corsi attualmente selezionati.
<Icon name="calendar_view_month" />)
</p> </p>
<p> <p>
Per via di eventuali preferenze personali, è possibile cambiare l'orientazione La selezione effettuata verrà preservata dalla visualizzazione <b>Corsi</b> a quella
della tabella Orario trasponendola, utilizzando il pulsante Trasponi ( Settimana, permettendo di individuare eventuali sovrapposizioni di orari.
<Icon name="switch_left" style="transform: rotate(-45deg)" />)
</p> </p>
<p> <p>
È anche possibile visualizzare in uno specchietto riassuntivo soltanto i corsi I corsi sono raggruppati per: primo, secondo, terzo anno (<b>I</b>,<b>II</b>,<b>III</b>)
selezionati, andando nella visualizzazione Lista ( e magistrale. Alternativamente, puoi scegliere fra tutti i corsi disponibili cliccando
<Icon name="list" />) su <b>Tutti</b>.
</p> </p>
<h3>Stampa</h3> <h3>Stampa</h3>
<p> <p>
Da desktop puoi stampare l'orario attualmente visibile con il bottone{' '} Da desktop puoi stampare l'orario attualmente visibile con il bottone <Icon name="print" /> (è
<Icon name="print" /> (è consigliato controllare le opzioni di stampa per consigliato controllare le opzioni di stampa per un risultato soddisfacente).
ottenere un risultato soddisfacente).
</p> </p>
<h3>Bug &amp; Contatti</h3> <h3>Bug &amp; Contatti</h3>
<p> <p>
In caso di bug puoi creare una issue sulla repo git del progetto{' '} In caso di bug puoi creare una issue sulla repo git del progetto{' '}
<a href="https://git.phc.dm.unipi.it/phc/orario"> <a href="https://git.phc.dm.unipi.it/phc/orario">
https://git.phc.dm.unipi.it/phc/orario https://git.phc.dm.unipi.it/phc/orario
</a>
, contattarci all'indirizzo{' '}
<a href="mailto:macchinisti@lists.dm.unipi.it">
macchinisti@lists.dm.unipi.it
</a>{' '} </a>{' '}
oppure passare direttamente in PHC. oppure puoi contattarci direttamente all'indirizzo{' '}
<a href="mailto:macchinisti@lists.dm.unipi.it">macchinisti@lists.dm.unipi.it</a>.
</p> </p>
</> </>
) )

@ -1,40 +0,0 @@
import { CompoundButton } from './CompoundButton.jsx'
import { Icon } from './Icon.jsx'
export const OptionBar = ({ view, setView }) => {
return (
<div class="option-bar">
<div class="option-group">
<div class="item option">
<CompoundButton
options={[
{ value: 'anno-1', label: 'I' },
{ value: 'anno-2', label: 'II' },
{ value: 'anno-3', label: 'III' },
{ value: 'magistrale', label: 'M' },
{ value: 'tutti', label: 'Tutti' },
]}
value={view}
setValue={setView}
/>
</div>
<CompoundButton
options={[
{
value: 'orario',
label: <Icon name="calendar_view_month" />,
icon: true,
},
{
value: 'lista',
label: <Icon name="list" />,
icon: true,
},
]}
value={view}
setValue={setView}
/>
</div>
</div>
)
}

@ -9,21 +9,17 @@ export const Popup = ({ title, children, onClose }) => {
class="popup-container" class="popup-container"
ref={popupLeaveRegionRef} ref={popupLeaveRegionRef}
onClick={e => onClick={e =>
popupLeaveRegionRef.current && popupLeaveRegionRef.current && popupLeaveRegionRef.current === e.target && onClose()
popupLeaveRegionRef.current === e.target &&
onClose()
} }
> >
<div class="popup-wrapper"> <div class="popup">
<div class="popup"> <div class="header">
<div class="header"> <div class="title">{title}</div>
<div class="title">{title}</div> <button class="flat icon" onClick={onClose}>
<button class="flat icon" onClick={onClose}> <Icon name="close" />
<Icon name="close" /> </button>
</button>
</div>
<div class="content">{children}</div>
</div> </div>
<div class="content">{children}</div>
</div> </div>
</div> </div>
) )

@ -1,18 +0,0 @@
import { DatePicker } from './DatePicker.jsx'
import { Icon } from './Icon.jsx'
export const SettingsBar = ({ theme, setTheme, date, setDate }) => {
return (
<div class="settings-bar">
<div class="settings-group">
<button
class="icon"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
<Icon name={theme === 'dark' ? 'dark_mode' : 'light_mode'} />
</button>
<DatePicker date={date} setDate={setDate} />
</div>
</div>
)
}

@ -1,13 +0,0 @@
import { Icon } from './Icon.jsx'
import { MODE_COURSES } from './EventsView.jsx'
export const ToolOverlay = ({ mode, toggleMode, onClose }) => (
<div class="overlay">
<button class="icon primary" onClick={toggleMode}>
<Icon name={mode === MODE_COURSES ? 'calendar_month' : 'view_list'} />
</button>
<button class="icon" onClick={onClose}>
<Icon name="close" />
</button>
</div>
)

@ -1,14 +1,13 @@
import { CompoundButton } from './CompoundButton.jsx' import { CompoundButton } from './CompoundButton.jsx'
import { DatePicker } from './DatePicker.jsx' import { MODE_COURSE, MODE_SCHEDULE, MODE_WORKWEEK, MODE_WORKWEEK_GRID } from './EventsView.jsx'
import { Icon } from './Icon.jsx' import { Icon } from './Icon.jsx'
export const Toolbar = ({ export const Toolbar = ({
mode,
setMode,
source, source,
setSource, setSource,
date, onShowMenu,
setDate,
showMobileMenu,
setShowMobileMenu,
onHelp, onHelp,
theme, theme,
setTheme, setTheme,
@ -16,11 +15,8 @@ export const Toolbar = ({
return ( return (
<div class="toolbar"> <div class="toolbar">
<div class="mobile"> <div class="mobile">
<button <button class="flat icon" onClick={onShowMenu}>
class="flat icon" <Icon name="menu" />
onClick={() => setShowMobileMenu(!showMobileMenu)}
>
<Icon name={`${showMobileMenu ? 'close' : 'menu'}`} />
</button> </button>
</div> </div>
<div class="item logo"> <div class="item logo">
@ -40,21 +36,20 @@ export const Toolbar = ({
setValue={setSource} setValue={setSource}
/> />
</div> </div>
</div>
<div class="option-group">
<div class="item option"> <div class="item option">
<CompoundButton <CompoundButton
options={[ options={[
{ value: 'orario', label: 'Orario' }, { value: MODE_COURSE, label: 'Corsi' },
{ value: 'lista', label: 'Lista' }, { value: MODE_WORKWEEK, label: 'Settimana' },
{ value: MODE_WORKWEEK_GRID, label: 'Schema' },
{ value: MODE_SCHEDULE, label: 'Giorno' },
]} ]}
value={source} value={mode}
setValue={setSource} setValue={setMode}
/> />
</div> </div>
</div>
<div class="option-group">
<DatePicker date={date} setDate={setDate} />
</div>
<div class="option-group">
<div class="item option"> <div class="item option">
<button class="icon" onClick={() => window.print()}> <button class="icon" onClick={() => window.print()}>
<Icon name="print" /> <Icon name="print" />

@ -2,39 +2,23 @@ import { format } from 'date-fns'
import _ from 'lodash' import _ from 'lodash'
import { useEffect, useRef, useState } from 'preact/hooks' import { useEffect, useRef, useState } from 'preact/hooks'
import { prettyCourseName, WEEK_DAYS } from '../../utils.jsx' import { normalizeCourseName, WEEK_DAYS } from '../../utils.jsx'
import { Icon } from '../Icon.jsx'
export const Courses = ({ export const Course = ({ events, selection, setSelection, hideOtherCourses }) => {
source,
timetables,
selection,
setSelection,
hideOtherCourses,
}) => {
const events = timetables[source]
const selectionSet = new Set(selection) const selectionSet = new Set(selection)
const visibleEvents = hideOtherCourses const visibleEvents = !hideOtherCourses ? events : events.filter(e => selectionSet.has(e.id))
? events.filter(e => selectionSet.has(e.id))
: events
const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'id'), 'id') const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'id'), 'id')
const profsPerCourse = _.mapValues(eventsByCourse, events =>
_.uniq(events.flatMap(event => event.docenti))
)
const [currentlyHovered, setCurrentlyHovered] = useState(null) const [currentlyHovered, setCurrentlyHovered] = useState(null)
const element = useRef() const element = useRef()
useEffect(() => { useEffect(() => {
if (element.current) { if (element.current) {
const l = e => { const l = e => {
const isMobile = window.matchMedia('(pointer: coarse)').matches
const $course = e.target.closest('.course') const $course = e.target.closest('.course')
if ($course && !isMobile) { if ($course) {
setCurrentlyHovered($course.dataset.courseId) setCurrentlyHovered($course.dataset.courseId)
} else { } else {
setCurrentlyHovered(null) setCurrentlyHovered(null)
@ -53,15 +37,6 @@ export const Courses = ({
return ( return (
<div class="course-view" ref={element}> <div class="course-view" ref={element}>
{hideOtherCourses && selection.length === 0 && (
<div class="warning">
<p>Non hai ancora selezionato nessun corso.</p>
<p>
Clicca sui corsi nelle altre visuali per selezionarli e
visualizzarli nella lista
</p>
</div>
)}
<div class="wrap-container"> <div class="wrap-container">
{Object.entries(eventsByCourse).map(([id, courseEvents]) => ( {Object.entries(eventsByCourse).map(([id, courseEvents]) => (
<div <div
@ -76,14 +51,14 @@ export const Courses = ({
else setSelection(selection.filter(selId => selId !== id)) else setSelection(selection.filter(selId => selId !== id))
}} }}
> >
<div class="title">{prettyCourseName(courseEvents[0].name)}</div> <div class="title">{normalizeCourseName(courseEvents[0].name)}</div>
<div class="docenti">{profsPerCourse[id].join(', ')}</div> <div class="docenti">{courseEvents[0].docenti.join(', ')}</div>
<div class="events"> <div class="events">
{courseEvents.map(course => ( {courseEvents.map(course => (
<div> <div>
{WEEK_DAYS[course.start.getDay()]}{' '} {WEEK_DAYS[course.start.getDay()]}{' '}
{format(course.start, 'H:mm')}&ndash; {format(course.start, 'H:mm')} &ndash;{' '}
{format(course.end, 'H:mm')} {course.aule.join(', ')} {format(course.end, 'H:mm')} {course.aula}
</div> </div>
))} ))}
</div> </div>

@ -1,266 +1,54 @@
import { useEffect, useRef, useState } from 'preact/hooks' import { format } from 'date-fns'
import _ from 'lodash' import _ from 'lodash'
import { differenceInMinutes, startOfDay } from 'date-fns' import { normalizeCourseName, WEEK_DAYS, withClasses } from '../../utils.jsx'
import { WEEK_DAYS_SHORT, prettyCourseName, usePersistentState } from '../../utils.jsx'
import { layoutEvents, layoutIntervals } from '../../interval-layout.js'
import { Popup } from '../Popup.jsx'
import { Icon } from '../Icon.jsx'
const TransposePopup = ({ onClose }) => { export const Schedule = ({ events, selection, setSelection, hideOtherCourses }) => {
return ( const selectionSet = new Set(selection)
<Popup
title={
<>
<Icon name="info" /> Attenzione! La tabella è stata trasposta!
</>
}
onClose={onClose}
>
<p>A grande richiesta popolare abbiamo trasposto la tabella dell'orario!</p>
<p>
Assicurati quindi di leggerla correttamente (dall'alto verso il basso
invece che da sinistra verso destra).
</p>
<p> const visibleEvents = !hideOtherCourses ? events : events.filter(e => selectionSet.has(e.id))
Se preferisci utilizzare la versione vecchia, puoi utilizzare il pulsante
Trasponi <Icon name="switch_left" style="transform: rotate(-45deg)" />{' '}
nell'origine della tabella per trasporla. Questa scelta verrà salvata nei
cookie e verrà ricordata in futuro
</p>
</Popup>
)
}
const NoCourseWarning = () => { const eventsByWeekday = _.mapValues(
return ( _.groupBy(visibleEvents, e => e.start.getDay()),
<div class="warning"> dailyEvents => _.groupBy(dailyEvents, e => e.start.getHours())
<p>Non hai ancora selezionato nessun corso.</p>
<p>
Clicca sui corsi nelle altre visuali per selezionarli e visualizzarli
nell'orario
</p>
</div>
) )
}
const Layout = ({ layout, day, colors }) => {
return ( return (
<> <div class="schedule-view">
{layout.map(block => ( {Object.entries(eventsByWeekday).map(([index, dailyEvents]) => (
<div
class="event-block-wrapper"
style={{
'--time-start': block.start,
'--time-end': block.end,
'--day-position': day,
}}
>
<div class="event-block">
{block.events.map((event, index) => (
<div
class="event-wrapper"
style={{
'--block-size': block.end - block.start,
'--size': event.end - event.start,
'--relative-start': event.start - block.start,
'--index': event.index,
'--of': block.layers,
'--color': `var(--event-${colors[event.id]})`,
}}
>
<div class="event">
{event.aule.map(aula => (
<div>{aula.replace(/^Fib /, '')}</div>
))}
</div>
</div>
))}
</div>
</div>
))}
</>
)
}
const ScheduleGrid = ({
orientation,
setOrientation,
weekStart,
weekEnd,
dayBlocksLayout,
colors,
}) => {
return (
<div
class={`grid ${orientation}`}
style={{
'--time-slots': weekEnd - weekStart,
}}
>
<div class="transpose-button">
<button
class="small"
onClick={() =>
setOrientation(
orientation === 'original' ? 'transposed' : 'original'
)
}
>
<Icon name="switch_left" style="transform: rotate(-45deg)" />
</button>
</div>
{[1, 2, 3, 4, 5].map(n => (
<> <>
<div class="day-label" style={`--position: ${n + 1}`}> <div class="header giorno">
{WEEK_DAYS_SHORT[n]} <div class="inner">{WEEK_DAYS[index]}</div>
</div> </div>
<div class="day-line" style={`--position: ${n + 1}`}></div> {Object.values(dailyEvents).map(events => (
</> <>
))} <div class="header orario">{format(events[0].start, 'H:mm')}</div>
{events.map(event => (
{[9, 11, 14, 16].map(n => ( <div
<div class={withClasses([
class="time-label" 'event',
style={{ selectionSet.has(event.id) && 'selected',
'--position': n * 2 - weekStart, ])}
}} onClick={() => {
> if (!selectionSet.has(event.id))
{n}-{n + 2} setSelection([...selection, event.id])
</div> else
))} setSelection(
<div class="time-line" style="--position: 0"></div> selection.filter(selId => selId !== event.id)
{[9, 11, 13, 14, 16, 18].map(n => ( )
<> }}
{n * 2 > weekStart && n * 2 < weekEnd && ( >
<div <div class="title">{normalizeCourseName(event.name)}</div>
class="time-line" <div class="orario">
style={{ {format(event.start, 'H:mm')} &ndash;{' '}
'--position': n * 2 - weekStart, {format(event.end, 'H:mm')}
}} </div>
></div> <div class="aula">{event.aula}</div>
)} </div>
</> ))}
))} </>
))}
{Object.entries(dayBlocksLayout).map(([day, layout]) => (
<Layout layout={layout} day={day} colors={colors} />
))}
</div>
)
}
const ScheduleLegend = ({ courses, colors }) => {
return (
<div class="legend">
{courses.map(course => (
<>
<div
class="color"
style={{
'--color': `var(--event-${colors[course.id]})`,
}}
></div>
<div class="name">{prettyCourseName(course.name)}</div>
</> </>
))} ))}
</div> </div>
) )
} }
const ScheduleCard = ({
orientation,
setOrientation,
weekStart,
weekEnd,
dayBlocksLayout,
courses,
colors,
}) => {
return (
<div class="schedule-card">
<ScheduleGrid
orientation={orientation}
setOrientation={setOrientation}
weekStart={weekStart}
weekEnd={weekEnd}
dayBlocksLayout={dayBlocksLayout}
colors={colors}
/>
<ScheduleLegend courses={courses} colors={colors} />
</div>
)
}
export const Schedule = ({ timetables, selection }) => {
const [hasSeenTranspose, setHasSeenTranspose] = usePersistentState(
'transpose_info',
'false'
)
const [orientation, setOrientation] = usePersistentState('orientation', 'original')
const colorList = ['red', 'purple', 'blue', 'yellow', 'green', 'orange', 'lightblue']
const allEvents = timetables['tutti']
const selectionSet = new Set(selection)
const events = allEvents
.filter(e => selectionSet.has(e.id))
.map(e => ({
...e,
day: e.start.getDay(),
start: differenceInMinutes(e.start, startOfDay(e.start)),
end: differenceInMinutes(e.end, startOfDay(e.start)),
}))
const weekStart = Math.min(...events.map(e => e.start), 9 * 60) / 30
const weekEnd = Math.max(...events.map(e => e.end), 18 * 60) / 30
const relativeEvents = events.map(e => ({
...e,
start: Math.round(e.start / 30 - weekStart),
end: Math.round(e.end / 30 - weekStart),
}))
const courses = _.uniqBy(events, event => event.id)
const colors = Object.fromEntries(
courses.map((course, index) => {
return [course.id, colorList[index % colorList.length]]
})
)
const base = {
1: [],
2: [],
3: [],
4: [],
5: [],
}
const eventsByWeekday = {
...base,
..._.groupBy(relativeEvents, event => event.day),
}
const dayBlocksLayout = _.mapValues(eventsByWeekday, dayEvents =>
layoutEvents(dayEvents)
)
return (
<>
{hasSeenTranspose === 'false' && (
<TransposePopup onClose={() => setHasSeenTranspose('true')} />
)}
<div class="schedule-view">
{selection.length === 0 && <NoCourseWarning />}
<ScheduleCard
orientation={orientation}
setOrientation={setOrientation}
weekStart={weekStart}
weekEnd={weekEnd}
dayBlocksLayout={dayBlocksLayout}
courses={courses}
colors={colors}
/>
</div>
</>
)
}

@ -0,0 +1,150 @@
import { useEffect, useRef, useState } from 'preact/hooks'
import _ from 'lodash'
import { differenceInMinutes, startOfDay } from 'date-fns'
import { hashString, normalizeCourseName, WEEK_DAYS } from '../../utils.jsx'
import { layoutIntervals } from '../../interval-layout.js'
export const WorkWeek = ({ events, selection, setSelection, hideOtherCourses }) => {
const selectionSet = new Set(selection)
const eventsByWeekday = _.groupBy(
!hideOtherCourses ? events : events.filter(e => selectionSet.has(e.id)),
event => event.start.getDay()
)
// const dayIntervalLayout = _.mapValues(Object.assign(base, eventsByWeekday), events =>
const dayIntervalLayout = _.mapValues(eventsByWeekday, events =>
layoutIntervals(
events.map(e => ({
start: differenceInMinutes(e.start, startOfDay(e.start)),
end: differenceInMinutes(e.end, startOfDay(e.start)),
data: e,
}))
)
)
const [currentlyHovered, setCurrentlyHovered] = useState(null)
const element = useRef()
useEffect(() => {
if (element.current) {
const l = e => {
const $event = e.target.closest('.event')
if ($event) {
setCurrentlyHovered($event.dataset.eventId)
} else {
setCurrentlyHovered(null)
}
}
element.current.addEventListener('mousemove', l)
element.current.addEventListener('mouseleave', l)
return () => {
element.current.removeEventListener('mousemove', l)
element.current.removeEventListener('mouseleave', l)
}
}
}, [element.current])
return (
<div class="work-week-v-view" ref={element}>
<div class="pivot"></div>
<div class="left-header">
<div class="blocks">
<div
class="block skip-border"
style={{
'--start': 9 - 7,
'--size': 2,
}}
>
9:00 &ndash; 11:00
</div>
<div
class="block"
style={{
'--start': 11 - 7,
'--size': 2,
}}
>
11:00 &ndash; 13:00
</div>
<div
class="block skip-border"
style={{
'--start': 14 - 7,
'--size': 2,
}}
>
14:00 &ndash; 16:00
</div>
<div
class="block"
style={{
'--start': 16 - 7,
'--size': 2,
}}
>
16:00 &ndash; 18:00
</div>
</div>
</div>
{Object.entries(dayIntervalLayout).map(([index, layout]) => (
<div class="day" style={{ '--size': Math.max(1, layout.length) }}>
<div class="top-header">{WEEK_DAYS[parseInt(index)]}</div>
<div class="events">
{layout.map((events, stackIndex) => (
<>
{events.map(event => (
<div
class={
'event' +
(currentlyHovered === event.data.id
? ' highlight'
: '') +
(selectionSet.has(event.data.id) ? ' selected' : '')
}
data-event-id={event.data.id}
style={{
'--start': event.start / 60 - 7,
'--stack': stackIndex + 1,
'--size': (event.end - event.start) / 60,
'--hue':
(Math.abs(hashString('seed3' + event.data.id)) %
360) +
'deg',
}}
onClick={() => {
if (!selectionSet.has(event.data.id))
setSelection([...selection, event.data.id])
else
setSelection(
selection.filter(id => event.data.id !== id)
)
}}
>
<div class="title">
{normalizeCourseName(event.data.name)}
</div>
<div class="aula">{event.data.aula}</div>
</div>
))}
</>
))}
{/* Grid Tracks */}
{[1, 3, 5].map(i => (
<div class="grid-line-h" style={{ '--track': i }}></div>
))}
{[6, 8, 10].map(i => (
<div class="grid-line-h" style={{ '--track': i }}></div>
))}
</div>
</div>
))}
</div>
)
}

@ -0,0 +1,200 @@
import { useEffect, useRef, useState } from 'preact/hooks'
import _ from 'lodash'
import { differenceInMinutes, startOfDay } from 'date-fns'
import { hashString, normalizeCourseName, WEEK_DAYS } from '../../utils.jsx'
import { layoutIntervals } from '../../interval-layout.js'
export const WorkWeekGrid = ({ events, selection, setSelection, hideOtherCourses }) => {
const selectionSet = new Set(selection)
const colorList = [
'red',
'purple',
'blue',
'yellow',
'green',
'orange',
'lightblue',
];
const courses = _.uniqBy(!hideOtherCourses ? events : events.filter(e => selectionSet.has(e.id)), event => event.id);
const colors = Object.fromEntries(courses.map((course, index) => {
return [
course.id,
[
colorList[index % colorList.length],
courses.length <= colorList.length ? '' : String.fromCharCode(65 + Math.floor(index / colorList.length))
]
];
}));
const base = {
1: [],
2: [],
3: [],
4: [],
5: [],
}
const eventsByWeekday = {
... base,
... _.groupBy(
!hideOtherCourses ? events : events.filter(e => selectionSet.has(e.id)),
event => event.start.getDay()
)
}
const dayIntervalLayout = _.mapValues(eventsByWeekday, events =>
layoutIntervals(
events.map(e => ({
start: differenceInMinutes(e.start, startOfDay(e.start)),
end: differenceInMinutes(e.end, startOfDay(e.start)),
data: e,
}))
)
)
const startsAndEnds = Object.entries(dayIntervalLayout).flatMap(([index, layout]) => layout.flatMap(events => events.map(event => [event.start, event.end])));
const minStart = Math.min(...startsAndEnds.map(([s, e]) => s)) / 60;
const maxEnd = Math.max(...startsAndEnds.map(([s, e]) => e)) / 60;
// const timeStart = minStart < 9 ? 7 :
// minStart < 11 ? 9 :
// minStart < 13 ? 11 :
// minStart < 14 ? 13 :
// minStart < 16 ? 14 :
// minStart < 18 ? 16 :
// 18;
// const timeEnd = maxEnd > 18 ? 20 :
// maxEnd > 16 ? 18 :
// maxEnd > 14 ? 16 :
// maxEnd > 13 ? 14 :
// maxEnd > 11 ? 13 :
// maxEnd > 9 ? 11 :
// 9;
const timeStart = minStart < 9 ? 7 : 9;
const timeEnd = maxEnd > 18 ? 20 : 18;
const daySizes = Object.entries(dayIntervalLayout).map(([index, layout]) => Math.max(1, layout.length));
const dayOffsets = daySizes.map((v, i) => {
let sum = 0;
for(let j = 0; j < i; j++) {
sum += daySizes[j];
}
return sum;
});
const daysLength = daySizes[4] + dayOffsets[4];
const [currentlyHovered, setCurrentlyHovered] = useState(null)
const element = useRef()
useEffect(() => {
if (element.current) {
const l = e => {
const $event = e.target.closest('.event')
if ($event) {
setCurrentlyHovered($event.dataset.eventId)
} else {
setCurrentlyHovered(null)
}
}
element.current.addEventListener('mousemove', l)
element.current.addEventListener('mouseleave', l)
return () => {
element.current.removeEventListener('mousemove', l)
element.current.removeEventListener('mouseleave', l)
}
}
}, [element.current])
return (
<div class="work-week-grid-view" ref={element}>
<div class="grid" style={{'--days-length': daysLength, '--time-length': timeEnd - timeStart}}>
{[7, 9, 11, 14, 16, 18].map(n =>
<>
{n + 2 > timeStart && n < timeEnd && (
<div class="time" style={{'--offset': n - timeStart + 2}}>{n}-{n+2}</div>
)}
</>
)}
<div class="vline" style="--offset: 2"></div>
{[9, 11, 13, 14, 16, 18].map(n =>
<>
{n > timeStart && n < timeEnd && (
<div class="vline" style={{'--offset': n - timeStart + 2}}></div>
)}
</>
)}
<div class="day-name" style={{'--line': 2 + dayOffsets[0]}}>Lun</div>
<div class="day-name" style={{'--line': 2 + dayOffsets[1]}}>Mar</div>
<div class="day-name" style={{'--line': 2 + dayOffsets[2]}}>Mer</div>
<div class="day-name" style={{'--line': 2 + dayOffsets[3]}}>Gio</div>
<div class="day-name" style={{'--line': 2 + dayOffsets[4]}}>Ven</div>
<div class="hline" style={{'--line': 2 + dayOffsets[0]}}></div>
<div class="hline" style={{'--line': 2 + dayOffsets[1]}}></div>
<div class="hline" style={{'--line': 2 + dayOffsets[2]}}></div>
<div class="hline" style={{'--line': 2 + dayOffsets[3]}}></div>
<div class="hline" style={{'--line': 2 + dayOffsets[4]}}></div>
{Object.entries(dayIntervalLayout).map(([index, layout]) => (
<>
{layout.map((events, stackIndex) => (
<>
{events.map(event => (
<div class={
'event' +
(currentlyHovered === event.data.id
? ' highlight'
: '') +
(selectionSet.has(event.data.id) ? ' selected' : '')
}
data-event-id={event.data.id}
style={{
'--line': 2 + dayOffsets[index-1] + stackIndex,
'--offset': event.start / 60 - timeStart + 2,
'--length': (event.end - event.start) / 60,
'--color': `var(--bubble-${colors[event.data.id][0]})`,
'--border-color': `var(--bubble-border-${colors[event.data.id][0]})`,
'--highlight-color': `var(--bubble-highlight-${colors[event.data.id][0]})`
}}
onClick={() => {
if (!selectionSet.has(event.data.id))
setSelection([...selection, event.data.id])
else
setSelection(
selection.filter(id => event.data.id !== id)
)
}}
>
{colors[event.data.id][1] !== '' && (
<div className="distinguisher">{`(${colors[event.data.id][1]})`}</div>
)}
{event.data.aula}</div>
))}
</>
))}
</>
))}
</div>
<div class="legend">
{courses.map(course => (
<>
<div class="color"
style={{
'--color': `var(--bubble-${colors[course.id][0]})`,
'--border-color': `var(--bubble-border-${colors[course.id][0]})`
}}
>
{colors[course.id][1]}
</div>
<div class="name">{normalizeCourseName(course.name)}</div>
</>
))}
</div>
</div>
)
}

@ -0,0 +1,104 @@
import { useEffect, useRef } from 'preact/hooks'
import _ from 'lodash'
import { differenceInMinutes, startOfDay } from 'date-fns'
import { hashString, normalizeCourseName, WEEK_DAYS } from '../../utils.jsx'
import { layoutIntervals } from '../../interval-layout.js'
export const WorkWeekTranspose = ({ events }) => {
const eventsByWeekday = _.groupBy(events, event => event.start.getDay())
// For each weekday compute the interval layout
const rowLayouts = _.mapValues(eventsByWeekday, events =>
layoutIntervals(
events.map(e => ({
start: differenceInMinutes(e.start, startOfDay(e.start)),
end: differenceInMinutes(e.end, startOfDay(e.start)),
data: e,
}))
)
)
return (
<div class="work-week-h-view">
<div class="week">
{WEEK_DAYS.slice(1, 6).map((label, index) => (
<div class="day" style={{ '--size': rowLayouts[index + 1]?.length ?? 0 }}>
{label}
</div>
))}
</div>
<div class="events">
<div class="header">
<div class="label" style={{ 'grid-column': '2 / span 2' }}>
9:00 &ndash; 11:00
</div>
<div class="label" style={{ 'grid-column': '4 / span 2' }}>
11:00 &ndash; 13:00
</div>
<div class="label" style={{ 'grid-column': '7 / span 2' }}>
14:00 &ndash; 16:00
</div>
<div class="label" style={{ 'grid-column': '9 / span 2' }}>
16:00 &ndash; 18:00
</div>
</div>
<div class="days">
{Object.values(rowLayouts).map(layout => (
<div class="day" style={{ '--size': layout.length }}>
{layout.map((events, rowIndex) =>
events.map(event => {
const Local = () => {
const eventRef = useRef()
const updateMinWidth = () => {
eventRef.current.style.minWidth =
eventRef.current.offsetWidth + 'px'
}
useEffect(() => {
if (eventRef.current) {
setTimeout(updateMinWidth, 100)
window.addEventListener('resize', updateMinWidth)
return () => {
window.removeEventListener(
'resize',
updateMinWidth
)
}
}
}, [eventRef.current])
return (
<div
class="event"
style={{
'--row': rowIndex + 1,
'--start': event.start / 60 - 7,
'--size': (event.end - event.start) / 60,
'--hue':
(Math.abs(
hashString('seed3' + event.data.id)
) %
360) +
'deg',
}}
ref={eventRef}
>
{normalizeCourseName(event.data.name)}
</div>
)
}
return <Local />
})
)}
</div>
))}
</div>
</div>
</div>
)
}

@ -34,55 +34,6 @@ export function layoutIntervals(intervals, { tight } = {}) {
return stack.map(({ intervals }) => intervals) return stack.map(({ intervals }) => intervals)
} }
function layoutBlockEvents(events) {
events.sort((a, b) => a.start - b.start)
let result = []
for (const event of events) {
let viableIndex = 0
while (
result.filter(
e => e.index === viableIndex && e.start < event.end && event.start < e.end
).length !== 0
) {
viableIndex += 1
}
result.push({ ...event, index: viableIndex })
}
return result
}
export function layoutEvents(events) {
const overlap = (event, block) => event.start < block.end && block.start < event.end
events.sort((a, b) => a.start - b.start)
let layout = []
for (const event of events) {
const blocks = layout.filter(block => overlap(event, block))
if (blocks.length > 0) {
layout = layout.filter(block => !overlap(event, block))
layout.push({
start: Math.min(event.start, ...blocks.map(block => block.start)),
end: Math.max(event.end, ...blocks.map(block => block.end)),
events: blocks.flatMap(block => block.events).concat([event]),
})
} else {
layout.push({ start: event.start, end: event.end, events: [event] })
}
}
return layout.map(block => {
const events = layoutBlockEvents(block.events)
return {
...block,
layers: Math.max(...events.map(event => event.index)) + 1,
events: events,
}
})
}
// // // //
// // Testing... // // Testing...
// // // //

@ -1,288 +1,180 @@
import _ from 'lodash' import _ from 'lodash'
import { render } from 'preact' import { render } from 'preact'
import { useEffect, useState } from 'preact/hooks' import { useEffect, useState } from 'preact/hooks'
import { ToolOverlay } from './components/CourseVisibility.jsx'
// import { ToolOverlay } from './components/ToolOverlay.jsx' import { EventsView, MODE_WORKWEEK_GRID } from './components/EventsView.jsx'
//
// import {
// EventsView,
// MODE_COURSES,
// MODE_SCHEDULE,
// } from './components/EventsView.jsx'
import { Courses } from './components/view/Courses.jsx'
import { Schedule } from './components/view/Schedule.jsx'
import { HamburgerMenu } from './components/HamburgerMenu.jsx' import { HamburgerMenu } from './components/HamburgerMenu.jsx'
import { Help } from './components/Help.jsx' import { Help } from './components/Help.jsx'
import { Icon } from './components/Icon.jsx' import { Icon } from './components/Icon.jsx'
import { Popup } from './components/Popup.jsx' import { Popup } from './components/Popup.jsx'
import { Toolbar } from './components/Toolbar.jsx' import { Toolbar } from './components/Toolbar.jsx'
import { OptionBar } from './components/OptionBar.jsx'
import {
prettyAulaName,
prettyProfName,
clearOldPersistentStates,
usePersistentState,
} from './utils.jsx'
import { SettingsBar } from './components/SettingsBar.jsx'
// Che fanno queste due righe?
window._ = _ window._ = _
window.dataBuffer = {} window.dataBuffer = {}
const TIMETABLE_IDS = { const CALENDAR_IDS = {
'anno-1': '667e88275e9623041f0e43d4', 'anno-1': ['6308cfcb1df5cb026699ce32'],
'anno-2': '667e89055e9623041f0e43d6', 'anno-2': ['6308e2dc09352a0208fefdd9'],
'anno-3': '667e89fcf748ed0415a11dcc', 'anno-3': ['6308e42a1df5cb026699ced4'],
'magistrale': '667ebae63379a3046517ffd4', 'magistrale': ['6308e8ea0c34e703bb1f7e85'],
'tutti': [
'6308cfcb1df5cb026699ce32',
'6308e2dc09352a0208fefdd9',
'6308e42a1df5cb026699ced4',
'6308e8ea0c34e703bb1f7e85',
],
} }
// const DEFAULT_DATE_RANGE = { async function loadEventi(ids) {
// from: '2023-10-09T00:00:00.000Z', const calendari = await Promise.all(
// to: '2023-10-14T00:00:00.000Z', ids.map(async id => {
// } // Almost directly copy-pasted from Chrome Dev Tools
const req = await fetch(
// const DATE_RANGES = { 'https://apache.prod.up.cineca.it/api/Impegni/getImpegniCalendarioPubblico',
// '64a7c1c651f079001d52e9c8': DEFAULT_DATE_RANGE, {
// '6308e2dc09352a0208fefdd9': DEFAULT_DATE_RANGE, headers: {
// '6308e42a1df5cb026699ced4': DEFAULT_DATE_RANGE, 'content-type': 'application/json;charset=UTF-8',
// '64a7c7091ab813002c5d9ede': DEFAULT_DATE_RANGE, 'sec-fetch-dest': 'empty',
// } 'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
function specialEventPatches(eventi) { },
// Il laboratorio del primo anno in realtà è in due gruppi separati body: JSON.stringify({
eventi.forEach(evento => { mostraImpegniAnnullati: true,
if ( mostraIndisponibilitaTotali: false,
evento.nome === 'LABORATORIO DI INTRODUZIONE ALLA MATEMATICA COMPUTAZIONALE' linkCalendarioId: id,
) { clienteId: '628de8b9b63679f193b87046',
if (evento.docenti[0].nome === 'GIOVANNI') { pianificazioneTemplate: false,
evento.nome += ' (A)' dataInizio: '2022-10-02T22:00:00.000Z',
} dataFine: '2022-10-07T22:00:00.000Z',
if (evento.docenti[0].nome === 'PAOLO') { }),
evento.nome += ' (B)' method: 'POST',
} mode: 'cors',
} credentials: 'omit',
}) }
)
return eventi
} return await req.json()
})
function formatEvents(timetable) { )
return timetable.map(({ nome, dataInizio, dataFine, docenti, aule }) => {
return {
id: nome,
name: _.split(nome, '-', 1)[0].trim(),
start: new Date(dataInizio),
end: new Date(dataFine),
docenti: docenti.map(({ nome, cognome }) => prettyProfName(nome, cognome)),
aule: aule.map(aula => prettyAulaName(aula.codice)),
}
})
}
async function loadCalendari(date) {
function getMonday(d) {
const day = d.getDay()
const diff = d.getDate() - day + (day === 0 ? -6 : 1)
const monday = new Date(d.setDate(diff))
monday.setUTCHours(0, 0, 0, 0)
return monday
}
const monday = getMonday(date)
const saturday = new Date(monday)
saturday.setDate(monday.getDate() + 5)
async function req(id) { // console.log(calendari)
// Almost directly copy-pasted from Chrome Dev Tools
const req = await fetch(
'https://apache.prod.up.cineca.it/api/Impegni/getImpegniCalendarioPubblico',
{
headers: {
'content-type': 'application/json;charset=UTF-8',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
},
body: JSON.stringify({
mostraImpegniAnnullati: true,
mostraIndisponibilitaTotali: false,
linkCalendarioId: id,
clienteId: '628de8b9b63679f193b87046',
pianificazioneTemplate: false,
dataInizio: monday.toISOString(),
dataFine: saturday.toISOString(),
}),
method: 'POST',
mode: 'cors',
credentials: 'omit',
}
)
return await req.json() if (ids.length === 1) {
return calendari[0]
} }
const requests = [ return _.uniqBy(_.concat(...calendari), 'id')
req(TIMETABLE_IDS['anno-1']),
req(TIMETABLE_IDS['anno-2']),
req(TIMETABLE_IDS['anno-3']),
req(TIMETABLE_IDS['magistrale']),
]
const results = await Promise.all(requests)
const timetablesRaw = results.map(timetable =>
specialEventPatches(_.uniqBy(timetable, 'id'))
)
const allRaw = specialEventPatches(_.concat(...results), 'id')
return {
'anno-1': formatEvents(timetablesRaw[0]),
'anno-2': formatEvents(timetablesRaw[1]),
'anno-3': formatEvents(timetablesRaw[2]),
'magistrale': formatEvents(timetablesRaw[3]),
'tutti': formatEvents(allRaw),
}
} }
const View = ({ view, selection, setSelection, timetables }) => {
if (view === 'orario') {
return <Schedule selection={selection} timetables={timetables} />
} else if (view === 'lista') {
return (
<Courses
selection={selection}
setSelection={setSelection}
source={'tutti'}
timetables={timetables}
hideOtherCourses={true}
/>
)
} else {
return (
<Courses
selection={selection}
setSelection={setSelection}
source={view}
timetables={timetables}
hideOtherCourses={false}
/>
)
}
}
const App = ({}) => { const App = ({}) => {
// Clear persistent states unless state_token corresponds to the one passed
// as the argument. Useful with breaking updates. Change this token if your
// (breaking) update needs a reset of persistent states to avoid crashes.
//
// Use any random string of your choice
// clearOldPersistentStates('e73cba02')
const [date, setDate] = useState(new Date().toISOString())
// Data Sources // Data Sources
const [view, setView] = usePersistentState('view', 'magistrale') const [source, setSource] = useState('anno-1')
const [timetables, setTimetables] = useState(null) const [eventi, setEventi] = useState([])
useEffect(async () => {
setTimetables(await loadCalendari(new Date(date)))
}, [date])
// View Modes // View Modes
// const [mode, setMode] = usePersistentState('orario.mode', MODE_COURSES) const [mode, setMode] = useState(MODE_WORKWEEK_GRID)
// Selection // Selection
const [selectedCourses, setSelectedCourses] = usePersistentState('selection', []) const [selectedCourses, setSelectedCourses] = useState([])
const [hideOtherCourses, setHideOtherCourses] = useState(false)
// Menus // Menus
const [helpVisible, setHelpVisible] = useState(false) const [helpVisible, setHelpVisible] = useState(false)
const [showMobileMenu, setShowMobileMenu] = useState(false) const [showMobileMenu, setShowMobileMenu] = useState(false)
// const groupIds = new Set(eventi.map(e => e.nome)) useEffect(async () => {
const toolOverlayVisible = selectedCourses.length > 0 console.log('source changed')
const eventi = await loadEventi(CALENDAR_IDS[source])
window.dataBuffer[source] = eventi
setEventi(eventi)
}, [source])
const [theme, setTheme] = usePersistentState( const groupIds = new Set(eventi.map(e => e.nome))
'theme', const toolOverlayVisible =
'light' selectedCourses.length > 0 && selectedCourses.filter(id => groupIds.has(id)).length > 0
// window.matchMedia('(prefers-color-scheme: dark)').matches
// ? 'dark' useEffect(() => {
// : 'light' console.log('course length changed')
const groupIds = new Set(eventi.map(e => e.nome))
if (
selectedCourses.length === 0 ||
selectedCourses.filter(id => groupIds.has(id)).length === 0
) {
setHideOtherCourses(false)
}
}, [eventi, selectedCourses.length])
const [theme, setTheme] = useState(
localStorage.getItem('theme') ??
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
) )
document.body.classList.toggle('dark-mode', theme === 'dark') document.body.classList.toggle('dark-mode', theme === 'dark')
localStorage.setItem('theme', theme)
return ( return (
<> <>
<Toolbar <Toolbar
{...{ {...{
source: view, mode,
setSource: setView, setMode,
date: date, source,
setDate: setDate, setSource,
showMobileMenu: showMobileMenu, onShowMenu: () => setShowMobileMenu(true),
setShowMobileMenu: setShowMobileMenu,
onHelp: () => setHelpVisible(true), onHelp: () => setHelpVisible(true),
theme, theme,
setTheme, setTheme,
}} }}
/> />
{showMobileMenu ? ( <EventsView
<SettingsBar mode={mode}
{...{ selection={selectedCourses}
theme, setSelection={setSelectedCourses}
setTheme, hideOtherCourses={hideOtherCourses}
date, start={new Date(2022, 10, 3)}
setDate, events={eventi.map(({ nome, dataInizio, dataFine, docenti, aule }) => ({
id: nome,
name: _.split(nome, '-', 1)[0].trim(),
start: new Date(dataInizio),
end: new Date(dataFine),
docenti: docenti.map(({ nome, cognome }) =>
_.startCase(_.lowerCase(nome) + ' ' + _.lowerCase(cognome))
),
aula: _.startCase(aule[0].codice.toLowerCase()).replace(
/([A-Z]) ([1-9])/,
'$1$2'
),
}))}
/>
{toolOverlayVisible && (
<ToolOverlay
visibility={hideOtherCourses}
toggleVisibility={() => setHideOtherCourses(s => !s)}
onClose={() => {
setSelectedCourses([])
setHideOtherCourses(false)
}} }}
/> />
) : ( )}
<OptionBar {showMobileMenu && (
<HamburgerMenu
{...{ {...{
view: view, mode,
setView: setView, setMode,
onHelp: () => setHelpVisible(true), source,
setSource,
theme,
setTheme,
onClose: () => {
setShowMobileMenu(false)
},
}} }}
orizzontale
/> />
)} )}
<div class="content">
{timetables &&
(showMobileMenu ? (
<HamburgerMenu
{...{
date,
setDate,
theme,
setTheme,
onClose: () => {
setShowMobileMenu(false)
},
}}
/>
) : timetables['tutti'].length === 0 ? (
<div class="warning">
<p>
Non esistono corsi per la settimana selezionata: buone
vacanze! 🎉
</p>
<p>
Per cambiare settimana puoi usare il widget Calendario (
<Icon name="calendar_month" />) in alto a destra
<br />
In versione mobile, il widget Calendario è situato dentro
il Menu (
<Icon name="menu" />)
</p>
</div>
) : (
<View
selection={selectedCourses}
setSelection={setSelectedCourses}
view={view}
timetables={timetables}
/>
))}
</div>
{/* showMobileMenu && (
) */}
{helpVisible && ( {helpVisible && (
<Popup <Popup
title={ title={

File diff suppressed because it is too large Load Diff

@ -1,7 +1,5 @@
// Calendar // Calendar
import { useEffect, useState } from 'preact/hooks'
export const WEEK_DAYS = [ export const WEEK_DAYS = [
'Domenica', 'Domenica',
'Lunedì', 'Lunedì',
@ -12,8 +10,6 @@ export const WEEK_DAYS = [
'Sabato', 'Sabato',
] ]
export const WEEK_DAYS_SHORT = ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab']
// Hashing // Hashing
export function hashString(str, seed = 0) { export function hashString(str, seed = 0) {
@ -31,7 +27,7 @@ export function hashString(str, seed = 0) {
// Courses // Courses
export function prettyCourseName(name) { export function normalizeCourseName(name) {
return name return name
.split(' ') .split(' ')
.map(word => word.toLowerCase()) .map(word => word.toLowerCase())
@ -44,24 +40,8 @@ export function prettyCourseName(name) {
}) })
.join(' ') .join(' ')
.replaceAll('Ii', 'II') .replaceAll('Ii', 'II')
.replaceAll('IIi', 'III')
.replaceAll('Iii', 'III') .replaceAll('Iii', 'III')
.replaceAll(/'(.)/g, ({}, letter) => "'" + letter.toUpperCase()) .replaceAll(/'(.)/g, ({}, letter) => "'" + letter.toUpperCase())
.replaceAll(/\((a|b)\)/g, ({}, letter) => '(' + letter.toUpperCase() + ')')
}
export function prettyAulaName(name) {
return name
.replace('FIB', 'Fib')
.replace('RIUNIONI', 'Riunioni')
.replace(/([A-Z0-9])\-LAB/, 'Lab $1')
}
export function prettyProfName(nome, cognome) {
return (nome + ' ' + cognome)
.split(/\b/)
.map(word => _.capitalize(word))
.join('')
} }
// JSX // JSX
@ -73,30 +53,3 @@ export const withClasses = classes =>
.filter(([, value]) => value) .filter(([, value]) => value)
.map(([key]) => key) .map(([key]) => key)
.join(' ') .join(' ')
// LocalStorage stuff
export const clearOldPersistentStates = stateToken => {
const lastStateToken = localStorage.getItem('orario.state_token')
if (lastStateToken === null || lastStateToken !== stateToken) {
localStorage.clear()
localStorage.setItem('orario.state_token', stateToken)
}
}
// Hooks
export const usePersistentState = (key, initialValue) => {
const previousValue = localStorage.getItem(`orario.${key}`)
const [value, setValue] = useState(
previousValue !== null ? JSON.parse(previousValue) : initialValue
)
useEffect(() => {
localStorage.setItem(`orario.${key}`, JSON.stringify(value))
}, [value])
return [value, setValue]
}

Loading…
Cancel
Save