82 Commits
Author SHA1 Message Date
Fran314 3ddf325c03 prototipo eventi custom 2023-10-16 10:34:51 +02:00
Fran314 973acad17a small fix on dynamic viewport height 2023-10-09 15:36:32 +02:00
Fran314 c8172ad88a Added warning messages when date outside of lecture period 2023-10-09 15:29:51 +02:00
Fran314 c0b2894c22 Merge branch 'dev' of git.phc.dm.unipi.it:phc/orario into dev 2023-10-09 14:26:22 +02:00
Fran314 fd0440465c Added date picker and fixed menu rendering 2023-10-09 14:24:49 +02:00
aziis98 32dc9a6104 fix: rimosso un console.log 2023-10-08 13:09:57 +02:00
Fran314 af41177561 reintrodotta visualizzazione lista rimossa nel commit precedente 2023-10-07 22:49:03 +02:00
Fran314 7efb311a61 Aggiunta trasposizione 2023-10-07 19:46:25 +02:00
Fran314 a34d36257b first brutal rework and first prototype 2023-08-19 03:57:46 +02:00
BachoSeven 04b6d5640d Merge pull request 'chore: update date ranges' (#5) from dev into main
Reviewed-on: phc/orario#5
2023-07-24 15:07:18 +00:00
BachoSeven 0caf967e14 chore: update date ranges 2023-07-24 17:04:40 +02:00
aziis98 291aa72fd0 Merge branch 'dev' 2023-03-05 23:17:28 +01:00
aziis98 17188b17ee fix: tolto definitivamente l'effetto hover da mobile 2023-03-05 23:17:07 +01:00
aziis98 e9d40598eb Merge pull request 'fix: aggiornato il range di date alla settimana del 2023-03-06' (#4) from dev into main
Reviewed-on: phc/orario#4
2023-03-05 22:07:33 +00:00
aziis98 9a8fa0c440 fix: aggiornato il range di date alla settimana del 2023-03-06 2023-03-05 23:05:23 +01:00
BachoSeven c831bb1715 WorkWeek: Add 18-20 slot, CSS seems to be fine. 2023-03-01 16:51:07 +01:00
aziis98 ce2f76da14 drone: now it actually works 2023-02-27 00:41:17 +01:00
aziis98 5ebc62b844 drone: testing per branch config 2023-02-27 00:40:22 +01:00
aziis98 d2f143133e fix: no hover on mobile 2023-02-27 00:29:23 +01:00
aziis98 82c21d2af9 fix: missing base url while building 2023-02-22 20:12:18 +01:00
aziis98 d4cd070cce fix: ora forse tutto funge 2023-02-22 20:05:01 +01:00
aziis98 bc1196f216 boh 2023-02-22 20:03:08 +01:00
aziis98 e77b4a19e3 boh 2023-02-22 20:02:40 +01:00
aziis98 4589cda93d fix: wrong dir again 2023-02-22 19:37:55 +01:00
aziis98 63b73ed0b3 fix: wrong folder 2023-02-22 18:55:36 +01:00
aziis98 1219eeb408 fix: testing drone 2023-02-22 18:24:10 +01:00
aziis98 15446e3cbc fix: missing command 2023-02-22 18:11:04 +01:00
aziis98 0af55b4755 fix: wrong image name 2023-02-22 18:10:14 +01:00
aziis98 4b01e91fa7 trying out drone deploy 2023-02-22 18:02:23 +01:00
aziis98 e7b145a02f fix: aggiunta un'altra patch per i nomi dei delle aule 2023-02-22 16:57:58 +01:00
aziis98 b6eb7a5ec1 fix: ehm avevo dimenticato una cosa 2023-02-03 22:28:22 +01:00
aziis98 03f9db6e76 fix: aggiornati gli id dei calendari 2023-02-03 22:18:36 +01:00
aziis98 e7f59c4693 fix: aggiunta finalmente un'icona 2023-01-24 17:27:03 +01:00
aziis98 f86edb9f6c fix: Beh che dire 2023-01-23 23:43:01 +01:00
aziis98 025a44472d fix: tutti gli orario ora sono nel range del secondo semestre 2023 2023-01-23 23:36:42 +01:00
aziis98 9f66f40984 fix: non so perché prima c'era 22 2023-01-23 23:32:18 +01:00
aziis98 9c12cbf759 chore: added new calendars 2023-01-23 23:28:59 +01:00
aziis98 4bf696ea75 feat: added analytics 2022-12-18 12:53:58 +01:00
aziis98 e432b60b54 Fixed half-hour lessons for work-week-v 2022-09-25 18:11:52 +02:00
aziis98 abb98dfbc3 Fixed grid lines again 2022-09-25 18:00:28 +02:00
aziis98 4af3529b6e Fixed grid lines offset 2022-09-25 17:54:56 +02:00
aziis98 8f9fbb2bc5 Changed dark theme colors a bit 2022-09-25 17:36:40 +02:00
BachoSeven e7f538c53c Update Help.jsx 2022-09-15 12:40:13 +02:00
BachoSeven 864a0d23da Revamp Help popup 2022-09-15 11:48:51 +02:00
aziis98 7cbbf243c4 Rimossa visualizzazione giornata dalla modalità desktop 2022-09-15 00:16:30 +02:00
aziis98 7d20ccab10 Fix: corsi con più aule, prof dei corsi, patch speciale per il lab di primo anno 2022-09-14 23:51:28 +02:00
aziis98 96ca0fef8f Added localstorage support 2022-09-14 14:55:42 +02:00
aziis98 d623df9158 Some fixes 2022-09-14 14:11:12 +02:00
aziis98 142c5e1b67 Finalizzazione nuovo layout da mobile 2022-09-14 14:00:17 +02:00
aziis98 5d8fbc9363 Missing conflicts 2022-09-14 13:31:17 +02:00
aziis98 0b1dbaebbd Merged conflicts 2022-09-14 13:30:23 +02:00
aziis98 22aa06ebf6 Some adjustments 2022-09-14 13:21:50 +02:00
aziis98 41195462f1 Merge pull request 'Added optionbar for mobile' (#2) from Fran314/orario:mobile-header into dev
Reviewed-on: phc/orario#2
2022-09-14 11:20:33 +00:00
Fran314 19389dbee2 Added optionbar for mobile 2022-09-13 15:40:52 +02:00
aziis98 4a5994210e Formatting 2022-09-13 14:42:59 +02:00
aziis98 16233adcf1 Formatting 2022-09-13 14:40:40 +02:00
aziis98 66b04ea7be Merge pull request 'Added compact grid view for the week' (#1) from Fran314/orario:compressed-week-view into dev
Reviewed-on: phc/orario#1
2022-09-13 12:37:24 +00:00
aziis98 7b1b9c2aed Merge branch 'dev' into compressed-week-view 2022-09-13 12:36:34 +00:00
Fran314 ebb78c4aff Added compact grid view for the week 2022-09-13 11:40:25 +02:00
BachoSeven 8e3d0b0155 Add hover to ScheduleView 2022-09-13 11:14:57 +02:00
BachoSeven 3b2fc34ef1 Add pointer cursor 2022-09-13 11:11:23 +02:00
aziis98 c0340c3407 Schedule view 2022-09-12 20:02:04 +02:00
aziis98 0f0bc1c52a Fiexed a bug 2022-09-12 12:03:56 +02:00
aziis98 01fa4b16e2 Refactor delle view-modes, readme and better dockerfile 2022-09-11 16:18:45 +02:00
aziis98 23c1510455 Slight adjustment to overlay buttons 2022-09-11 13:46:56 +02:00
aziis98 b76213e75d Merge branch 'main' of git.phc.dm.unipi.it:phc/orario 2022-09-11 13:37:46 +02:00
aziis98 fdeb17c90e Merged colors 2022-09-11 13:37:40 +02:00
BachoSeven d767615545 Formatting 2022-09-11 13:35:22 +02:00
aziis98 0a7331f63a Fixed a color 2022-09-11 13:34:46 +02:00
BachoSeven 85b24db785 Better selected-border colors for headerbar 2022-09-11 13:34:32 +02:00
aziis98 46d8b5b878 Updated some styles and colors 2022-09-11 13:28:27 +02:00
aziis98 c3810670b2 Some fixes 2022-09-11 13:13:59 +02:00
BachoSeven b593407915 Lighter borders for dark mode 2022-09-11 12:52:34 +02:00
BachoSeven d2526bcfa6 Slightly more readable dark mode text 2022-09-11 12:46:50 +02:00
aziis98 f90b60fbd9 Merged css and js 2022-09-10 19:49:25 +02:00
aziis98 c7c8ce9d48 Darkmode anche in mobile 2022-09-10 19:48:27 +02:00
BachoSeven 09ebad224b Add prefers-dark-theme 2022-09-10 19:42:49 +02:00
BachoSeven 0e81edfa3b Fix mobile css and links in help 2022-09-10 19:32:04 +02:00
BachoSeven 360fa42606 localstorage 2022-09-10 19:10:24 +02:00
BachoSeven 2dda03dd8a Minor fix 2022-09-10 19:06:31 +02:00
BachoSeven d5dd2a7824 Merge branch 'main' of git.phc.dm.unipi.it:phc/orario 2022-09-10 19:02:44 +02:00
BachoSeven 03ca03debc Add dark theme 2022-09-10 19:00:35 +02:00
29 changed files with 1938 additions and 63104 deletions
+46
View File
@@ -0,0 +1,46 @@
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
View File
@@ -6,3 +6,5 @@ node_modules/
.env
.env.production
.vscode/
+2 -1
View File
@@ -2,7 +2,8 @@ FROM node:18
WORKDIR /app
COPY . ./
COPY package.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "build"]
+32
View File
@@ -0,0 +1,32 @@
# Orario
Sito per visualizzare l'orario delle lezioni per i vari anni di corso.
## Usage
You need to have installed `node` and `npm` (or `pnpm`). To setup the project just run `npm install`.
### Development
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
To build the ViteJS project run `npm run build`, for deployment a `.env` files can be used to set the `BASE_URL` variable.
+10 -51
View File
@@ -1,81 +1,40 @@
[
"ALGEBRA 1",
"ANALISI ARMONICA - ANALISI ARMONICA/a",
"ANALISI MATEMATICA 1",
"ANALISI MATEMATICA 2",
"ANALISI MATEMATICA 3",
"ANALISI NUMERICA CON LABORATORIO - ANALISI NUMERICA",
"ARITMETICA",
"ASPETTI MATEMATICI NELLA COMPUTAZIONE QUANTISTICA",
"CALCOLO SCIENTIFICO",
"COMBINATORIA ALGEBRICA",
"DETERMINAZIONE ORBITALE",
"DINAMICA DEL SISTEMA SOLARE",
"DINAMICA DEL SISTEMA SOLARE",
"ELEMENTI DI GEOMETRIA ALGEBRICA",
"ELEMENTI DI GEOMETRIA ALGEBRICA",
"ELEMENTI DI GEOMETRIA ALGEBRICA",
"ELEMENTI DI GEOMETRIA ALGEBRICA",
"ELEMENTI DI TOPOLOGIA ALGEBRICA",
"ELEMENTI DI TOPOLOGIA ALGEBRICA",
"ELEMENTI DI TOPOLOGIA ALGEBRICA",
"ELEMENTI DI TOPOLOGIA ALGEBRICA",
"FISICA II",
"FISICA II",
"FISICA II",
"FISICA II",
"FISICA II",
"FISICA II",
"FISICA II",
"FISICA II",
"FONDAMENTI DI PROGRAMMAZIONE CON LABORATORIO - FONDAMENTI DI PROGRAMMAZIONE ",
"GEOMETRIA 1",
"GEOMETRIA 2 - GEOMETRIA 2 A",
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
"ISTITUZIONI DI ALGEBRA",
"INGLESE SCIENTIFICO",
"ISTITUZIONI DI ALGEBRA",
"ISTITUZIONI DI ANALISI MATEMATICA",
"ISTITUZIONI DI ANALISI MATEMATICA",
"ISTITUZIONI DI ANALISI MATEMATICA",
"ISTITUZIONI DI DIDATTICA DELLA MATEMATICA",
"ISTITUZIONI DI DIDATTICA DELLA MATEMATICA",
"ISTITUZIONI DI DIDATTICA DELLA MATEMATICA",
"ISTITUZIONI DI FISICA MATEMATICA",
"ISTITUZIONI DI FISICA MATEMATICA",
"LABORATORIO COMPUTAZIONALE",
"LOGICA MATEMATICA",
"LOGICA MATEMATICA",
"LOGICA MATEMATICA",
"LABORATORIO DI INTRODUZIONE ALLA MATEMATICA COMPUTAZIONALE",
"LOGICA MATEMATICA",
"MECCANICA SUPERIORE - MECCANICA SUPERIORE/a",
"MECCANICA SUPERIORE - MECCANICA SUPERIORE/a",
"METODI NUMERICI PER CATENE DI MARKOV - METODI NUMERICI PER CATENE DI MARKOV/a",
"METODI NUMERICI PER CATENE DI MARKOV - METODI NUMERICI PER CATENE DI MARKOV/a",
"METODI NUMERICI PER LA GRAFICA - METODI NUMERICI PER LA GRAFICA/a",
"METODI NUMERICI PER LA GRAFICA - METODI NUMERICI PER LA GRAFICA/a",
"PROBABILITÀ",
"PROBABILITÀ",
"PROBABILITÀ",
"PROBABILITÀ",
"PROBABILITÀ",
"PROBABILITÀ",
"RICERCA OPERATIVA",
"RICERCA OPERATIVA",
"RICERCA OPERATIVA",
"RICERCA OPERATIVA",
"RICERCA OPERATIVA",
"RICERCA OPERATIVA",
"SISTEMI DINAMICI",
"SISTEMI DINAMICI",
"SISTEMI DINAMICI",
"SISTEMI DINAMICI",
"STORIA DELLA MATEMATICA",
"STORIA DELLA MATEMATICA",
"STORIA DELLA MATEMATICA",
"STORIA DELLA MATEMATICA",
"TECNOLOGIE PER LA DIDATTICA",
"TECNOLOGIE PER LA DIDATTICA",
"TEORIA ANALITICA DEI NUMERI A - TEORIA ANALITICA DEI NUMERI A/a",
"TEORIA ANALITICA DEI NUMERI A - TEORIA ANALITICA DEI NUMERI A/a",
"TEORIA E METODI DELL'OTTIMIZZAZIONE",
"TEORIA E METODI DELL'OTTIMIZZAZIONE",
"TOPOLOGIA DIFFERENZIALE - TOPOLOGIA DIFFERENZIALE/a",
"TOPOLOGIA DIFFERENZIALE - TOPOLOGIA DIFFERENZIALE/a"
]
-61819
View File
File diff suppressed because it is too large Load Diff
+22 -18
View File
@@ -1,24 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Orario DM Unipi</title>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Orario DM Unipi</title>
<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:type" content="website" />
<meta property="og:url" content="https://lab.phc.dm.unipi.it/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:type" content="website" />
<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.gstatic.com" crossorigin>
<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="preconnect" href="https://fonts.googleapis.com" />
<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 rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,1,0" />
<link rel="stylesheet" href="/src/styles/main.scss">
</head>
<body>
<script type="module" src="/src/main.jsx"></script>
</body>
<link rel="icon" type="image/png" href="icon.png" />
<link rel="stylesheet" href="/src/styles/main.scss" />
<script data-goatcounter="https://analytics.phc.dm.unipi.it/count" async src="//analytics.phc.dm.unipi.it/count.js"></script>
</head>
<body>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
+2 -1
View File
@@ -16,7 +16,8 @@
"lodash-es": "^4.17.21",
"preact": "^10.10.6",
"sass": "^1.54.8",
"vite": "^3.0.9"
"vite": "^3.0.9",
"yaml": "^2.3.2"
},
"devDependencies": {
"@babel/core": "^7.18.13",
+175 -157
View File
@@ -1,30 +1,43 @@
lockfileVersion: 5.4
lockfileVersion: '6.0'
specifiers:
'@babel/core': ^7.18.13
'@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
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
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_sass@1.54.8
date-fns:
specifier: ^2.29.2
version: 2.29.2
lodash:
specifier: ^4.17.21
version: 4.17.21
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)
yaml:
specifier: ^2.3.2
version: 2.3.2
devDependencies:
'@babel/core': 7.18.13
'@preact/preset-vite': 2.3.0_wibdudaz52xr4lhcmpvnys3x3u
'@babel/core':
specifier: ^7.18.13
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:
/@ampproject/remapping/2.2.0:
/@ampproject/remapping@2.2.0:
resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==}
engines: {node: '>=6.0.0'}
dependencies:
@@ -32,26 +45,26 @@ packages:
'@jridgewell/trace-mapping': 0.3.15
dev: true
/@babel/code-frame/7.18.6:
/@babel/code-frame@7.18.6:
resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/highlight': 7.18.6
dev: true
/@babel/compat-data/7.18.13:
/@babel/compat-data@7.18.13:
resolution: {integrity: sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/core/7.18.13:
/@babel/core@7.18.13:
resolution: {integrity: sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==}
engines: {node: '>=6.9.0'}
dependencies:
'@ampproject/remapping': 2.2.0
'@babel/code-frame': 7.18.6
'@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/helpers': 7.18.9
'@babel/parser': 7.18.13
@@ -67,7 +80,7 @@ packages:
- supports-color
dev: true
/@babel/generator/7.18.13:
/@babel/generator@7.18.13:
resolution: {integrity: sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==}
engines: {node: '>=6.9.0'}
dependencies:
@@ -76,14 +89,14 @@ packages:
jsesc: 2.5.2
dev: true
/@babel/helper-annotate-as-pure/7.18.6:
/@babel/helper-annotate-as-pure@7.18.6:
resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.13
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==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -96,12 +109,12 @@ packages:
semver: 6.3.0
dev: true
/@babel/helper-environment-visitor/7.18.9:
/@babel/helper-environment-visitor@7.18.9:
resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-function-name/7.18.9:
/@babel/helper-function-name@7.18.9:
resolution: {integrity: sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==}
engines: {node: '>=6.9.0'}
dependencies:
@@ -109,21 +122,21 @@ packages:
'@babel/types': 7.18.13
dev: true
/@babel/helper-hoist-variables/7.18.6:
/@babel/helper-hoist-variables@7.18.6:
resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.13
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==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.13
dev: true
/@babel/helper-module-transforms/7.18.9:
/@babel/helper-module-transforms@7.18.9:
resolution: {integrity: sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==}
engines: {node: '>=6.9.0'}
dependencies:
@@ -139,41 +152,41 @@ packages:
- supports-color
dev: true
/@babel/helper-plugin-utils/7.18.9:
/@babel/helper-plugin-utils@7.18.9:
resolution: {integrity: sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-simple-access/7.18.6:
/@babel/helper-simple-access@7.18.6:
resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.13
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==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.13
dev: true
/@babel/helper-string-parser/7.18.10:
/@babel/helper-string-parser@7.18.10:
resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-validator-identifier/7.18.6:
/@babel/helper-validator-identifier@7.18.6:
resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-validator-option/7.18.6:
/@babel/helper-validator-option@7.18.6:
resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helpers/7.18.9:
/@babel/helpers@7.18.9:
resolution: {integrity: sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==}
engines: {node: '>=6.9.0'}
dependencies:
@@ -184,7 +197,7 @@ packages:
- supports-color
dev: true
/@babel/highlight/7.18.6:
/@babel/highlight@7.18.6:
resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==}
engines: {node: '>=6.9.0'}
dependencies:
@@ -193,7 +206,7 @@ packages:
js-tokens: 4.0.0
dev: true
/@babel/parser/7.18.13:
/@babel/parser@7.18.13:
resolution: {integrity: sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -201,7 +214,7 @@ packages:
'@babel/types': 7.18.13
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==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -211,17 +224,17 @@ packages:
'@babel/helper-plugin-utils': 7.18.9
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==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@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
/@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==}
engines: {node: '>=6.9.0'}
peerDependencies:
@@ -231,11 +244,11 @@ packages:
'@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-module-imports': 7.18.6
'@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
dev: true
/@babel/template/7.18.10:
/@babel/template@7.18.10:
resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==}
engines: {node: '>=6.9.0'}
dependencies:
@@ -244,7 +257,7 @@ packages:
'@babel/types': 7.18.13
dev: true
/@babel/traverse/7.18.13:
/@babel/traverse@7.18.13:
resolution: {integrity: sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==}
engines: {node: '>=6.9.0'}
dependencies:
@@ -262,7 +275,7 @@ packages:
- supports-color
dev: true
/@babel/types/7.18.13:
/@babel/types@7.18.13:
resolution: {integrity: sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==}
engines: {node: '>=6.9.0'}
dependencies:
@@ -271,7 +284,7 @@ packages:
to-fast-properties: 2.0.0
dev: true
/@esbuild/linux-loong64/0.14.54:
/@esbuild/linux-loong64@0.14.54:
resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==}
engines: {node: '>=12'}
cpu: [loong64]
@@ -279,7 +292,7 @@ packages:
requiresBuild: true
optional: true
/@jridgewell/gen-mapping/0.1.1:
/@jridgewell/gen-mapping@0.1.1:
resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
engines: {node: '>=6.0.0'}
dependencies:
@@ -287,7 +300,7 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@jridgewell/gen-mapping/0.3.2:
/@jridgewell/gen-mapping@0.3.2:
resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
engines: {node: '>=6.0.0'}
dependencies:
@@ -296,53 +309,53 @@ packages:
'@jridgewell/trace-mapping': 0.3.15
dev: true
/@jridgewell/resolve-uri/3.1.0:
/@jridgewell/resolve-uri@3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/set-array/1.1.2:
/@jridgewell/set-array@1.1.2:
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec/1.4.14:
/@jridgewell/sourcemap-codec@1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
dev: true
/@jridgewell/trace-mapping/0.3.15:
/@jridgewell/trace-mapping@0.3.15:
resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==}
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@preact/preset-vite/2.3.0_wibdudaz52xr4lhcmpvnys3x3u:
/@preact/preset-vite@2.3.0(@babel/core@7.18.13)(preact@10.10.6)(vite@3.0.9):
resolution: {integrity: sha512-0kOuz7wdrQLqrPlyI/Ypw9IWDF2++GGcOHMRBYO5T2w2+dheelaBH+XrIN/okqdsGIflzFIFNyIGubo5BC8wbQ==}
peerDependencies:
'@babel/core': 7.x
vite: 2.x || 3.x
dependencies:
'@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
'@prefresh/vite': 2.2.8_preact@10.10.6+vite@3.0.9
'@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)
'@prefresh/vite': 2.2.8(preact@10.10.6)(vite@3.0.9)
'@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
kolorist: 1.5.1
resolve: 1.22.1
vite: 3.0.9_sass@1.54.8
vite: 3.0.9(sass@1.54.8)
transitivePeerDependencies:
- preact
- supports-color
dev: true
/@prefresh/babel-plugin/0.4.3:
/@prefresh/babel-plugin@0.4.3:
resolution: {integrity: sha512-fYAWbU1WDSLn108kKY4eDaaeUcnszFqXjgaGKYXNZ5NLulpRTpsrY+Sbfo9q8LDpWrBpqIgzjrwNnvglWI1xNQ==}
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==}
peerDependencies:
preact: ^10.0.0
@@ -350,11 +363,11 @@ packages:
preact: 10.10.6
dev: true
/@prefresh/utils/1.1.3:
/@prefresh/utils@1.1.3:
resolution: {integrity: sha512-Mb9abhJTOV4yCfkXrMrcgFiFT7MfNOw8sDa+XyZBdq/Ai2p4Zyxqsb3EgHLOEdHpMj6J9aiZ54W8H6FTam1u+A==}
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==}
peerDependencies:
preact: ^10.4.0
@@ -362,16 +375,16 @@ packages:
dependencies:
'@babel/core': 7.18.13
'@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
'@rollup/pluginutils': 4.2.1
preact: 10.10.6
vite: 3.0.9_sass@1.54.8
vite: 3.0.9(sass@1.54.8)
transitivePeerDependencies:
- supports-color
dev: true
/@rollup/pluginutils/4.2.1:
/@rollup/pluginutils@4.2.1:
resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
engines: {node: '>= 8.0.0'}
dependencies:
@@ -379,21 +392,21 @@ packages:
picomatch: 2.3.1
dev: true
/ansi-styles/3.2.1:
/ansi-styles@3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
dependencies:
color-convert: 1.9.3
dev: true
/anymatch/3.1.2:
/anymatch@3.1.2:
resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
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==}
peerDependencies:
'@babel/core': ^7.12.10
@@ -401,17 +414,17 @@ packages:
'@babel/core': 7.18.13
dev: true
/binary-extensions/2.2.0:
/binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
/braces/3.0.2:
/braces@3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
/browserslist/4.21.3:
/browserslist@4.21.3:
resolution: {integrity: sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
@@ -419,14 +432,14 @@ packages:
caniuse-lite: 1.0.30001388
electron-to-chromium: 1.4.241
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
/caniuse-lite/1.0.30001388:
/caniuse-lite@1.0.30001388:
resolution: {integrity: sha512-znVbq4OUjqgLxMxoNX2ZeeLR0d7lcDiE5uJ4eUiWdml1J1EkxbnQq6opT9jb9SMfJxB0XA16/ziHwni4u1I3GQ==}
dev: true
/chalk/2.4.2:
/chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
dependencies:
@@ -435,7 +448,7 @@ packages:
supports-color: 5.5.0
dev: true
/chokidar/3.5.3:
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
dependencies:
@@ -449,28 +462,28 @@ packages:
optionalDependencies:
fsevents: 2.3.2
/color-convert/1.9.3:
/color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
color-name: 1.1.3
dev: true
/color-name/1.1.3:
/color-name@1.1.3:
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
dev: true
/convert-source-map/1.8.0:
/convert-source-map@1.8.0:
resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}
dependencies:
safe-buffer: 5.1.2
dev: true
/date-fns/2.29.2:
/date-fns@2.29.2:
resolution: {integrity: sha512-0VNbwmWJDS/G3ySwFSJA3ayhbURMTJLtwM2DTxf9CWondCnh6DTNlO9JgRSq6ibf4eD0lfMJNBxUdEAHHix+bA==}
engines: {node: '>=0.11'}
dev: false
/debug/4.3.4:
/debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
@@ -482,11 +495,11 @@ packages:
ms: 2.1.2
dev: true
/electron-to-chromium/1.4.241:
/electron-to-chromium@1.4.241:
resolution: {integrity: sha512-e7Wsh4ilaioBZ5bMm6+F4V5c11dh56/5Jwz7Hl5Tu1J7cnB+Pqx5qIF2iC7HPpfyQMqGSvvLP5bBAIDd2gAtGw==}
dev: true
/esbuild-android-64/0.14.54:
/esbuild-android-64@0.14.54:
resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
engines: {node: '>=12'}
cpu: [x64]
@@ -494,7 +507,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-android-arm64/0.14.54:
/esbuild-android-arm64@0.14.54:
resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==}
engines: {node: '>=12'}
cpu: [arm64]
@@ -502,7 +515,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-darwin-64/0.14.54:
/esbuild-darwin-64@0.14.54:
resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==}
engines: {node: '>=12'}
cpu: [x64]
@@ -510,7 +523,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-darwin-arm64/0.14.54:
/esbuild-darwin-arm64@0.14.54:
resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==}
engines: {node: '>=12'}
cpu: [arm64]
@@ -518,7 +531,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-freebsd-64/0.14.54:
/esbuild-freebsd-64@0.14.54:
resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==}
engines: {node: '>=12'}
cpu: [x64]
@@ -526,7 +539,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-freebsd-arm64/0.14.54:
/esbuild-freebsd-arm64@0.14.54:
resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==}
engines: {node: '>=12'}
cpu: [arm64]
@@ -534,7 +547,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-linux-32/0.14.54:
/esbuild-linux-32@0.14.54:
resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==}
engines: {node: '>=12'}
cpu: [ia32]
@@ -542,7 +555,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-linux-64/0.14.54:
/esbuild-linux-64@0.14.54:
resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==}
engines: {node: '>=12'}
cpu: [x64]
@@ -550,15 +563,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-linux-arm/0.14.54:
resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
optional: true
/esbuild-linux-arm64/0.14.54:
/esbuild-linux-arm64@0.14.54:
resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==}
engines: {node: '>=12'}
cpu: [arm64]
@@ -566,7 +571,15 @@ packages:
requiresBuild: true
optional: true
/esbuild-linux-mips64le/0.14.54:
/esbuild-linux-arm@0.14.54:
resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
optional: true
/esbuild-linux-mips64le@0.14.54:
resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==}
engines: {node: '>=12'}
cpu: [mips64el]
@@ -574,7 +587,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-linux-ppc64le/0.14.54:
/esbuild-linux-ppc64le@0.14.54:
resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==}
engines: {node: '>=12'}
cpu: [ppc64]
@@ -582,7 +595,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-linux-riscv64/0.14.54:
/esbuild-linux-riscv64@0.14.54:
resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==}
engines: {node: '>=12'}
cpu: [riscv64]
@@ -590,7 +603,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-linux-s390x/0.14.54:
/esbuild-linux-s390x@0.14.54:
resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==}
engines: {node: '>=12'}
cpu: [s390x]
@@ -598,7 +611,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-netbsd-64/0.14.54:
/esbuild-netbsd-64@0.14.54:
resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==}
engines: {node: '>=12'}
cpu: [x64]
@@ -606,7 +619,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-openbsd-64/0.14.54:
/esbuild-openbsd-64@0.14.54:
resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==}
engines: {node: '>=12'}
cpu: [x64]
@@ -614,7 +627,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-sunos-64/0.14.54:
/esbuild-sunos-64@0.14.54:
resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==}
engines: {node: '>=12'}
cpu: [x64]
@@ -622,7 +635,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-windows-32/0.14.54:
/esbuild-windows-32@0.14.54:
resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==}
engines: {node: '>=12'}
cpu: [ia32]
@@ -630,7 +643,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-windows-64/0.14.54:
/esbuild-windows-64@0.14.54:
resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==}
engines: {node: '>=12'}
cpu: [x64]
@@ -638,7 +651,7 @@ packages:
requiresBuild: true
optional: true
/esbuild-windows-arm64/0.14.54:
/esbuild-windows-arm64@0.14.54:
resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==}
engines: {node: '>=12'}
cpu: [arm64]
@@ -646,7 +659,7 @@ packages:
requiresBuild: true
optional: true
/esbuild/0.14.54:
/esbuild@0.14.54:
resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==}
engines: {node: '>=12'}
hasBin: true
@@ -674,147 +687,147 @@ packages:
esbuild-windows-64: 0.14.54
esbuild-windows-arm64: 0.14.54
/escalade/3.1.1:
/escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
dev: true
/escape-string-regexp/1.0.5:
/escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
dev: true
/estree-walker/2.0.2:
/estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
/fill-range/7.0.1:
/fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
/fsevents/2.3.2:
/fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
optional: true
/function-bind/1.1.1:
/function-bind@1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
/gensync/1.0.0-beta.2:
/gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
dev: true
/glob-parent/5.1.2:
/glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.3
/globals/11.12.0:
/globals@11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
dev: true
/has-flag/3.0.0:
/has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
dev: true
/has/1.0.3:
/has@1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
dependencies:
function-bind: 1.1.1
/immutable/4.1.0:
/immutable@4.1.0:
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==}
engines: {node: '>=8'}
dependencies:
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==}
dependencies:
has: 1.0.3
/is-extglob/2.1.1:
/is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
/is-glob/4.0.3:
/is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
/is-number/7.0.0:
/is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
/js-tokens/4.0.0:
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: true
/jsesc/2.5.2:
/jsesc@2.5.2:
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
engines: {node: '>=4'}
hasBin: true
dev: true
/json5/2.2.1:
/json5@2.2.1:
resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==}
engines: {node: '>=6'}
hasBin: true
dev: true
/kolorist/1.5.1:
/kolorist@1.5.1:
resolution: {integrity: sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ==}
dev: true
/lodash-es/4.17.21:
/lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
dev: false
/lodash/4.17.21:
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: false
/ms/2.1.2:
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/nanoid/3.3.4:
/nanoid@3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
/node-releases/2.0.6:
/node-releases@2.0.6:
resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
dev: true
/normalize-path/3.0.0:
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
/path-parse/1.0.7:
/path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
/picocolors/1.0.0:
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
/picomatch/2.3.1:
/picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
/postcss/8.4.16:
/postcss@8.4.16:
resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
@@ -822,16 +835,16 @@ packages:
picocolors: 1.0.0
source-map-js: 1.0.2
/preact/10.10.6:
/preact@10.10.6:
resolution: {integrity: sha512-w0mCL5vICUAZrh1DuHEdOWBjxdO62lvcO++jbzr8UhhYcTbFkpegLH9XX+7MadjTl/y0feoqwQ/zAnzkc/EGog==}
/readdirp/3.6.0:
/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
/resolve/1.22.1:
/resolve@1.22.1:
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
hasBin: true
dependencies:
@@ -839,18 +852,18 @@ packages:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
/rollup/2.77.3:
/rollup@2.77.3:
resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
engines: {node: '>=10.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
/safe-buffer/5.1.2:
/safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: true
/sass/1.54.8:
/sass@1.54.8:
resolution: {integrity: sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==}
engines: {node: '>=12.0.0'}
hasBin: true
@@ -859,38 +872,38 @@ packages:
immutable: 4.1.0
source-map-js: 1.0.2
/semver/6.3.0:
/semver@6.3.0:
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
hasBin: true
dev: true
/source-map-js/1.0.2:
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
/supports-color/5.5.0:
/supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
dependencies:
has-flag: 3.0.0
dev: true
/supports-preserve-symlinks-flag/1.0.0:
/supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
/to-fast-properties/2.0.0:
/to-fast-properties@2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'}
dev: true
/to-regex-range/5.0.1:
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
dependencies:
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==}
hasBin: true
peerDependencies:
@@ -901,7 +914,7 @@ packages:
picocolors: 1.0.0
dev: true
/vite/3.0.9_sass@1.54.8:
/vite@3.0.9(sass@1.54.8):
resolution: {integrity: sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
@@ -927,3 +940,8 @@ packages:
sass: 1.54.8
optionalDependencies:
fsevents: 2.3.2
/yaml@2.3.2:
resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==}
engines: {node: '>= 14'}
dev: false
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

