Compare commits

..

No commits in common. '935309147c284b5e9fb59f342aad368cf47c87ef' and 'd9d2aaed11ec2dfdd7db892caa62769d42e0d8d5' have entirely different histories.

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

@ -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"

@ -24,6 +24,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 (

@ -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,13 @@ 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>
<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">
@ -63,9 +64,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">

@ -72,18 +72,27 @@ export const Courses = ({
} }
data-course-id={id} data-course-id={id}
onClick={() => { onClick={() => {
if (!selectionSet.has(id)) setSelection([...selection, id]) if (!selectionSet.has(id))
else setSelection(selection.filter(selId => selId !== 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.start.getDay()]}{' '}
{format(course.start, 'H:mm')}&ndash; {format(course.start, 'H:mm')}&ndash;
{format(course.end, 'H:mm')} {course.aule.join(', ')} {format(course.end, 'H:mm')}{' '}
{course.aule.join(', ')}
</div> </div>
))} ))}
</div> </div>

@ -3,7 +3,11 @@ import { useEffect, useRef, useState } from 'preact/hooks'
import _ from 'lodash' import _ from 'lodash'
import { differenceInMinutes, startOfDay } from 'date-fns' import { differenceInMinutes, startOfDay } from 'date-fns'
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 +17,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 +49,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 +75,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 +118,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 => (
@ -198,9 +215,20 @@ export const Schedule = ({ timetables, selection }) => {
'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)

@ -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]),
}) })

@ -53,7 +53,8 @@ function specialEventPatches(eventi) {
// Il laboratorio del primo anno in realtà è in due gruppi separati // Il laboratorio del primo anno in realtà è in due gruppi separati
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') { if (evento.docenti[0].nome === 'GIOVANNI') {
evento.nome += ' (A)' evento.nome += ' (A)'
@ -74,7 +75,9 @@ function formatEvents(timetable) {
name: _.split(nome, '-', 1)[0].trim(), name: _.split(nome, '-', 1)[0].trim(),
start: new Date(dataInizio), start: new Date(dataInizio),
end: new Date(dataFine), end: new Date(dataFine),
docenti: docenti.map(({ nome, cognome }) => prettyProfName(nome, cognome)), docenti: docenti.map(({ nome, cognome }) =>
prettyProfName(nome, cognome)
),
aule: aule.map(aula => prettyAulaName(aula.codice)), aule: aule.map(aula => prettyAulaName(aula.codice)),
} }
}) })
@ -188,7 +191,10 @@ const App = ({}) => {
// const [mode, setMode] = usePersistentState('orario.mode', MODE_COURSES) // const [mode, setMode] = usePersistentState('orario.mode', MODE_COURSES)
// Selection // Selection
const [selectedCourses, setSelectedCourses] = usePersistentState('selection', []) const [selectedCourses, setSelectedCourses] = usePersistentState(
'selection',
[]
)
// Menus // Menus
const [helpVisible, setHelpVisible] = useState(false) const [helpVisible, setHelpVisible] = useState(false)
@ -259,15 +265,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>

@ -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)
)
); );
} }
} }

@ -12,7 +12,15 @@ export const WEEK_DAYS = [
'Sabato', 'Sabato',
] ]
export const WEEK_DAYS_SHORT = ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'] export const WEEK_DAYS_SHORT = [
'Dom',
'Lun',
'Mar',
'Mer',
'Gio',
'Ven',
'Sab'
]
// Hashing // Hashing
@ -24,8 +32,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 +50,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 +61,7 @@ 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() + ')') .replaceAll(/\((a|b)\)/g, ({}, letter) => "(" + letter.toUpperCase() + ")")
} }
export function prettyAulaName(name) { export function prettyAulaName(name) {

Loading…
Cancel
Save