prototype mostly finished

feat-domande-esami
Antonio De Lucreziis 1 month ago
parent 7c36f87149
commit 4757c3e0a9

@ -18,6 +18,11 @@ export default defineConfig({
theme: 'github-light', theme: 'github-light',
}, },
}, },
integrations: [preact(), mdx()], integrations: [
preact({
compat: true,
}),
mdx(),
],
output: 'static', output: 'static',
}) })

Binary file not shown.

@ -21,6 +21,7 @@
"@fontsource/source-sans-pro": "^5.0.8", "@fontsource/source-sans-pro": "^5.0.8",
"@fontsource/space-mono": "^5.0.20", "@fontsource/space-mono": "^5.0.20",
"@phosphor-icons/core": "^2.1.1", "@phosphor-icons/core": "^2.1.1",
"@phosphor-icons/react": "^2.1.7",
"@preact/signals": "^1.3.0", "@preact/signals": "^1.3.0",
"@types/jsdom": "^21.1.7", "@types/jsdom": "^21.1.7",
"astro": "5.1.0", "astro": "5.1.0",

@ -0,0 +1,116 @@
import { useEffect, useState } from 'preact/hooks'
import { Funnel } from '@phosphor-icons/react'
import { marked } from 'marked'
import type { Database } from '@/data/domande-esami.yaml'
const useRemoteValue = <T,>(url: string): T | null => {
const [value, setValue] = useState<T | null>(null)
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(value => setValue(value))
}, [url])
return value
}
type Props = {
course: string
}
export const DomandeEsamiCourse = ({ course }: Props) => {
const database = useRemoteValue<Database>(`/domande-esami/api/${course}.json`)
if (!database) {
return <>Loading...</>
}
// @ts-ignore
requestIdleCallback(() => window.renderMath())
console.log(database)
const courseTags = [
...new Set(
database.questions
.filter(question => question.course === course)
.flatMap(question => question.tags)
),
]
const [selectedTags, setSelectedTags] = useState<string[]>([])
const filteredQuestions = database.questions
.filter(question => question.course === course)
.filter(
question => selectedTags.length === 0 || selectedTags.every(tag => question.tags.includes(tag))
)
return (
<>
<div class="grid-center">
<h3>
<a href="/domande-esami">Domande Esami</a>
</h3>
<h1>{database.names[course]}</h1>
</div>
<div class="card filter">
<div class="grid-h">
<Funnel />
<strong>Filtra Tag</strong>
</div>
<div class="flex-row-wrap">
{selectedTags.length === 0
? courseTags.map(tag => (
<div class="chip clickable" onClick={() => setSelectedTags([tag])}>
{tag}
</div>
))
: courseTags.map(tag => (
<div
class={
selectedTags.includes(tag)
? 'chip clickable'
: 'chip clickable disabled'
}
onClick={() =>
setSelectedTags(
selectedTags.includes(tag)
? selectedTags.filter(t => t !== tag)
: [...selectedTags, tag]
)
}
>
{tag}
</div>
))}
</div>
</div>
<div class="wide-card-list" id="questions">
{filteredQuestions.length === 0 ? (
<div class="grid-center">
<em>No questions found</em>
</div>
) : (
filteredQuestions.map(question => (
<div class="card">
<div
class="text"
dangerouslySetInnerHTML={{
__html: marked(question.content, { async: false }),
}}
/>
<div class="metadata">
{question.tags.map(tag => (
<div class="chip small">{tag}</div>
))}
</div>
</div>
))
)}
</div>
</>
)
}