-25
View File
@@ -1,25 +0,0 @@
// import { createContext } from 'preact'
// import { toChildArray } from 'preact'
// import { useContext, useState } from 'preact/hooks'
// const AnimationContext = createContext()
// export const useAnimation = animation => {
// const [styles, setStyles] = useState({})
// const start = () => {
// setStyles({ animation })
// }
// return [
// start,
// {
// styles,
// onAnimationEnd: e => {
// console.log('animation end')
// },
// },
// ]
// }
// export const Animation = ({ animation, children }) => {}
+3 -13
View File
@@ -1,28 +1,18 @@
import { useRef, useState } from 'preact/hooks'
import { Icon } from './Icon.jsx'
let ids = 0
function generateId() {
return 'combo-id-' + ids++
}
export const ComboBox = ({ options, value, setValue }) => {
const [uid] = useState(() => generateId())
const selectRef = useRef()
return (
<div class="input-combo">
<select ref={selectRef} id={uid} onInput={e => setValue(e.target.value)}>
<select onInput={e => setValue(e.target.value)}>
{options.map(option => (
<option value={option.value} selected={option.value === value}>
{option.label}
</option>
))}
</select>
<label class="icon" for={uid}>
<div class="icon">
<Icon name="expand_more" />
</label>
</div>
</div>
)
}
+5 -1
View File
@@ -5,7 +5,11 @@ export const CompoundButton = ({ options, value, setValue }) => {
<div class="compound">
{options.map(option => (
<button
class={withClasses(['radio', option.value === value && 'selected'])}
class={withClasses([
'radio',
option.value === value && 'selected',
option.icon && 'icon',
])}
onClick={() => setValue(option.value)}
>
{option.label}
-12
View File
@@ -1,12 +0,0 @@
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>
)
+26
View File
@@ -0,0 +1,26 @@
import { useRef } from 'preact/hooks'
import { Icon } from './Icon.jsx'
export const DatePicker = ({ date, setDate }) => {
const input = useRef()
const [year, month, day] = date.split('T')[0].split('-')
return (
<div class="date-picker" onClick={() => 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>
)
}
+35 -369
View File
@@ -1,376 +1,42 @@
//
// Modes
//
import { Courses } from './view/Courses.jsx'
import { Schedule } from './view/Schedule.jsx'
import { differenceInMinutes, format, startOfDay } from 'date-fns'
import _ from 'lodash'
import { useEffect, useRef, useState } from 'preact/hooks'
import { layoutIntervals } from '../interval-layout.js'
const WEEK_DAYS = ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato']
function hashString(str, seed = 0) {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i)
h1 = Math.imul(h1 ^ ch, 2654435761)
h2 = Math.imul(h2 ^ ch, 1597334677)
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909)
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909)
return 4294967296 * (2097151 & h2) + (h1 >>> 0)
}
function normalizeCourseName(name) {
return name
.split(' ')
.map(word => word.toLowerCase())
.map(word => {
if (word.trim().length === 0) return word
return /(^del|^nel|^di$|^dei$|^con$|^alla$|^per$|^e$|^la$)/.test(word)
? word
: word[0].toUpperCase() + word.slice(1)
})
.join(' ')
.replaceAll('Ii', 'II')
.replaceAll('Iii', 'III')
.replaceAll(/'(.)/g, ({}, letter) => "'" + letter.toUpperCase())
}
const WorkWeekView = ({ 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.name)
) %
360) +
'deg',
}}
ref={eventRef}
>
{normalizeCourseName(event.data.name)}
</div>
)
}
return <Local />
})
)}
</div>
))}
</div>
</div>
</div>
)
}
const WorkWeekVerticalView = ({ events, selection, setSelection, hideOtherCourses }) => {
const selectionSet = new Set(selection)
// const base = {
// 1: [],
// 2: [],
// 3: [],
// 4: [],
// 5: [],
// }
const eventsByWeekday = _.groupBy(
!hideOtherCourses ? events : events.filter(e => selectionSet.has(e.name)),
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.name
? ' highlight'
: '') +
(selectionSet.has(event.data.name) ? ' selected' : '')
}
data-event-id={event.data.name}
style={{
'--start': event.start / 60 - 7,
'--stack': stackIndex + 1,
'--size': (event.end - event.start) / 60,
'--hue':
(Math.abs(hashString('seed3' + event.data.name)) %
360) +
'deg',
}}
onClick={() => {
if (!selectionSet.has(event.data.name))
setSelection([...selection, event.data.name])
else
setSelection(
selection.filter(
name => event.data.name !== name
)
)
}}
>
<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>
)
}
const CourseView = ({ events, selection, setSelection, hideOtherCourses }) => {
const selectionSet = new Set(selection)
const visibleEvents = !hideOtherCourses ? events : events.filter(e => selectionSet.has(e.name))
const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'name'), 'name')
const [currentlyHovered, setCurrentlyHovered] = useState(null)
const element = useRef()
useEffect(() => {
if (element.current) {
const l = e => {
const $course = e.target.closest('.course')
if ($course) {
setCurrentlyHovered($course.dataset.courseId)
} 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="course-view" ref={element}>
<div class="wrap-container">
{Object.entries(eventsByCourse).map(([name, courseEvents]) => (
<div
class={
'course' +
(currentlyHovered === name ? ' highlight' : '') +
(selectionSet.has(name) ? ' selected' : '')
}
data-course-id={name}
onClick={() => {
if (!selectionSet.has(name)) setSelection([...selection, name])
else setSelection(selection.filter(n => n !== name))
}}
>
<div class="title">{normalizeCourseName(name)}</div>
<div class="docenti">{courseEvents[0].docenti.join(', ')}</div>
<div class="events">
{courseEvents.map(course => (
<div>
{WEEK_DAYS[course.start.getDay()]}{' '}
{format(course.start, 'H:mm')} &ndash;
{format(course.end, 'H:mm')} {course.aula}
</div>
))}
</div>
</div>
))}
</div>
</div>
)
}
//
// EventsView
//
export const MODE_COURSES = 'course'
export const MODE_WORKWEEK = 'work-week'
export const MODE_SCHEDULE = 'schedule'
export const MODE_WORKWEEK_GRID = 'work-week-grid'
export const MODE_WORKWEEK_TRANSPOSE = 'work-week-transpose'
const viewModeMap = {
'work-week-h': WorkWeekView,
'work-week-v': WorkWeekVerticalView,
'course': CourseView,
[MODE_COURSES]: Courses,
[MODE_SCHEDULE]: Schedule,
}
export const EventsView = ({ mode, ...viewProps }) => {
const Mode = viewModeMap[mode]
// export const EventsView = ({ mode, ...viewProps }) => {
// const Mode = viewModeMap[mode]
//
// return (
// <div class="events-view">
// <Mode {...viewProps} />
// </div>
// )
// }
return (
<div class="events-view">
<Mode {...viewProps} />
</div>
)
export const EventsView = ({ mode, source, ...viewProps }) => {
// const Mode = viewModeMap[mode]
if (source === 'orario') {
return (
<div class="events-view">
<Schedule source={source} {...viewProps} />
</div>
)
} else {
return (
<div class="events-view">
<Courses source={source} {...viewProps} />
</div>
)
}
}
+8 -37
View File
@@ -1,46 +1,17 @@
import { ComboBox } from './ComboBox.jsx'
import {
MODE_COURSES,
MODE_SCHEDULE,
MODE_WORKWEEK,
MODE_WORKWEEK_GRID,
} from './EventsView.jsx'
import { DatePicker } from './DatePicker.jsx'
import { Help } from './Help.jsx'
import { Icon } from './Icon.jsx'
export const HamburgerMenu = ({ onClose, mode, setMode, source, setSource }) => {
export const HamburgerMenu = ({ date, setDate, onClose, theme, setTheme }) => {
return (
<div class="menu">
<div class="header">
<button class="flat icon" onClick={onClose}>
<Icon name="close" />
</button>
<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: 'course', label: 'Corsi' },
{ value: 'work-week-v', label: 'Settimana' },
// {
// value: 'work-week-h',
// label: 'Settimana (Trasposto)',
// },
]}
/>
</div>
<hr />
<div class="help">
<h2>
<Icon name="info" />
+24 -15
View File
@@ -2,38 +2,47 @@ import { Icon } from './Icon.jsx'
export const Help = ({}) => (
<>
<h3>Selezione Corsi</h3>
<h3>Visualizzazione Corsi</h3>
<p>
Per visualizzare solo una selezione dei corsi disponibili, clicca su quelli che ti
interessano e poi nascondi gli altri con il tasto <Icon name="visibility" /> che
comparirà in basso a destra.
Per visualizzare i corsi che ti interessano nella tabella
dell'orario, devi prima selezionarli. Puoi selezionare dei corsi
cliccandoli, cercandoli nelle sezioni Primo anno (<b>I</b>), Secondo
anno (<b>II</b>), Terzo anno (<b>III</b>), Magistrale (<b>M</b>), o
Tutti.
</p>
<p>
Per vedere l'orario settimanale, clicca su <b>Settimana</b>, che mostrerà un calendario
con i corsi attualmente selezionati.
Una volta compiuta la selezione, è possibile vedere la tabella delle
lezioni andando nella visualizzazione Orario (
<Icon name="calendar_view_month" />)
</p>
<p>
La selezione effettuata verrà preservata dalla visualizzazione <b>Corsi</b> a quella
Settimana, permettendo di individuare eventuali sovrapposizioni di orari.
Per via di eventuali preferenze personali, è possibile cambiare
l'orientazione della tabella Orario trasponendola, utilizzando il
pulsante Trasponi (
<Icon name="switch_left" style="transform: rotate(-45deg)" />)
</p>
<p>
I corsi sono raggruppati per: primo, secondo, terzo anno (<b>I</b>,<b>II</b>,<b>III</b>)
e magistrale. Alternativamente, puoi scegliere fra tutti i corsi disponibili cliccando
su <b>Tutti</b>.
È anche possibile visualizzare in uno specchietto riassuntivo
soltanto i corsi selezionati, andando nella visualizzazione Lista (
<Icon name="list" />)
</p>
<h3>Stampa</h3>
<p>
Da desktop puoi stampare l'orario attualmente visibile con il bottone <Icon name="print" /> (è
consigliato controllare le opzioni di stampa per un risultato soddisfacente).
Da desktop puoi stampare l'orario attualmente visibile con il
bottone <Icon name="print" /> (è consigliato controllare le opzioni
di stampa per ottenere un risultato soddisfacente).
</p>
<h3>Bug &amp; Contatti</h3>
<p>
In caso di bug puoi creare una issue sulla repo git del progetto{' '}
<a href="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>{' '}
oppure puoi contattarci direttamente all'indirizzo{' '}
<a href="mailto:macchinisti@lists.dm.unipi.it">macchinisti@lists.dm.unipi.it</a>.
oppure passare direttamente in PHC.
</p>
</>
)
+40
View File
@@ -0,0 +1,40 @@
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>
)
}
+12 -8
View File
@@ -9,17 +9,21 @@ export const Popup = ({ title, children, onClose }) => {
class="popup-container"
ref={popupLeaveRegionRef}
onClick={e =>
popupLeaveRegionRef.current && popupLeaveRegionRef.current === e.target && onClose()
popupLeaveRegionRef.current &&
popupLeaveRegionRef.current === e.target &&
onClose()
}
>
<div class="popup">
<div class="header">
<div class="title">{title}</div>
<button class="flat icon" onClick={onClose}>
<Icon name="close" />
</button>
<div class="popup-wrapper">
<div class="popup">
<div class="header">
<div class="title">{title}</div>
<button class="flat icon" onClick={onClose}>
<Icon name="close" />
</button>
</div>
<div class="content">{children}</div>
</div>
<div class="content">{children}</div>
</div>
</div>
)
+32
View File
@@ -0,0 +1,32 @@
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>
<button
class="icon"
onClick={() =>
setTheme(theme === 'dark' ? 'light' : 'dark')
}
>
<Icon
name="add"
/>
</button>
<DatePicker date={date} setDate={setDate} />
</div>
</div>
)
}
+13
View File
@@ -0,0 +1,13 @@
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>
)
+42 -19
View File
@@ -1,16 +1,31 @@
import { CompoundButton } from './CompoundButton.jsx'
import { DatePicker } from './DatePicker.jsx'
import { Icon } from './Icon.jsx'
export const Toolbar = ({ mode, setMode, source, setSource, onShowMenu, onHelp }) => {
export const Toolbar = ({
source,
setSource,
date,
setDate,
showMobileMenu,
setShowMobileMenu,
onHelp,
theme,
setTheme,
}) => {
return (
<div class="toolbar">
<div class="mobile">
<button class="flat icon" onClick={onShowMenu}>
<Icon name="menu" />
<button
class="flat icon"
onClick={() => setShowMobileMenu(!showMobileMenu)}
>
<Icon name={`${showMobileMenu ? 'close' : 'menu'}`} />
</button>
</div>
<div class="item logo">
<img src="logo-circuit-board.svg" alt="logo" /> / <span>Orario</span>
<img src="logo-circuit-board.svg" alt="logo" /> /{' '}
<span>Orario</span>
</div>
<div class="option-group">
<div class="item option">
@@ -21,37 +36,45 @@ export const Toolbar = ({ mode, setMode, source, setSource, onShowMenu, onHelp }
{ value: 'anno-3', label: 'III' },
{ value: 'magistrale', label: 'Magistrale' },
{ value: 'tutti', label: 'Tutti' },
{ value: 'custom', label: 'Custom' },
]}
value={source}
setValue={setSource}
/>
</div>
</div>
<div class="option-group">
<div class="item option">
<CompoundButton
options={[
{ value: 'course', label: 'Corsi' },
{ value: 'work-week-v', label: 'Settimana' },
// {
// value: 'work-week-h',
// label: (
// <>
// <Icon name="warning" />
// Settimana<sup>T</sup>
// </>
// ),
// },
{ value: 'orario', label: 'Orario' },
{ value: 'lista', label: 'Lista' },
]}
value={mode}
setValue={setMode}
value={source}
setValue={setSource}
/>
</div>
<div class="empty"></div>
<div class="item option">
<DatePicker date={date} setDate={setDate} />
</div>
</div>
<div class="option-group">
<div class="item option">
<button class="icon" onClick={() => window.print()}>
<Icon name="print" />
</button>
</div>
<div class="item option">
<button
class="icon"
onClick={() =>
setTheme(theme === 'dark' ? 'light' : 'dark')
}
>
<Icon
name={theme === 'dark' ? 'dark_mode' : 'light_mode'}
/>
</button>
</div>
<div class="item option">
<button class="icon" onClick={onHelp}>
<Icon name="question_mark" />
+116
View File
@@ -0,0 +1,116 @@
import { format } from 'date-fns'
import _ from 'lodash'
import { useEffect, useRef, useState } from 'preact/hooks'
import { parseCustomEvents, prettyCourseName, WEEK_DAYS } from '../../utils.jsx'
import { Icon } from '../Icon.jsx'
export const Courses = ({
source,
timetables,
selection,
setSelection,
custom,
isRestrictedList,
}) => {
const events = isRestrictedList ? timetables['tutti'] : timetables[source]
const selectionSet = new Set(selection)
console.log(events)
const visibleEvents = isRestrictedList
? events
.filter(e => selectionSet.has(e.id))
.concat(parseCustomEvents(custom))
: events
const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'id'), 'id')
const profsPerCourse = _.mapValues(eventsByCourse, events =>
_.uniq(events.flatMap(event => event.docenti))
)
const [currentlyHovered, setCurrentlyHovered] = useState(null)
const element = useRef()
useEffect(() => {
if (element.current) {
const l = e => {
const isMobile = window.matchMedia('(pointer: coarse)').matches
const $course = e.target.closest('.course')
if ($course && !isMobile) {
setCurrentlyHovered($course.dataset.courseId)
} 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="course-view" ref={element}>
{isRestrictedList && 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">
{Object.entries(eventsByCourse).map(([id, courseEvents]) => (
<div
class={
'course' +
(currentlyHovered === id && !isRestrictedList
? ' highlight'
: '') +
(selectionSet.has(id) && !isRestrictedList
? ' selected'
: '')
}
data-course-id={id}
onClick={() => {
if (isRestrictedList) return
if (!selectionSet.has(id))
setSelection([...selection, id])
else
setSelection(
selection.filter(selId => selId !== id)
)
}}
>
<div class="title">
{prettyCourseName(courseEvents[0].name)}
</div>
<div class="docenti">
{profsPerCourse[id].join(', ')}
</div>
<div class="events">
{courseEvents.map(course => (
<div>
{WEEK_DAYS[course.day]} {course.start / 60}:
{String(course.start % 60).padStart(2, '0')}
&ndash;
{course.end / 60}:
{String(course.end % 60).padStart(2, '0')}{' '}
{course.aule.join(', ')}
</div>
))}
</div>
</div>
))}
</div>
</div>
)
}
+290
View File
@@ -0,0 +1,290 @@
import { useEffect, useRef, useState } from 'preact/hooks'
import _, { parseInt } from 'lodash'
import { differenceInMinutes, startOfDay } from 'date-fns'
import { parse } from 'yaml'
import { parseCustomEvents } 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 }) => {
return (
<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>
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 = () => {
return (
<div class="warning">
<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 (
<>
{layout.map(block => (
<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}`}>
{WEEK_DAYS_SHORT[n]}
</div>
<div class="day-line" style={`--position: ${n + 1}`}></div>
</>
))}
{[9, 11, 14, 16].map(n => (
<div
class="time-label"
style={{
'--position': n * 2 - weekStart,
}}
>
{n}-{n + 2}
</div>
))}
<div class="time-line" style="--position: 0"></div>
{[9, 11, 13, 14, 16, 18].map(n => (
<>
{n * 2 > weekStart && n * 2 < weekEnd && (
<div
class="time-line"
style={{
'--position': n * 2 - weekStart,
}}
></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>
)
}
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, custom }) => {
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))
parseCustomEvents(custom).forEach(event => events.push(event))
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>
</>
)
}
+56
View File
@@ -34,6 +34,62 @@ export function layoutIntervals(intervals, { tight } = {}) {
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...
// //
+288 -120
View File
@@ -1,156 +1,324 @@
import _ from 'lodash'
import { render } from 'preact'
import { useEffect, useState } from 'preact/hooks'
import { ToolOverlay } from './components/CourseVisibility.jsx'
import { differenceInMinutes, startOfDay } from 'date-fns'
import { EventsView } from './components/EventsView.jsx'
// import { ToolOverlay } from './components/ToolOverlay.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 { Help } from './components/Help.jsx'
import { Icon } from './components/Icon.jsx'
import { Popup } from './components/Popup.jsx'
import { Toolbar } from './components/Toolbar.jsx'
import { OptionBar } from './components/OptionBar.jsx'
import { prettyAulaName, prettyProfName, usePersistentState } from './utils.jsx'
import { SettingsBar } from './components/SettingsBar.jsx'
// Che fanno queste due righe?
window._ = _
window.dataBuffer = {}
const CALENDAR_IDS = {
'anno-1': ['6308cfcb1df5cb026699ce32'],
'anno-2': ['6308e2dc09352a0208fefdd9'],
'anno-3': ['6308e42a1df5cb026699ced4'],
'magistrale': ['6308e8ea0c34e703bb1f7e85'],
'tutti': [
'6308cfcb1df5cb026699ce32',
'6308e2dc09352a0208fefdd9',
'6308e42a1df5cb026699ced4',
'6308e8ea0c34e703bb1f7e85',
],
const TIMETABLE_IDS = {
'anno-1': '64a7c1c651f079001d52e9c8',
'anno-2': '6308e2dc09352a0208fefdd9',
'anno-3': '6308e42a1df5cb026699ced4',
'magistrale': '64a7c7091ab813002c5d9ede',
}
async function loadEventi(ids) {
const calendari = await Promise.all(
ids.map(async id => {
// 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: '2022-10-02T22:00:00.000Z',
dataFine: '2022-10-07T22:00:00.000Z',
}),
method: 'POST',
mode: 'cors',
credentials: 'omit',
}
)
// const DEFAULT_DATE_RANGE = {
// from: '2023-10-09T00:00:00.000Z',
// to: '2023-10-14T00:00:00.000Z',
// }
return await req.json()
})
// const DATE_RANGES = {
// '64a7c1c651f079001d52e9c8': DEFAULT_DATE_RANGE,
// '6308e2dc09352a0208fefdd9': DEFAULT_DATE_RANGE,
// '6308e42a1df5cb026699ced4': DEFAULT_DATE_RANGE,
// '64a7c7091ab813002c5d9ede': DEFAULT_DATE_RANGE,
// }
function specialEventPatches(eventi) {
// Il laboratorio del primo anno in realtà è in due canali separati
let i = 1
eventi.forEach(evento => {
if (
evento.nome ===
'LABORATORIO DI INTRODUZIONE ALLA MATEMATICA COMPUTAZIONALE'
) {
evento.nome += ` (${i})`
i++
}
})
return eventi
}
function formatEvents(timetable) {
return timetable.map(({ nome, dataInizio, dataFine, docenti, aule }) => {
const start = new Date(dataInizio)
const end = new Date(dataFine)
return {
id: nome,
name: _.split(nome, '-', 1)[0].trim(),
day: start.getDay(),
start: differenceInMinutes(start, startOfDay(start)),
end: differenceInMinutes(end, startOfDay(start)),
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) {
// 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()
}
const requests = [
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(_.uniqBy(_.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,
custom,
setCustom,
}) => {
if (view === 'orario') {
return (
<Schedule
selection={selection}
timetables={timetables}
custom={custom}
/>
)
} else if (view === 'lista') {
return (
<Courses
selection={selection}
setSelection={setSelection}
timetables={timetables}
custom={custom}
isRestrictedList={true}
/>
)
} else if (view === 'custom') {
return (
<div class="custom-events-view">
<textarea
value={custom}
onChange={e => setCustom(e.target.value)}
/>
<p>Esempio di evento personalizzato</p>
<p>
Nome evento [label globale]:
<br />
- Lun 9-11
<br />
- Mar 9:00-11:00
<br />- Gio 8:00-12:00 [label locale]
</p>
</div>
)
} else {
return (
<Courses
selection={selection}
setSelection={setSelection}
source={view}
timetables={timetables}
isRestrictedList={false}
/>
)
}
}
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
const [view, setView] = usePersistentState('view', 'magistrale')
const [timetables, setTimetables] = useState(null)
useEffect(async () => {
setTimetables(await loadCalendari(new Date(date)))
}, [date])
// Selection
const [selectedCourses, setSelectedCourses] = usePersistentState(
'selection',
[]
)
// console.log(calendari)
if (ids.length === 1) {
return calendari[0]
}
return _.uniqBy(_.concat(...calendari), 'id')
}
const App = ({}) => {
const [source, setSource] = useState('magistrale')
const [eventi, setEventi] = useState([])
const [selectedCourses, setSelectedCourses] = useState([])
useEffect(() => {
setSelectedCourses([])
}, [source])
useEffect(async () => {
const eventi = await loadEventi(CALENDAR_IDS[source])
window.dataBuffer[source] = eventi
setEventi(eventi)
}, [source])
// Custom Events
const [custom, setCustom] = usePersistentState('custom', [])
// Menus
const [helpVisible, setHelpVisible] = useState(false)
const [mode, setMode] = useState('course')
const [hideOtherCourses, setHideOtherCourses] = useState(false)
// TODO: Should wrap in "useEffect"?
if (selectedCourses.length === 0) {
setHideOtherCourses(false)
}
const [showMobileMenu, setShowMobileMenu] = useState(false)
// const groupIds = new Set(eventi.map(e => e.nome))
const toolOverlayVisible = selectedCourses.length > 0
const [theme, setTheme] = usePersistentState(
'theme',
'light'
// window.matchMedia('(prefers-color-scheme: dark)').matches
// ? 'dark'
// : 'light'
)
document.body.classList.toggle('dark-mode', theme === 'dark')
return (
<>
<Toolbar
{...{
mode,
setMode,
source,
setSource,
onShowMenu: () => setShowMobileMenu(true),
source: view,
setSource: setView,
date: date,
setDate: setDate,
showMobileMenu: showMobileMenu,
setShowMobileMenu: setShowMobileMenu,
onHelp: () => setHelpVisible(true),
theme,
setTheme,
}}
/>
<EventsView
mode={mode}
selection={selectedCourses}
setSelection={setSelectedCourses}
hideOtherCourses={hideOtherCourses}
start={new Date(2022, 10, 3)}
events={eventi.map(({ nome, dataInizio, dataFine, docenti, aule }) => ({
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'
),
}))}
/>
{selectedCourses.length > 0 && (
<ToolOverlay
visibility={hideOtherCourses}
toggleVisibility={() => setHideOtherCourses(s => !s)}
onClose={() => {
setSelectedCourses([])
setHideOtherCourses(false)
}}
/>
)}
{showMobileMenu && (
<HamburgerMenu
{showMobileMenu ? (
<SettingsBar
{...{
mode,
setMode,
source,
setSource,
onClose: () => {
setShowMobileMenu(false)
},
theme,
setTheme,
date,
setDate,
}}
/>
) : (
<OptionBar
{...{
view: view,
setView: setView,
onHelp: () => setHelpVisible(true),
}}
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}
custom={custom}
setCustom={setCustom}
/>
))}
</div>
{/* showMobileMenu && (
) */}
{helpVisible && (
<Popup
title={
+500 -438
View File
File diff suppressed because it is too large Load Diff
+157
View File
@@ -1,3 +1,133 @@
import { useEffect, useState } from 'preact/hooks'
import { parse } from 'yaml'
// Calendar
export const WEEK_DAYS = [
'Domenica',
'Lunedì',
'Martedì',
'Mercoledì',
'Giovedì',
'Venerdì',
'Sabato',
]
export const WEEK_DAYS_SHORT = ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab']
// Hashing
export function hashString(str, seed = 0) {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i)
h1 = Math.imul(h1 ^ ch, 2654435761)
h2 = Math.imul(h2 ^ ch, 1597334677)
}
h1 =
Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^
Math.imul(h2 ^ (h2 >>> 13), 3266489909)
h2 =
Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^
Math.imul(h1 ^ (h1 >>> 13), 3266489909)
return 4294967296 * (2097151 & h2) + (h1 >>> 0)
}
// Courses
export function prettyCourseName(name) {
return name
.split(' ')
.map(word => word.toLowerCase())
.map(word => {
if (word.trim().length === 0) return word
return /(^del|^nel|^di$|^dei$|^con$|^alla$|^per$|^e$|^la$)/.test(
word
)
? word
: word[0].toUpperCase() + word.slice(1)
})
.join(' ')
.replaceAll('Ii', 'II')
.replaceAll('IIi', 'III')
.replaceAll('Iii', 'III')
.replaceAll(/'(.)/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('')
}
// Custom events
export function parseCustomEvents(customEventsString) {
try {
const customEvents = []
const parsedEvents = parse(customEventsString)
for (const customCourse in parsedEvents) {
const [_, name, teachers] = /([^\[]*)(?:\[(.*)\])?/.exec(
customCourse
)
const docenti = teachers ? teachers.split(',') : []
for (const customEvent of parsedEvents[customCourse]) {
const [_b, day, startH, startM, endH, endM, label] =
/(lun|lunedì|mar|martedì|mer|mercoledì|gio|giovedì|ven|venerdì)\s*(\d{1,2})(?:\:(\d\d))?\s*-\s*(\d{1,2})(?:\:(\d\d))?\s*(.*)?/i.exec(
customEvent
)
const dayNumber = {
lun: 1,
lunedì: 1,
mar: 2,
martedì: 2,
mer: 3,
mercoledì: 3,
gio: 4,
giovedì: 4,
ven: 5,
venerdì: 5,
}[day.toLowerCase()]
const start = startM
? parseInt(startH) * 60 + parseInt(startM)
: parseInt(startH) * 60
const end = endM
? parseInt(endH) * 60 + parseInt(endM)
: parseInt(endH) * 60
const aule = label ? label.split(',') : []
customEvents.push({
id: name,
name,
docenti,
start,
end,
day: dayNumber,
aule,
})
}
}
return customEvents
} catch (e) {
console.log('ehi', e)
return []
}
}
// JSX
export const withClasses = classes =>
Array.isArray(classes)
? classes.filter(e => !!e).join(' ')
@@ -5,3 +135,30 @@ export const withClasses = classes =>
.filter(([, value]) => value)
.map(([key]) => key)
.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]
}