feat: new jumbotron page for dm hall screen

dev
Antonio De Lucreziis 11 months ago
parent 036a0820f9
commit babf668a2f

@ -0,0 +1,38 @@
# Architecture
This is a Typescript project that Preact for the frontend and _server-side rendering_ (custom made).
To use the project you just have to run
```bash shell
# Development
$ npm run dev
# Production
$ npm run build
$ npm run serve
```
Let's see what's inside the `package.json`
```json
...
"scripts": {
"build:client": "vite build --outDir dist/entry-client",
"build:ssr": "vite build --ssr client/entry-server.tsx --outDir dist/entry-server",
"build:server": "esbuild server.ts --bundle --platform=node --format=esm --external:./node_modules/* --outdir=dist/server",
"build": "run-s build:client build:ssr build:server",
"dev": "run-s build:server serve:dev",
"serve:dev": "MODE=development node dist/server/server.js",
"serve": "node dist/server/server.js"
},
...
```
ma vediamolo meglio come diagramma
![diagram](docs/out/package-json.svg)
- Quando sviluppiamo abbiamo la regola `dev` che prima chiama la regola `build:server` che compila il file `server.ts` che contiene il vero e proprio server in ExpressJS che gestisce tutta l'applicazione. Internamente questo _entrypoint_ usa `server/` in fase di development usa ViteJS
- Il comando `build` invece prima costruisce il client utilizzando Vite,

@ -15,6 +15,7 @@ import { ProblemPage } from './pages/ProblemPage'
import { ProfilePage } from './pages/ProfilePage' import { ProfilePage } from './pages/ProfilePage'
import { ScoresPage } from './pages/ScoresPage' import { ScoresPage } from './pages/ScoresPage'
import { UserPage } from './pages/UserPage' import { UserPage } from './pages/UserPage'
import { JumbotronPage } from './pages/JumbotronPage'
// const Redirect = ({ to }: { to: string }) => { // const Redirect = ({ to }: { to: string }) => {
// useEffect(() => { // useEffect(() => {
@ -61,6 +62,10 @@ export const App = ({ url }: { url?: string }) => {
// @ts-ignore // @ts-ignore
path={pbu('/problem/:id')} path={pbu('/problem/:id')}
/> />
<JumbotronPage
// @ts-ignore
path={pbu('/jumbotron')}
/>
<UserPage <UserPage
// @ts-ignore // @ts-ignore
path={pbu('/u/:uid')} path={pbu('/u/:uid')}

@ -0,0 +1,51 @@
import { route } from 'preact-router'
import { useContext, useEffect, useState } from 'preact/hooks'
import { isAdministrator, Problem as ProblemModel, Solution as SolutionModel } from '../../shared/model'
import { prependBaseUrl } from '../../shared/utils'
import { server } from '../api'
import { Header } from '../components/Header'
import { MarkdownEditor } from '../components/MarkdownEditor'
import { Problem } from '../components/Problem'
import { Solution } from '../components/Solution'
import { MetadataContext, useListResource, useResource, ServerContext, DatabaseContext, useServerAsyncCallback } from '../hooks'
import { useLoggedInUser } from '../hooks/useCurrentUser'
export const JumbotronPage = ({}) => {
const metadata = useContext(MetadataContext)
const db = useContext(DatabaseContext)
if (db) {
useServerAsyncCallback(async () => {
const problem = await db.getJumbotronProblem()
if (problem) {
metadata.title = `PHC Problemi | ${problem.title}`
metadata.description = problem.content
}
})
}
const [problem] = useResource<ProblemModel | null>(`/api/jumbotron`, null, problem => {
if (problem === null) {
route(prependBaseUrl(`/error?message=${encodeURIComponent(`Il problema jumbotron non esiste`)}`))
}
})
if (!problem) {
return <></>
}
const [solutions] = useListResource<SolutionModel>(`/api/solutions?problem=${problem.id}`)
return (
<>
<main class="page-jumbotron">
<div class="subtitle">
<a href="/">PHC Problemi</a>
</div>
<>{problem && <Problem {...problem} />}</>
<div class="centered">
Per ora ci sono {solutions.length} soluzion{'ie'[solutions.length === 1 ? 1 : 0]}
</div>
</main>
</>
)
}

@ -467,6 +467,15 @@ main.page-scores {
} }
} }
main.page-jumbotron {
width: 100%;
height: 100%;
justify-content: center;
zoom: 1.5;
}
// //
// Components // Components
// //