@ -21,7 +21,7 @@ names:
groups: groups:
- id: triennale-anno-1 - id: triennale-anno-1
name: Triennale - Anno I name: I Anno Triennale
items: items:
- aritmetica - aritmetica
- geometria-1 - geometria-1
@ -29,14 +29,14 @@ groups:
- algebra-1 - algebra-1
- id: triennale-anno-2 - id: triennale-anno-2
name: Triennale - Anno II name: II Anno Triennale
items: items:
- geometria-2 - geometria-2
- analisi-2 - analisi-2
- algebra-1 - algebra-1
- id: triennale-anno-3 - id: triennale-anno-3
name: Triennale - Anno III name: III Anno Triennale
items: items:
- ricerca-operativa - ricerca-operativa
- fisica-3 - fisica-3
@ -60,101 +60,142 @@ groups:
questions: questions:
- course: geometria-1 - course: geometria-1
year: 2024
content: | content: |
Dimostrare il teorema spettrale reale Dimostrare il teorema spettrale reale
tags:
- 2024
- course: gtd - course: gtd
year: 2024
content: | content: |
Calcolare la curvatura gaussiana di una sfera di raggio $r$ Calcolare la curvatura gaussiana di una sfera di raggio $r$
tags:
- 2024
- course: geometria-1 - course: geometria-1
year: 2023
content: | content: |
Dimostrare che _l'area_ di un cerchio è $A = \pi r^2$ Dimostrare che _l'area_ di un cerchio è $A = \pi r^2$
tags:
- 2023
- course: algebra-1 - course: algebra-1
year: 2024
content: | content: |
Dimostrare il teorema di Sylow Dimostrare il teorema di Sylow
tags:
- 2024
- course: algebra-1 - course: algebra-1
year: 2023
content: | content: |
Dimostrare che ogni sottogruppo di un gruppo ciclico è ciclico Dimostrare che ogni sottogruppo di un gruppo ciclico è ciclico
tags:
- 2023
- course: analisi-1 - course: analisi-1
year: 2024
content: | content: |
Dimostrare il teorema di Weierstrass Dimostrare il teorema di Weierstrass
tags:
- 2024
- course: analisi-1 - course: analisi-1
year: 2023
content: | content: |
Dimostrare che se $f$ è continua su $[a,b]$, allora $f$ è limitata Dimostrare che se $f$ è continua su $[a,b]$, allora $f$ è limitata
tags:
- 2023
- course: analisi-2 - course: analisi-2
year: 2024
content: | content: |
Dimostrare il teorema di Stokes Dimostrare il teorema di Stokes
tags:
- 2024
- course: analisi-2 - course: analisi-2
year: 2023
content: | content: |
Dimostrare il teorema di Green Dimostrare il teorema di Green
tags:
- 2023
- course: analisi-3 - course: analisi-3
year: 2024
content: | content: |
Dimostrare il teorema di Cauchy Dimostrare il teorema di Cauchy
tags:
- 2024
- course: analisi-3 - course: analisi-3
year: 2023
content: | content: |
Dimostrare il teorema di Liouville Dimostrare il teorema di Liouville
tags:
- 2023
- course: algebra-2 - course: algebra-2
year: 2024
content: | content: |
Dimostrare il teorema di struttura per i gruppi abeliani finiti Dimostrare il teorema di struttura per i gruppi abeliani finiti
tags:
- 2024
- course: algebra-2 - course: algebra-2
year: 2023
content: | content: |
Dimostrare il teorema di struttura per i gruppi finiti Dimostrare il teorema di struttura per i gruppi finiti
tags:
- 2023
- course: ricerca-operativa - course: ricerca-operativa
year: 2024
content: | content: |
Dimostrare il teorema di dualità per i problemi di programmazione lineare Dimostrare il teorema di dualità per i problemi di programmazione lineare
tags:
- 2024
- course: geometria-topologia-differenziale - course: geometria-topologia-differenziale
year: 2024
content: | content: |
Dimostrare il teorema di Poincaré-Hopf Dimostrare il teorema di Poincaré-Hopf
tags:
- 2024
- course: analisi-armonica - course: analisi-armonica
year: 2024
content: | content: |
Dimostrare il teorema di Hahn-Banach Dimostrare il teorema di Hahn-Banach
tags:
- 2024
- course: analisi-complessa - course: analisi-complessa
year: 2024
content: | content: |
Dimostrare il teorema di Morera Dimostrare il teorema di Morera
tags:
- 2024
- course: analisi-complessa - course: analisi-complessa
year: 2023
content: | content: |
Dimostrare il teorema degli zeri di Weierstrass Dimostrare il teorema degli zeri di Weierstrass
tags:
- 2023
- course: analisi-complessa - course: analisi-complessa
year: 2022
content: | content: |
Dimostrare il teorema della mappa aperta Dimostrare il teorema della mappa aperta
tags:
- 2022
- course: analisi-complessa - course: analisi-complessa
year: 2021
content: | content: |
Dimostrare il teorema della mappa aperta Dimostrare il teorema della mappa aperta
tags:
- 2021
- course: geometria-2
content: |
Dimostrare il teorema di Van Kampen
tags:
- 2024
- Frigerio
- course: geometria-2
content: |
Definizione di gruppo fondamentale
tags:
- 2023
- Frigerio
- course: geometria-2
content: |
Dimostrare il teorema Cauchy
tags:
- 2022
- Abate

