Compare commits

..

2 Commits

Author SHA1 Message Date
Francesco Baldino 3ddf325c03 prototipo eventi custom 1 year ago
Francesco Baldino 973acad17a small fix on dynamic viewport height 1 year ago

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

@ -16,7 +16,8 @@
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"preact": "^10.10.6", "preact": "^10.10.6",
"sass": "^1.54.8", "sass": "^1.54.8",
"vite": "^3.0.9" "vite": "^3.0.9",
"yaml": "^2.3.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.18.13", "@babel/core": "^7.18.13",

@ -23,6 +23,9 @@ dependencies:
vite: vite:
specifier: ^3.0.9 specifier: ^3.0.9
version: 3.0.9(sass@1.54.8) version: 3.0.9(sass@1.54.8)
yaml:
specifier: ^2.3.2
version: 2.3.2
devDependencies: devDependencies:
'@babel/core': '@babel/core':
@ -937,3 +940,8 @@ packages:
sass: 1.54.8 sass: 1.54.8
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2
/yaml@2.3.2:
resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==}
engines: {node: '>= 14'}
dev: false

@ -1,19 +1,12 @@
import { useRef } from 'preact/hooks' import { useRef } from 'preact/hooks'
import { Icon } from './Icon.jsx' import { Icon } from './Icon.jsx'
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
export const DatePicker = ({ date, setDate }) => { export const DatePicker = ({ date, setDate }) => {
const input = useRef() const input = useRef()
const [year, month, day] = date.split('T')[0].split('-') const [year, month, day] = date.split('T')[0].split('-')
return ( return (
<div <div class="date-picker" onClick={() => input.current.showPicker()}>
class="date-picker"
onClick={() =>
isSafari ? input.current.focus() : input.current.showPicker()
}
>
<input <input
ref={input} ref={input}
type="date" type="date"

@ -25,6 +25,7 @@ const viewModeMap = {
export const EventsView = ({ mode, source, ...viewProps }) => { export const EventsView = ({ mode, source, ...viewProps }) => {
// const Mode = viewModeMap[mode] // const Mode = viewModeMap[mode]
if (source === 'orario') { if (source === 'orario') {
return ( return (
<div class="events-view"> <div class="events-view">

@ -4,31 +4,33 @@ export const Help = ({}) => (
<> <>
<h3>Visualizzazione Corsi</h3> <h3>Visualizzazione Corsi</h3>
<p> <p>
Per visualizzare i corsi che ti interessano nella tabella dell'orario, devi Per visualizzare i corsi che ti interessano nella tabella
prima selezionarli. Puoi selezionare dei corsi cliccandoli, cercandoli nelle dell'orario, devi prima selezionarli. Puoi selezionare dei corsi
sezioni Primo anno (<b>I</b>), Secondo anno (<b>II</b>), Terzo anno ( cliccandoli, cercandoli nelle sezioni Primo anno (<b>I</b>), Secondo
<b>III</b>), Magistrale (<b>M</b>), o Tutti. anno (<b>II</b>), Terzo anno (<b>III</b>), Magistrale (<b>M</b>), o
Tutti.
</p> </p>
<p> <p>
Una volta compiuta la selezione, è possibile vedere la tabella delle lezioni Una volta compiuta la selezione, è possibile vedere la tabella delle
andando nella visualizzazione Orario ( lezioni andando nella visualizzazione Orario (
<Icon name="calendar_view_month" />) <Icon name="calendar_view_month" />)
</p> </p>
<p> <p>
Per via di eventuali preferenze personali, è possibile cambiare l'orientazione Per via di eventuali preferenze personali, è possibile cambiare
della tabella Orario trasponendola, utilizzando il pulsante Trasponi ( l'orientazione della tabella Orario trasponendola, utilizzando il
pulsante Trasponi (
<Icon name="switch_left" style="transform: rotate(-45deg)" />) <Icon name="switch_left" style="transform: rotate(-45deg)" />)
</p> </p>
<p> <p>
È anche possibile visualizzare in uno specchietto riassuntivo soltanto i corsi È anche possibile visualizzare in uno specchietto riassuntivo
selezionati, andando nella visualizzazione Lista ( soltanto i corsi selezionati, andando nella visualizzazione Lista (
<Icon name="list" />) <Icon name="list" />)
</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
<Icon name="print" /> (è consigliato controllare le opzioni di stampa per bottone <Icon name="print" /> (è consigliato controllare le opzioni
ottenere un risultato soddisfacente). di stampa per ottenere un risultato soddisfacente).
</p> </p>
<h3>Bug &amp; Contatti</h3> <h3>Bug &amp; Contatti</h3>
<p> <p>

@ -7,9 +7,23 @@ export const SettingsBar = ({ theme, setTheme, date, setDate }) => {
<div class="settings-group"> <div class="settings-group">
<button <button
class="icon" class="icon"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} onClick={() =>
setTheme(theme === 'dark' ? 'light' : 'dark')
}
> >
<Icon name={theme === 'dark' ? 'dark_mode' : 'light_mode'} /> <Icon
name={theme === 'dark' ? 'dark_mode' : 'light_mode'}
/>
</button>
<button
class="icon"
onClick={() =>
setTheme(theme === 'dark' ? 'light' : 'dark')
}
>
<Icon
name="add"
/>
</button> </button>
<DatePicker date={date} setDate={setDate} /> <DatePicker date={date} setDate={setDate} />
</div> </div>

@ -24,7 +24,8 @@ export const Toolbar = ({
</button> </button>
</div> </div>
<div class="item logo"> <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>
<div class="option-group"> <div class="option-group">
<div class="item option"> <div class="item option">
@ -35,6 +36,7 @@ export const Toolbar = ({
{ value: 'anno-3', label: 'III' }, { value: 'anno-3', label: 'III' },
{ value: 'magistrale', label: 'Magistrale' }, { value: 'magistrale', label: 'Magistrale' },
{ value: 'tutti', label: 'Tutti' }, { value: 'tutti', label: 'Tutti' },
{ value: 'custom', label: 'Custom' },
]} ]}
value={source} value={source}
setValue={setSource} setValue={setSource}
@ -50,9 +52,10 @@ export const Toolbar = ({
setValue={setSource} setValue={setSource}
/> />
</div> </div>
</div> <div class="empty"></div>
<div class="option-group"> <div class="item option">
<DatePicker date={date} setDate={setDate} /> <DatePicker date={date} setDate={setDate} />
</div>
</div> </div>
<div class="option-group"> <div class="option-group">
<div class="item option"> <div class="item option">
@ -63,9 +66,13 @@ export const Toolbar = ({
<div class="item option"> <div class="item option">
<button <button
class="icon" class="icon"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} onClick={() =>
setTheme(theme === 'dark' ? 'light' : 'dark')
}
> >
<Icon name={theme === 'dark' ? 'dark_mode' : 'light_mode'} /> <Icon
name={theme === 'dark' ? 'dark_mode' : 'light_mode'}
/>
</button> </button>
</div> </div>
<div class="item option"> <div class="item option">

@ -2,7 +2,7 @@ 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 { parseCustomEvents, prettyCourseName, WEEK_DAYS } from '../../utils.jsx'
import { Icon } from '../Icon.jsx' import { Icon } from '../Icon.jsx'
export const Courses = ({ export const Courses = ({
@ -10,13 +10,18 @@ export const Courses = ({
timetables, timetables,
selection, selection,
setSelection, setSelection,
hideOtherCourses, custom,
isRestrictedList,
}) => { }) => {
const events = timetables[source] const events = isRestrictedList ? timetables['tutti'] : timetables[source]
const selectionSet = new Set(selection) const selectionSet = new Set(selection)
const visibleEvents = hideOtherCourses console.log(events)
? events.filter(e => selectionSet.has(e.id))
const visibleEvents = isRestrictedList
? events
.filter(e => selectionSet.has(e.id))
.concat(parseCustomEvents(custom))
: events : events
const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'id'), 'id') const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'id'), 'id')
@ -53,7 +58,7 @@ export const Courses = ({
return ( return (
<div class="course-view" ref={element}> <div class="course-view" ref={element}>
{hideOtherCourses && selection.length === 0 && ( {isRestrictedList && selection.length === 0 && (
<div class="warning"> <div class="warning">
<p>Non hai ancora selezionato nessun corso.</p> <p>Non hai ancora selezionato nessun corso.</p>
<p> <p>
@ -67,23 +72,39 @@ export const Courses = ({
<div <div
class={ class={
'course' + 'course' +
(currentlyHovered === id ? ' highlight' : '') + (currentlyHovered === id && !isRestrictedList
(selectionSet.has(id) ? ' selected' : '') ? ' highlight'
: '') +
(selectionSet.has(id) && !isRestrictedList
? ' selected'
: '')
} }
data-course-id={id} data-course-id={id}
onClick={() => { onClick={() => {
if (!selectionSet.has(id)) setSelection([...selection, id]) if (isRestrictedList) return
else setSelection(selection.filter(selId => selId !== id)) if (!selectionSet.has(id))
setSelection([...selection, id])
else
setSelection(
selection.filter(selId => selId !== id)
)
}} }}
> >
<div class="title">{prettyCourseName(courseEvents[0].name)}</div> <div class="title">
<div class="docenti">{profsPerCourse[id].join(', ')}</div> {prettyCourseName(courseEvents[0].name)}
</div>
<div class="docenti">
{profsPerCourse[id].join(', ')}
</div>
<div class="events"> <div class="events">
{courseEvents.map(course => ( {courseEvents.map(course => (
<div> <div>
{WEEK_DAYS[course.start.getDay()]}{' '} {WEEK_DAYS[course.day]} {course.start / 60}:
{format(course.start, 'H:mm')}&ndash; {String(course.start % 60).padStart(2, '0')}
{format(course.end, 'H:mm')} {course.aule.join(', ')} &ndash;
{course.end / 60}:
{String(course.end % 60).padStart(2, '0')}{' '}
{course.aule.join(', ')}
</div> </div>
))} ))}
</div> </div>

@ -1,9 +1,15 @@
import { useEffect, useRef, useState } from 'preact/hooks' import { useEffect, useRef, useState } from 'preact/hooks'
import _ from 'lodash' import _, { parseInt } from 'lodash'
import { differenceInMinutes, startOfDay } from 'date-fns' 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 {
WEEK_DAYS_SHORT,
prettyCourseName,
usePersistentState,
} from '../../utils.jsx'
import { layoutEvents, layoutIntervals } from '../../interval-layout.js' import { layoutEvents, layoutIntervals } from '../../interval-layout.js'
import { Popup } from '../Popup.jsx' import { Popup } from '../Popup.jsx'
import { Icon } from '../Icon.jsx' import { Icon } from '../Icon.jsx'
@ -13,23 +19,28 @@ const TransposePopup = ({ onClose }) => {
<Popup <Popup
title={ title={
<> <>
<Icon name="info" /> Attenzione! La tabella è stata trasposta! <Icon name="info" /> Attenzione! La tabella è stata
trasposta!
</> </>
} }
onClose={onClose} onClose={onClose}
> >
<p>A grande richiesta popolare abbiamo trasposto la tabella dell'orario!</p> <p>
A grande richiesta popolare abbiamo trasposto la tabella
dell'orario!
</p>
<p> <p>
Assicurati quindi di leggerla correttamente (dall'alto verso il basso Assicurati quindi di leggerla correttamente (dall'alto verso il
invece che da sinistra verso destra). basso invece che da sinistra verso destra).
</p> </p>
<p> <p>
Se preferisci utilizzare la versione vecchia, puoi utilizzare il pulsante Se preferisci utilizzare la versione vecchia, puoi utilizzare il
Trasponi <Icon name="switch_left" style="transform: rotate(-45deg)" />{' '} pulsante Trasponi{' '}
nell'origine della tabella per trasporla. Questa scelta verrà salvata nei <Icon name="switch_left" style="transform: rotate(-45deg)" />{' '}
cookie e verrà ricordata in futuro nell'origine della tabella per trasporla. Questa scelta verrà
salvata nei cookie e verrà ricordata in futuro
</p> </p>
</Popup> </Popup>
) )
@ -40,8 +51,8 @@ const NoCourseWarning = () => {
<div class="warning"> <div class="warning">
<p>Non hai ancora selezionato nessun corso.</p> <p>Non hai ancora selezionato nessun corso.</p>
<p> <p>
Clicca sui corsi nelle altre visuali per selezionarli e visualizzarli Clicca sui corsi nelle altre visuali per selezionarli e
nell'orario visualizzarli nell'orario
</p> </p>
</div> </div>
) )
@ -66,10 +77,13 @@ const Layout = ({ layout, day, colors }) => {
style={{ style={{
'--block-size': block.end - block.start, '--block-size': block.end - block.start,
'--size': event.end - event.start, '--size': event.end - event.start,
'--relative-start': event.start - block.start, '--relative-start':
event.start - block.start,
'--index': event.index, '--index': event.index,
'--of': block.layers, '--of': block.layers,
'--color': `var(--event-${colors[event.id]})`, '--color': `var(--event-${
colors[event.id]
})`,
}} }}
> >
<div class="event"> <div class="event">
@ -106,11 +120,16 @@ const ScheduleGrid = ({
class="small" class="small"
onClick={() => onClick={() =>
setOrientation( setOrientation(
orientation === 'original' ? 'transposed' : 'original' orientation === 'original'
? 'transposed'
: 'original'
) )
} }
> >
<Icon name="switch_left" style="transform: rotate(-45deg)" /> <Icon
name="switch_left"
style="transform: rotate(-45deg)"
/>
</button> </button>
</div> </div>
{[1, 2, 3, 4, 5].map(n => ( {[1, 2, 3, 4, 5].map(n => (
@ -193,25 +212,30 @@ const ScheduleCard = ({
) )
} }
export const Schedule = ({ timetables, selection }) => { export const Schedule = ({ timetables, selection, custom }) => {
const [hasSeenTranspose, setHasSeenTranspose] = usePersistentState( const [hasSeenTranspose, setHasSeenTranspose] = usePersistentState(
'transpose_info', 'transpose_info',
'false' 'false'
) )
const [orientation, setOrientation] = usePersistentState('orientation', 'original') const [orientation, setOrientation] = usePersistentState(
'orientation',
'original'
)
const colorList = ['red', 'purple', 'blue', 'yellow', 'green', 'orange', 'lightblue'] const colorList = [
'red',
'purple',
'blue',
'yellow',
'green',
'orange',
'lightblue',
]
const allEvents = timetables['tutti'] const allEvents = timetables['tutti']
const selectionSet = new Set(selection) const selectionSet = new Set(selection)
const events = allEvents const events = allEvents.filter(e => selectionSet.has(e.id))
.filter(e => selectionSet.has(e.id)) parseCustomEvents(custom).forEach(event => events.push(event))
.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 weekStart = Math.min(...events.map(e => e.start), 9 * 60) / 30
const weekEnd = Math.max(...events.map(e => e.end), 18 * 60) / 30 const weekEnd = Math.max(...events.map(e => e.end), 18 * 60) / 30

@ -42,7 +42,10 @@ function layoutBlockEvents(events) {
let viableIndex = 0 let viableIndex = 0
while ( while (
result.filter( result.filter(
e => e.index === viableIndex && e.start < event.end && event.start < e.end e =>
e.index === viableIndex &&
e.start < event.end &&
event.start < e.end
).length !== 0 ).length !== 0
) { ) {
viableIndex += 1 viableIndex += 1
@ -54,7 +57,8 @@ function layoutBlockEvents(events) {
return result return result
} }
export function layoutEvents(events) { export function layoutEvents(events) {
const overlap = (event, block) => event.start < block.end && block.start < event.end const overlap = (event, block) =>
event.start < block.end && block.start < event.end
events.sort((a, b) => a.start - b.start) events.sort((a, b) => a.start - b.start)
@ -64,7 +68,10 @@ export function layoutEvents(events) {
if (blocks.length > 0) { if (blocks.length > 0) {
layout = layout.filter(block => !overlap(event, block)) layout = layout.filter(block => !overlap(event, block))
layout.push({ layout.push({
start: Math.min(event.start, ...blocks.map(block => block.start)), start: Math.min(
event.start,
...blocks.map(block => block.start)
),
end: Math.max(event.end, ...blocks.map(block => block.end)), end: Math.max(event.end, ...blocks.map(block => block.end)),
events: blocks.flatMap(block => block.events).concat([event]), events: blocks.flatMap(block => block.events).concat([event]),
}) })

@ -1,6 +1,7 @@
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 { differenceInMinutes, startOfDay } from 'date-fns'
// import { ToolOverlay } from './components/ToolOverlay.jsx' // import { ToolOverlay } from './components/ToolOverlay.jsx'
// //
@ -18,12 +19,7 @@ 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 { OptionBar } from './components/OptionBar.jsx'
import { import { prettyAulaName, prettyProfName, usePersistentState } from './utils.jsx'
prettyAulaName,
prettyProfName,
clearOldPersistentStates,
usePersistentState,
} from './utils.jsx'
import { SettingsBar } from './components/SettingsBar.jsx' import { SettingsBar } from './components/SettingsBar.jsx'
// Che fanno queste due righe? // Che fanno queste due righe?
@ -31,10 +27,10 @@ window._ = _
window.dataBuffer = {} window.dataBuffer = {}
const TIMETABLE_IDS = { const TIMETABLE_IDS = {
'anno-1': '667e88275e9623041f0e43d4', 'anno-1': '64a7c1c651f079001d52e9c8',
'anno-2': '667e89055e9623041f0e43d6', 'anno-2': '6308e2dc09352a0208fefdd9',
'anno-3': '667e89fcf748ed0415a11dcc', 'anno-3': '6308e42a1df5cb026699ced4',
'magistrale': '667ebae63379a3046517ffd4', 'magistrale': '64a7c7091ab813002c5d9ede',
} }
// const DEFAULT_DATE_RANGE = { // const DEFAULT_DATE_RANGE = {
@ -50,17 +46,15 @@ const TIMETABLE_IDS = {
// } // }
function specialEventPatches(eventi) { function specialEventPatches(eventi) {
// Il laboratorio del primo anno in realtà è in due gruppi separati // Il laboratorio del primo anno in realtà è in due canali separati
let i = 1
eventi.forEach(evento => { eventi.forEach(evento => {
if ( if (
evento.nome === 'LABORATORIO DI INTRODUZIONE ALLA MATEMATICA COMPUTAZIONALE' evento.nome ===
'LABORATORIO DI INTRODUZIONE ALLA MATEMATICA COMPUTAZIONALE'
) { ) {
if (evento.docenti[0].nome === 'GIOVANNI') { evento.nome += ` (${i})`
evento.nome += ' (A)' i++
}
if (evento.docenti[0].nome === 'PAOLO') {
evento.nome += ' (B)'
}
} }
}) })
@ -69,12 +63,17 @@ function specialEventPatches(eventi) {
function formatEvents(timetable) { function formatEvents(timetable) {
return timetable.map(({ nome, dataInizio, dataFine, docenti, aule }) => { return timetable.map(({ nome, dataInizio, dataFine, docenti, aule }) => {
const start = new Date(dataInizio)
const end = new Date(dataFine)
return { return {
id: nome, id: nome,
name: _.split(nome, '-', 1)[0].trim(), name: _.split(nome, '-', 1)[0].trim(),
start: new Date(dataInizio), day: start.getDay(),
end: new Date(dataFine), start: differenceInMinutes(start, startOfDay(start)),
docenti: docenti.map(({ nome, cognome }) => prettyProfName(nome, cognome)), end: differenceInMinutes(end, startOfDay(start)),
docenti: docenti.map(({ nome, cognome }) =>
prettyProfName(nome, cognome)
),
aule: aule.map(aula => prettyAulaName(aula.codice)), aule: aule.map(aula => prettyAulaName(aula.codice)),
} }
}) })
@ -131,7 +130,7 @@ async function loadCalendari(date) {
const timetablesRaw = results.map(timetable => const timetablesRaw = results.map(timetable =>
specialEventPatches(_.uniqBy(timetable, 'id')) specialEventPatches(_.uniqBy(timetable, 'id'))
) )
const allRaw = specialEventPatches(_.concat(...results), 'id') const allRaw = specialEventPatches(_.uniqBy(_.concat(...results), 'id'))
return { return {
'anno-1': formatEvents(timetablesRaw[0]), 'anno-1': formatEvents(timetablesRaw[0]),
@ -142,19 +141,50 @@ async function loadCalendari(date) {
} }
} }
const View = ({ view, selection, setSelection, timetables }) => { const View = ({
view,
selection,
setSelection,
timetables,
custom,
setCustom,
}) => {
if (view === 'orario') { if (view === 'orario') {
return <Schedule selection={selection} timetables={timetables} /> return (
<Schedule
selection={selection}
timetables={timetables}
custom={custom}
/>
)
} else if (view === 'lista') { } else if (view === 'lista') {
return ( return (
<Courses <Courses
selection={selection} selection={selection}
setSelection={setSelection} setSelection={setSelection}
source={'tutti'}
timetables={timetables} timetables={timetables}
hideOtherCourses={true} 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 { } else {
return ( return (
<Courses <Courses
@ -162,7 +192,7 @@ const View = ({ view, selection, setSelection, timetables }) => {
setSelection={setSelection} setSelection={setSelection}
source={view} source={view}
timetables={timetables} timetables={timetables}
hideOtherCourses={false} isRestrictedList={false}
/> />
) )
} }
@ -184,11 +214,14 @@ const App = ({}) => {
setTimetables(await loadCalendari(new Date(date))) setTimetables(await loadCalendari(new Date(date)))
}, [date]) }, [date])
// View Modes
// const [mode, setMode] = usePersistentState('orario.mode', MODE_COURSES)
// Selection // Selection
const [selectedCourses, setSelectedCourses] = usePersistentState('selection', []) const [selectedCourses, setSelectedCourses] = usePersistentState(
'selection',
[]
)
// Custom Events
const [custom, setCustom] = usePersistentState('custom', [])
// Menus // Menus
const [helpVisible, setHelpVisible] = useState(false) const [helpVisible, setHelpVisible] = useState(false)
@ -259,15 +292,16 @@ const App = ({}) => {
) : timetables['tutti'].length === 0 ? ( ) : timetables['tutti'].length === 0 ? (
<div class="warning"> <div class="warning">
<p> <p>
Non esistono corsi per la settimana selezionata: buone Non esistono corsi per la settimana selezionata:
vacanze! 🎉 buone vacanze! 🎉
</p> </p>
<p> <p>
Per cambiare settimana puoi usare il widget Calendario ( Per cambiare settimana puoi usare il widget
Calendario (
<Icon name="calendar_month" />) in alto a destra <Icon name="calendar_month" />) in alto a destra
<br /> <br />
In versione mobile, il widget Calendario è situato dentro In versione mobile, il widget Calendario è
il Menu ( situato dentro il Menu (
<Icon name="menu" />) <Icon name="menu" />)
</p> </p>
</div> </div>
@ -277,6 +311,8 @@ const App = ({}) => {
setSelection={setSelectedCourses} setSelection={setSelectedCourses}
view={view} view={view}
timetables={timetables} timetables={timetables}
custom={custom}
setCustom={setCustom}
/> />
))} ))}
</div> </div>

@ -530,9 +530,9 @@ body {
} }
.content { .content {
height: calc(100vh - 4rem); height: calc(100dvh - 4rem);
@media screen and (max-width: $device-s-width), (pointer: coarse) { @media screen and (max-width: $device-s-width), (pointer: coarse) {
height: calc(100vh - 8rem); height: calc(100dvh - 8rem);
} }
overflow-y: scroll; overflow-y: scroll;
@ -624,7 +624,7 @@ body {
.schedule-view { .schedule-view {
min-height: 100%; min-height: 100%;
width: 100%; width: 100%;
max-width: 57rem; max-width: 55rem;
margin: auto; margin: auto;
padding: 0rem 0.5rem; padding: 0rem 0.5rem;
@ -644,7 +644,8 @@ body {
border: 1px solid var(--border-600); border: 1px solid var(--border-600);
border-radius: 10px 10px 0 0; border-radius: 10px 10px 0 0;
@media screen and (max-width: $device-s-width), (pointer: coarse) { @media screen and (max-width: $device-s-width),
(pointer: coarse) {
font-size: 12px; font-size: 12px;
} }
@ -699,7 +700,10 @@ body {
&.original { &.original {
grid-template-columns: auto repeat(5, 1fr); grid-template-columns: auto repeat(5, 1fr);
grid-template-rows: min-content repeat(var(--time-slots), 1fr); grid-template-rows: min-content repeat(
var(--time-slots),
1fr
);
.transpose-button, .transpose-button,
.day-label { .day-label {
@ -726,16 +730,22 @@ body {
} }
.event-block-wrapper { .event-block-wrapper {
grid-row: calc(var(--time-start) + 2) / calc(var(--time-end) + 2); grid-row: calc(var(--time-start) + 2) /
calc(var(--time-end) + 2);
grid-column: calc(var(--day-position) + 1); grid-column: calc(var(--day-position) + 1);
.event-block { .event-block {
.event-wrapper { .event-wrapper {
width: calc(100% / var(--of)); width: calc(100% / var(--of));
height: calc(100% * var(--size) / var(--block-size)); height: calc(
100% * var(--size) / var(--block-size)
);
transform: translateX(calc(100% * var(--index))) transform: translateX(calc(100% * var(--index)))
translateY( translateY(
calc(100% * var(--relative-start) / var(--size)) calc(
100% * var(--relative-start) /
var(--size)
)
); );
} }
} }
@ -743,7 +753,10 @@ body {
} }
&.transposed { &.transposed {
grid-template-rows: auto repeat(5, 1fr); grid-template-rows: auto repeat(5, 1fr);
grid-template-columns: min-content repeat(var(--time-slots), 1fr); grid-template-columns: min-content repeat(
var(--time-slots),
1fr
);
.transpose-button, .transpose-button,
.day-label { .day-label {
@ -779,10 +792,15 @@ body {
.event-block { .event-block {
.event-wrapper { .event-wrapper {
height: calc(100% / var(--of)); height: calc(100% / var(--of));
width: calc(100% * var(--size) / var(--block-size)); width: calc(
100% * var(--size) / var(--block-size)
);
transform: translateY(calc(100% * var(--index))) transform: translateY(calc(100% * var(--index)))
translateX( translateX(
calc(100% * var(--relative-start) / var(--size)) calc(
100% * var(--relative-start) /
var(--size)
)
); );
} }
} }
@ -879,6 +897,27 @@ body {
overflow-y: scroll; overflow-y: scroll;
} }
} }
.custom-events-view {
min-height: 100%;
width: 100%;
max-width: 30rem;
margin: auto;
padding: 1rem 1rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
textarea {
width: 100%;
min-height: 20rem;
font-size: 20px;
}
}
} }
.overlay { .overlay {

@ -1,6 +1,7 @@
// Calendar
import { useEffect, useState } from 'preact/hooks' import { useEffect, useState } from 'preact/hooks'
import { parse } from 'yaml'
// Calendar
export const WEEK_DAYS = [ export const WEEK_DAYS = [
'Domenica', 'Domenica',
@ -24,8 +25,12 @@ export function hashString(str, seed = 0) {
h1 = Math.imul(h1 ^ ch, 2654435761) h1 = Math.imul(h1 ^ ch, 2654435761)
h2 = Math.imul(h2 ^ ch, 1597334677) h2 = Math.imul(h2 ^ ch, 1597334677)
} }
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909) h1 =
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909) 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) return 4294967296 * (2097151 & h2) + (h1 >>> 0)
} }
@ -38,7 +43,9 @@ export function prettyCourseName(name) {
.map(word => { .map(word => {
if (word.trim().length === 0) return word if (word.trim().length === 0) return word
return /(^del|^nel|^di$|^dei$|^con$|^alla$|^per$|^e$|^la$)/.test(word) return /(^del|^nel|^di$|^dei$|^con$|^alla$|^per$|^e$|^la$)/.test(
word
)
? word ? word
: word[0].toUpperCase() + word.slice(1) : word[0].toUpperCase() + word.slice(1)
}) })
@ -47,7 +54,6 @@ export function prettyCourseName(name) {
.replaceAll('IIi', 'III') .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) { export function prettyAulaName(name) {
@ -64,6 +70,62 @@ export function prettyProfName(nome, cognome) {
.join('') .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 // JSX
export const withClasses = classes => export const withClasses = classes =>

Loading…
Cancel
Save