@ -0,0 +1,10 @@
.PHONY: all
all: setup out/package-json.svg
.PHONY: setup
setup:
mkdir -p out
out/package-json.svg: package-json.gv
dot -T svg -o out/package-json.svg package-json.gv

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 7.1.0 (0)
-->
<!-- Pages: 1 -->
<svg width="882pt" height="260pt"
viewBox="0.00 0.00 882.00 260.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 256)">
<polygon fill="white" stroke="none" points="-4,4 -4,-256 878,-256 878,4 -4,4"/>
<!-- build -->
<g id="node1" class="node">
<title>build</title>
<path fill="none" stroke="black" d="M42,-144C42,-144 12,-144 12,-144 6,-144 0,-138 0,-132 0,-132 0,-120 0,-120 0,-114 6,-108 12,-108 12,-108 42,-108 42,-108 48,-108 54,-114 54,-120 54,-120 54,-132 54,-132 54,-138 48,-144 42,-144"/>
<text text-anchor="middle" x="27" y="-122.3" font-family="Times,serif" font-size="14.00">build</text>
</g>
<!-- build:client -->
<g id="node2" class="node">
<title>build:client</title>
<path fill="none" stroke="black" d="M193,-198C193,-198 139,-198 139,-198 133,-198 127,-192 127,-186 127,-186 127,-174 127,-174 127,-168 133,-162 139,-162 139,-162 193,-162 193,-162 199,-162 205,-168 205,-174 205,-174 205,-186 205,-186 205,-192 199,-198 193,-198"/>
<text text-anchor="middle" x="166" y="-176.3" font-family="Times,serif" font-size="14.00">build:client</text>
</g>
<!-- build&#45;&gt;build:client -->
<g id="edge1" class="edge">
<title>build&#45;&gt;build:client</title>
<path fill="none" stroke="black" d="M54.39,-137.94C65.34,-142.78 78.22,-148.32 90,-153 98.32,-156.3 107.22,-159.66 115.89,-162.84"/>
<polygon fill="black" stroke="black" points="114.65,-166.11 125.24,-166.23 117.03,-159.53 114.65,-166.11"/>
</g>
<!-- build:ssr -->
<g id="node3" class="node">
<title>build:ssr</title>
<path fill="none" stroke="black" d="M185.5,-144C185.5,-144 146.5,-144 146.5,-144 140.5,-144 134.5,-138 134.5,-132 134.5,-132 134.5,-120 134.5,-120 134.5,-114 140.5,-108 146.5,-108 146.5,-108 185.5,-108 185.5,-108 191.5,-108 197.5,-114 197.5,-120 197.5,-120 197.5,-132 197.5,-132 197.5,-138 191.5,-144 185.5,-144"/>
<text text-anchor="middle" x="166" y="-122.3" font-family="Times,serif" font-size="14.00">build:ssr</text>
</g>
<!-- build&#45;&gt;build:ssr -->
<g id="edge2" class="edge">
<title>build&#45;&gt;build:ssr</title>
<path fill="none" stroke="black" d="M54.41,-126C73.88,-126 100.75,-126 123.13,-126"/>
<polygon fill="black" stroke="black" points="122.85,-129.5 132.85,-126 122.85,-122.5 122.85,-129.5"/>
</g>
<!-- build:server -->
<g id="node4" class="node">
<title>build:server</title>
<path fill="none" stroke="black" d="M194.5,-90C194.5,-90 137.5,-90 137.5,-90 131.5,-90 125.5,-84 125.5,-78 125.5,-78 125.5,-66 125.5,-66 125.5,-60 131.5,-54 137.5,-54 137.5,-54 194.5,-54 194.5,-54 200.5,-54 206.5,-60 206.5,-66 206.5,-66 206.5,-78 206.5,-78 206.5,-84 200.5,-90 194.5,-90"/>
<text text-anchor="middle" x="166" y="-68.3" font-family="Times,serif" font-size="14.00">build:server</text>
</g>
<!-- build&#45;&gt;build:server -->
<g id="edge3" class="edge">
<title>build&#45;&gt;build:server</title>
<path fill="none" stroke="black" d="M54.39,-114.06C65.34,-109.22 78.22,-103.68 90,-99 97.87,-95.87 106.26,-92.7 114.48,-89.68"/>
<polygon fill="black" stroke="black" points="115.49,-93.03 123.69,-86.33 113.1,-86.46 115.49,-93.03"/>
</g>
<!-- vite build &#45;&#45;outDir dist/entry&#45;client -->
<g id="node5" class="node">
<title>vite build &#45;&#45;outDir dist/entry&#45;client</title>
<path fill="none" stroke="black" d="M667,-198C667,-198 485,-198 485,-198 479,-198 473,-192 473,-186 473,-186 473,-174 473,-174 473,-168 479,-162 485,-162 485,-162 667,-162 667,-162 673,-162 679,-168 679,-174 679,-174 679,-186 679,-186 679,-192 673,-198 667,-198"/>
<text text-anchor="middle" x="576" y="-176.3" font-family="Times,serif" font-size="14.00">vite build &#45;&#45;outDir dist/entry&#45;client</text>
</g>
<!-- build:client&#45;&gt;vite build &#45;&#45;outDir dist/entry&#45;client -->
<g id="edge4" class="edge">
<title>build:client&#45;&gt;vite build &#45;&#45;outDir dist/entry&#45;client</title>
<path fill="none" stroke="black" d="M205.38,-180C263.38,-180 375.67,-180 461.33,-180"/>
<polygon fill="black" stroke="black" points="461.19,-183.5 471.19,-180 461.19,-176.5 461.19,-183.5"/>
</g>
<!-- vite build &#45;&#45;ssr client/entry&#45;server.tsx &#45;&#45;outDir dist/entry&#45;server -->
<g id="node6" class="node">
<title>vite build &#45;&#45;ssr client/entry&#45;server.tsx &#45;&#45;outDir dist/entry&#45;server</title>
<path fill="none" stroke="black" d="M743,-144C743,-144 409,-144 409,-144 403,-144 397,-138 397,-132 397,-132 397,-120 397,-120 397,-114 403,-108 409,-108 409,-108 743,-108 743,-108 749,-108 755,-114 755,-120 755,-120 755,-132 755,-132 755,-138 749,-144 743,-144"/>
<text text-anchor="middle" x="576" y="-122.3" font-family="Times,serif" font-size="14.00">vite build &#45;&#45;ssr client/entry&#45;server.tsx &#45;&#45;outDir dist/entry&#45;server</text>
</g>
<!-- build:ssr&#45;&gt;vite build &#45;&#45;ssr client/entry&#45;server.tsx &#45;&#45;outDir dist/entry&#45;server -->
<g id="edge5" class="edge">
<title>build:ssr&#45;&gt;vite build &#45;&#45;ssr client/entry&#45;server.tsx &#45;&#45;outDir dist/entry&#45;server</title>
<path fill="none" stroke="black" d="M197.67,-126C238.3,-126 313.32,-126 385.83,-126"/>
<polygon fill="black" stroke="black" points="385.46,-129.5 395.46,-126 385.46,-122.5 385.46,-129.5"/>
</g>
<!-- esbuild server.ts &#45;&#45;bundle &#45;&#45;platform=node &#45;&#45;format=esm &#45;&#45;external:./node_modules/* &#45;&#45;outdir=dist/server -->
<g id="node7" class="node">
<title>esbuild server.ts &#45;&#45;bundle &#45;&#45;platform=node &#45;&#45;format=esm &#45;&#45;external:./node_modules/* &#45;&#45;outdir=dist/server</title>
<path fill="none" stroke="black" d="M862,-90C862,-90 290,-90 290,-90 284,-90 278,-84 278,-78 278,-78 278,-66 278,-66 278,-60 284,-54 290,-54 290,-54 862,-54 862,-54 868,-54 874,-60 874,-66 874,-66 874,-78 874,-78 874,-84 868,-90 862,-90"/>
<text text-anchor="middle" x="576" y="-68.3" font-family="Times,serif" font-size="14.00">esbuild server.ts &#45;&#45;bundle &#45;&#45;platform=node &#45;&#45;format=esm &#45;&#45;external:./node_modules/* &#45;&#45;outdir=dist/server</text>
</g>
<!-- build:server&#45;&gt;esbuild server.ts &#45;&#45;bundle &#45;&#45;platform=node &#45;&#45;format=esm &#45;&#45;external:./node_modules/* &#45;&#45;outdir=dist/server -->
<g id="edge6" class="edge">
<title>build:server&#45;&gt;esbuild server.ts &#45;&#45;bundle &#45;&#45;platform=node &#45;&#45;format=esm &#45;&#45;external:./node_modules/* &#45;&#45;outdir=dist/server</title>
<path fill="none" stroke="black" d="M206.86,-72C223.15,-72 243.53,-72 266.29,-72"/>
<polygon fill="black" stroke="black" points="266.14,-75.5 276.14,-72 266.14,-68.5 266.14,-75.5"/>
</g>
<!-- dev -->
<g id="node8" class="node">
<title>dev</title>
<path fill="none" stroke="black" d="M42,-63C42,-63 12,-63 12,-63 6,-63 0,-57 0,-51 0,-51 0,-39 0,-39 0,-33 6,-27 12,-27 12,-27 42,-27 42,-27 48,-27 54,-33 54,-39 54,-39 54,-51 54,-51 54,-57 48,-63 42,-63"/>
<text text-anchor="middle" x="27" y="-41.3" font-family="Times,serif" font-size="14.00">dev</text>
</g>
<!-- dev&#45;&gt;build:server -->
<g id="edge7" class="edge">
<title>dev&#45;&gt;build:server</title>
<path fill="none" stroke="black" d="M54.41,-50.21C71.26,-53.53 93.66,-57.94 113.88,-61.93"/>
<polygon fill="black" stroke="black" points="113.19,-65.36 123.68,-63.86 114.55,-58.49 113.19,-65.36"/>
</g>
<!-- serve:dev -->
<g id="node9" class="node">
<title>serve:dev</title>
<path fill="none" stroke="black" d="M188,-36C188,-36 144,-36 144,-36 138,-36 132,-30 132,-24 132,-24 132,-12 132,-12 132,-6 138,0 144,0 144,0 188,0 188,0 194,0 200,-6 200,-12 200,-12 200,-24 200,-24 200,-30 194,-36 188,-36"/>
<text text-anchor="middle" x="166" y="-14.3" font-family="Times,serif" font-size="14.00">serve:dev</text>
</g>
<!-- dev&#45;&gt;serve:dev -->
<g id="edge8" class="edge">
<title>dev&#45;&gt;serve:dev</title>
<path fill="none" stroke="black" d="M54.41,-39.79C73.15,-36.1 98.75,-31.06 120.6,-26.75"/>
<polygon fill="black" stroke="black" points="121.01,-30.24 130.14,-24.87 119.66,-23.37 121.01,-30.24"/>
</g>
<!-- MODE=development node dist/server/server.js -->
<g id="node10" class="node">
<title>MODE=development node dist/server/server.js</title>
<path fill="none" stroke="black" d="M701.5,-36C701.5,-36 450.5,-36 450.5,-36 444.5,-36 438.5,-30 438.5,-24 438.5,-24 438.5,-12 438.5,-12 438.5,-6 444.5,0 450.5,0 450.5,0 701.5,0 701.5,0 707.5,0 713.5,-6 713.5,-12 713.5,-12 713.5,-24 713.5,-24 713.5,-30 707.5,-36 701.5,-36"/>
<text text-anchor="middle" x="576" y="-14.3" font-family="Times,serif" font-size="14.00">MODE=development node dist/server/server.js</text>
</g>
<!-- serve:dev&#45;&gt;MODE=development node dist/server/server.js -->
<g id="edge9" class="edge">
<title>serve:dev&#45;&gt;MODE=development node dist/server/server.js</title>
<path fill="none" stroke="black" d="M200.39,-18C249.65,-18 344.84,-18 426.93,-18"/>
<polygon fill="black" stroke="black" points="426.91,-21.5 436.91,-18 426.91,-14.5 426.91,-21.5"/>
</g>
<!-- serve -->
<g id="node11" class="node">
<title>serve</title>
<path fill="none" stroke="black" d="M42,-252C42,-252 12,-252 12,-252 6,-252 0,-246 0,-240 0,-240 0,-228 0,-228 0,-222 6,-216 12,-216 12,-216 42,-216 42,-216 48,-216 54,-222 54,-228 54,-228 54,-240 54,-240 54,-246 48,-252 42,-252"/>
<text text-anchor="middle" x="27" y="-230.3" font-family="Times,serif" font-size="14.00">serve</text>
</g>
<!-- node dist/server/server.js -->
<g id="node12" class="node">
<title>node dist/server/server.js</title>
<path fill="none" stroke="black" d="M230,-252C230,-252 102,-252 102,-252 96,-252 90,-246 90,-240 90,-240 90,-228 90,-228 90,-222 96,-216 102,-216 102,-216 230,-216 230,-216 236,-216 242,-222 242,-228 242,-228 242,-240 242,-240 242,-246 236,-252 230,-252"/>
<text text-anchor="middle" x="166" y="-230.3" font-family="Times,serif" font-size="14.00">node dist/server/server.js</text>
</g>
<!-- serve&#45;&gt;node dist/server/server.js -->
<g id="edge10" class="edge">
<title>serve&#45;&gt;node dist/server/server.js</title>
<path fill="none" stroke="black" d="M54.41,-234C61.55,-234 69.68,-234 78.22,-234"/>
<polygon fill="black" stroke="black" points="78.12,-237.5 88.12,-234 78.12,-230.5 78.12,-237.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