17
src/files.d.ts vendored

@ -17,21 +17,24 @@ declare module '@/data/macchinisti.yaml' {
} }
declare module '@/data/domande-esami.yaml' { declare module '@/data/domande-esami.yaml' {
type Question = { export type Question = {
course: string course: string
year: number
content: string content: string
tags: string[]
} }
type Group = { export type Group = {
id: string id: string
name: string name: string
items: Array<string> items: Array<string>
} }
const names: Record<string, string> export type Database = {
const groups: Group[] names: Record<string, string>
const questions: Question[] groups: Group[]
questions: Question[]
}
export { names, groups, questions } const value: Database
export default value
} }

@ -44,7 +44,7 @@ const { title, description, thumbnail, pageTags } = Astro.props
<script> <script>
import renderMathInElement from 'katex/contrib/auto-render' import renderMathInElement from 'katex/contrib/auto-render'
document.addEventListener('DOMContentLoaded', function () { const renderMath = () => {
renderMathInElement(document.body, { renderMathInElement(document.body, {
delimiters: [ delimiters: [
{ left: '$$', right: '$$', display: true }, { left: '$$', right: '$$', display: true },
@ -54,7 +54,12 @@ const { title, description, thumbnail, pageTags } = Astro.props
], ],
throwOnError: false, throwOnError: false,
}) })
}) }
document.addEventListener('DOMContentLoaded', () => renderMath())
// @ts-ignore
window.renderMath = renderMath
</script> </script>
<script is:inline> <script is:inline>