@ -0,0 +1,12 @@
digraph {
rankdir=LR;
node [shape=box, style=rounded];
"build" -> { "build:client", "build:ssr", "build:server" }
"build:client" -> "vite build --outDir dist/entry-client"
"build:ssr" -> "vite build --ssr client/entry-server.tsx --outDir dist/entry-server"
"build:server" -> "esbuild server.ts --bundle --platform=node --format=esm --external:./node_modules/* --outdir=dist/server"
"dev" -> { "build:server", "serve:dev" }
"serve:dev" -> "MODE=development node dist/server/server.js"
"serve" -> "node dist/server/server.js"
}

@ -65,6 +65,9 @@ export function createDatabaseWrapper(db: DatabaseInternal): DatabaseConnection
getProblem(id: string): Promise<Problem | null> { getProblem(id: string): Promise<Problem | null> {
return getProblem(db, id) return getProblem(db, id)
}, },
getJumbotronProblem(): Promise<Problem | null> {
return getJumbotronProblem(db)
},
} }
} }
@ -145,6 +148,18 @@ export const getProblem = (db: DatabaseInternal, id: string): Promise<Problem |
return problem && !problem.deleted ? problem : null return problem && !problem.deleted ? problem : null
}) })
export const getJumbotronProblem = (db: DatabaseInternal): Promise<Problem | null> =>
withDatabase(db, state => {
const mostRecentProblems = Object.values(state.problems)
.filter(p => !p.deleted)
.sort((p1, p2) => new Date(p2.createdAt).getTime() - new Date(p1.createdAt).getTime())
.slice(0, 3)
if (mostRecentProblems.length === 0) return null
return mostRecentProblems[Math.floor(Math.random() * mostRecentProblems.length)]
})
export const getProblems = (db: DatabaseInternal): Promise<Problem[]> => export const getProblems = (db: DatabaseInternal): Promise<Problem[]> =>
withDatabase(db, state => { withDatabase(db, state => {
return Object.values(state.problems).filter(p => !p.deleted) return Object.values(state.problems).filter(p => !p.deleted)

@ -1,5 +1,5 @@
import { UserId } from '../../shared/model' import { UserId } from '../../shared/model'
import { Database } from './database' import { Database } from '../../shared/database'
export const initialDatabaseValue: Database = { export const initialDatabaseValue: Database = {
users: {}, users: {},

@ -14,6 +14,7 @@ import {
createSolution, createSolution,
deleteProblem, deleteProblem,
deleteSolution, deleteSolution,
getJumbotronProblem,
getProblem, getProblem,
getProblems, getProblems,
getSolution, getSolution,
@ -140,6 +141,11 @@ export async function createApiRouter(): Promise<[Router, DatabaseConnection]> {
res.json(users) res.json(users)
}) })
r.get('/api/jumbotron', async (req, res) => {
const problem = await getJumbotronProblem(db)
res.json(problem)
})
r.get('/api/problems', async (req, res) => { r.get('/api/problems', async (req, res) => {
type ProblemWithSolutionsCount = ProblemModel & { solutionsCount?: number } type ProblemWithSolutionsCount = ProblemModel & { solutionsCount?: number }

@ -2,6 +2,7 @@ import { Problem, Solution, User } from './model'
export type DatabaseConnection = { export type DatabaseConnection = {
getProblem(id: string): Promise<Problem | null> getProblem(id: string): Promise<Problem | null>
getJumbotronProblem(): Promise<Problem | null>
} }
export type Database = { export type Database = {

Loading…
Cancel
Save