@ -4,11 +4,11 @@ import BaseLayout from '@/layouts/BaseLayout.astro'
import Footer from '@/components/Footer.astro' import Footer from '@/components/Footer.astro'
import Header from '@/components/Header.astro' import Header from '@/components/Header.astro'
import { PhosphorIcon } from '@/client/Icon'
import { marked } from 'marked' import { marked } from 'marked'
import database from '@/data/domande-esami.yaml' import database from '@/data/domande-esami.yaml'
import PhosphorIcon from '@/components/PhosphorIcon.astro'
import { DomandeEsamiCourse } from '@/client/DomandeEsamiCourse'
export const getStaticPaths = (() => { export const getStaticPaths = (() => {
return Object.keys(database.names).map(course => ({ return Object.keys(database.names).map(course => ({
@ -22,29 +22,7 @@ const { course } = Astro.params
<BaseLayout title="Domande Esami | PHC" pageTags={'domande-esami'}> <BaseLayout title="Domande Esami | PHC" pageTags={'domande-esami'}>
<Header /> <Header />
<main> <main>
<h1>{course}</h1> <DomandeEsamiCourse client:load course={course} />
<div class="search">
<input type="text" placeholder="Cerca una domanda per un esame..." />
<PhosphorIcon client:load name="magnifying-glass" />
</div>
<div class="wide-card-list">
{
database.questions
.filter(question => question.course === course)
.map(question => (
<div class="card">
<div class="text">
<Fragment set:html={marked(question.content)} />
</div>
<div class="metadata">
<div class="chip">{question.year}</div>
</div>
</div>
))
}
</div>
</main> </main>
<Footer /> <Footer />
</BaseLayout> </BaseLayout>

@ -0,0 +1,11 @@
import type { APIRoute } from 'astro'
import database from '@/data/domande-esami.yaml'
export const GET: APIRoute = ({}) => {
return new Response(JSON.stringify(database), {
headers: {
'content-type': 'application/json',
},
})
}

@ -0,0 +1,24 @@
import type { APIRoute, GetStaticPaths } from 'astro'
import database from '@/data/domande-esami.yaml'
export const getStaticPaths = (() => {
return Object.keys(database.names).map(course => ({
params: { course },
}))
}) satisfies GetStaticPaths
export const GET: APIRoute = ({ params: { course } }) => {
return new Response(
JSON.stringify({
groups: [],
names: Object.fromEntries(Object.entries(database.names).filter(([key]) => key === course)),
questions: database.questions.filter(question => question.course === course),
}),
{
headers: {
'content-type': 'application/json',
},
}
)
}

@ -18,46 +18,42 @@ const courseQuestionCounts = Object.fromEntries(
<Header /> <Header />
<main> <main>
<h1>Domande Esami</h1> <h1>Domande Esami</h1>
<div class="card large">
<div class="text">
<p>
[Copilot] Benvenuto nella sezione delle domande d'esame! Qui puoi trovare domande di esami
passati per prepararti al meglio per il tuo prossimo esame.
</p>
<ul>
<li><a href="#triennale-anno-1">Triennale - Anno I</a></li>
<li><a href="#triennale-anno-2">Triennale - Anno II</a></li>
<li><a href="#triennale-anno-3">Triennale - Anno III</a></li>
<li><a href="#istituzioni">Istituzioni</a></li>
<li><a href="#other">Esami a Scelta</a></li>
</ul>
<p>
Se volessi contribuire con nuove domande, puoi <a href="#">facendo una pull request</a>,
inviandole al
<a href="#">form domande</a> oppure <a href="#">mandandoci una email</a>.
</p>
</div>
</div>
{ {
database.groups.map(group => ( database.groups.map(group => (
<> <details open>
<h2 id={group.id}>{group.name}</h2> <summary>
<h2 id={group.id}>
<div class="details-closed">
<PhosphorIcon name="caret-down" />
</div>
<div class="details-openned">
<PhosphorIcon name="caret-up" />
</div>
{group.name}
</h2>
</summary>
<div class="wide-card-list"> <div class="wide-card-list">
{group.items.map(course => ( {group.items.map(course => (
<a href={`/domande-esami/${course}`}> <a href={`/domande-esami/${course}`}>
<div class="card"> <div class="card">
<h2>{database.names[course]}</h2> <h2>{database.names[course]}</h2>
<div class="text"> <div class="text">
<p>{courseQuestionCounts[course] || 0} domande disponibili</p> <p>{courseQuestionCounts[course] || 0} domande</p>
</div> </div>
</div> </div>
</a> </a>
))} ))}
</div> </div>
</> </details>
)) ))
} }
<h3>Come Contribuire</h3>
<div class="card large">
<div class="text">
<p>Se hai raccolte di domande da condividere, puoi...</p>
</div>
</div>
</main> </main>
<Footer /> <Footer />
</BaseLayout> </BaseLayout>

@ -14,7 +14,7 @@
width: 22px; width: 22px;
height: 22px; height: 22px;
display: grid; display: grid inline;
place-content: center; place-content: center;
} }
@ -79,29 +79,6 @@
} }
} }
.flex-column {
display: flex;
flex-direction: column;
gap: 1rem;
}
.flex-row {
display: flex;
flex-direction: row;
gap: 1rem;
}
// just to know for reference
.fake-masonry {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
grid-auto-flow: dense;
& > * {
grid-row: span var(--masonry-height);
}
}
.search-results { .search-results {
width: 100%; width: 100%;
@ -597,17 +574,6 @@
grid-auto-flow: column; grid-auto-flow: column;
justify-content: start; justify-content: start;
gap: 0.5rem; gap: 0.5rem;
.chip {
background: #0003;
font-size: 13px;
padding: 0 0.25rem;
border-radius: 0.25rem;
line-height: 1.5;
font-weight: 600;
}
} }
@media screen and (max-width: $screen-desktop-min) { @media screen and (max-width: $screen-desktop-min) {
@ -627,6 +593,33 @@
} }
} }
.chip {
user-select: none;
display: grid;
place-content: center;
place-items: center;
background: #0003;
padding: 0 0.25rem;
border-radius: 0.25rem;
line-height: 1.5;
font-size: 16px;
font-weight: 500;
&.small {
font-size: 13px;
font-weight: 600;
}
&.disabled {
color: #0005;
background: #0002;
}
}
a:has(> .card) { a:has(> .card) {
display: contents; display: contents;
} }
@ -908,6 +901,10 @@
justify-content: center; justify-content: center;
.text > * {
max-width: none;
}
@media screen and (max-width: $screen-desktop-min) { @media screen and (max-width: $screen-desktop-min) {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@ -915,4 +912,68 @@
gap: 1rem; gap: 1rem;
} }
} }
.filter {
min-width: 15rem;
}
.flex-column {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.flex-row {
display: flex;
flex-direction: row;
gap: 0.5rem;
}
.flex-row-wrap {
display: flex;
flex-direction: row;
gap: 0.5rem;
flex-wrap: wrap;
}
.grid-h {
display: grid;
grid-auto-flow: column;
justify-content: start;
align-items: center;
gap: 0.5rem;
}
.grid-v {
display: grid;
justify-items: start;
align-content: start;
grid-auto-flow: row;
gap: 0.5rem;
}
.grid-center {
display: grid;
place-content: center;
place-items: center;
gap: 0.5rem;
grid-auto-flow: row;
}
.clickable {
cursor: pointer;
}
// just to know for reference
.fake-masonry {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
grid-auto-flow: dense;
& > * {
grid-row: span var(--masonry-height);
}
}
} }

@ -129,4 +129,47 @@
justify-self: center; justify-self: center;
} }
} }
details {
width: 100%;
summary {
display: grid;
place-content: center;
place-items: center;
list-style: none;
&::-webkit-details-marker {
display: none;
}
}
.details-openned,
.details-closed {
display: contents;
}
&:not([open]) {
.details-openned {
display: none;
}
}
&[open] {
summary {
padding-bottom: 1rem;
}
.details-closed {
display: none;
}
}
@media screen and (max-width: $screen-desktop-min) {
summary {
place-content: stretch;
place-items: stretch;
}
}
}
} }

@ -208,6 +208,10 @@ body {
@media screen and (max-width: $screen-desktop-min) { @media screen and (max-width: $screen-desktop-min) {
padding: 3rem 1rem; padding: 3rem 1rem;
gap: 3rem; gap: 3rem;
.card {
width: 100%;
}
} }
} }

@ -33,6 +33,18 @@
@layer typography { @layer typography {
@include geometric-headings; @include geometric-headings;
strong {
font-weight: 600;
}
em {
font-style: italic;
}
.text-center {
text-align: center;
}
.text { .text {
// text-align: justify; // text-align: justify;
// hyphens: auto; // hyphens: auto;

Loading…
Cancel
Save