From babf668a2fa908f353f72df2185a82adcf8b3044 Mon Sep 17 00:00:00 2001 From: Antonio De Lucreziis Date: Sat, 2 Dec 2023 18:24:05 +0100 Subject: [PATCH] feat: new jumbotron page for dm hall screen --- ARCHITECTURE.md | 38 +++++++++ client/App.tsx | 5 ++ client/pages/JumbotronPage.tsx | 51 ++++++++++++ client/styles/main.scss | 9 +++ docs/Makefile | 10 +++ docs/out/package-json.svg | 144 +++++++++++++++++++++++++++++++++ docs/package-json.gv | 12 +++ server/db/database.ts | 15 ++++ server/db/example-data.ts | 2 +- server/routes.ts | 6 ++ shared/database.ts | 1 + 11 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 ARCHITECTURE.md create mode 100644 client/pages/JumbotronPage.tsx create mode 100644 docs/Makefile create mode 100644 docs/out/package-json.svg create mode 100644 docs/package-json.gv diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..4cdbbfb --- /dev/null +++ b/ARCHITECTURE.md @@ -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, \ No newline at end of file diff --git a/client/App.tsx b/client/App.tsx index 7280eeb..db8c00d 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -15,6 +15,7 @@ import { ProblemPage } from './pages/ProblemPage' import { ProfilePage } from './pages/ProfilePage' import { ScoresPage } from './pages/ScoresPage' import { UserPage } from './pages/UserPage' +import { JumbotronPage } from './pages/JumbotronPage' // const Redirect = ({ to }: { to: string }) => { // useEffect(() => { @@ -61,6 +62,10 @@ export const App = ({ url }: { url?: string }) => { // @ts-ignore path={pbu('/problem/:id')} /> + { + 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(`/api/jumbotron`, null, problem => { + if (problem === null) { + route(prependBaseUrl(`/error?message=${encodeURIComponent(`Il problema jumbotron non esiste`)}`)) + } + }) + + if (!problem) { + return <> + } + + const [solutions] = useListResource(`/api/solutions?problem=${problem.id}`) + + return ( + <> +
+ + <>{problem && } +
+ Per ora ci sono {solutions.length} soluzion{'ie'[solutions.length === 1 ? 1 : 0]} +
+
+ + ) +} diff --git a/client/styles/main.scss b/client/styles/main.scss index 459c357..3cfcbeb 100644 --- a/client/styles/main.scss +++ b/client/styles/main.scss @@ -467,6 +467,15 @@ main.page-scores { } } +main.page-jumbotron { + width: 100%; + height: 100%; + + justify-content: center; + + zoom: 1.5; +} + // // Components // diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..f30e24e --- /dev/null +++ b/docs/Makefile @@ -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 \ No newline at end of file diff --git a/docs/out/package-json.svg b/docs/out/package-json.svg new file mode 100644 index 0000000..01243ce --- /dev/null +++ b/docs/out/package-json.svg @@ -0,0 +1,144 @@ + + + + + + + + + +build + +build + + + +build:client + +build:client + + + +build->build:client + + + + + +build:ssr + +build:ssr + + + +build->build:ssr + + + + + +build:server + +build:server + + + +build->build:server + + + + + +vite build --outDir dist/entry-client + +vite build --outDir dist/entry-client + + + +build:client->vite build --outDir dist/entry-client + + + + + +vite build --ssr client/entry-server.tsx --outDir dist/entry-server + +vite build --ssr client/entry-server.tsx --outDir dist/entry-server + + + +build:ssr->vite build --ssr client/entry-server.tsx --outDir dist/entry-server + + + + + +esbuild server.ts --bundle --platform=node --format=esm --external:./node_modules/* --outdir=dist/server + +esbuild server.ts --bundle --platform=node --format=esm --external:./node_modules/* --outdir=dist/server + + + +build:server->esbuild server.ts --bundle --platform=node --format=esm --external:./node_modules/* --outdir=dist/server + + + + + +dev + +dev + + + +dev->build:server + + + + + +serve:dev + +serve:dev + + + +dev->serve:dev + + + + + +MODE=development node dist/server/server.js + +MODE=development node dist/server/server.js + + + +serve:dev->MODE=development node dist/server/server.js + + + + + +serve + +serve + + + +node dist/server/server.js + +node dist/server/server.js + + + +serve->node dist/server/server.js + + + + + diff --git a/docs/package-json.gv b/docs/package-json.gv new file mode 100644 index 0000000..1e0734d --- /dev/null +++ b/docs/package-json.gv @@ -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" +} diff --git a/server/db/database.ts b/server/db/database.ts index 9730771..e268df9 100644 --- a/server/db/database.ts +++ b/server/db/database.ts @@ -65,6 +65,9 @@ export function createDatabaseWrapper(db: DatabaseInternal): DatabaseConnection getProblem(id: string): Promise { return getProblem(db, id) }, + getJumbotronProblem(): Promise { + return getJumbotronProblem(db) + }, } } @@ -145,6 +148,18 @@ export const getProblem = (db: DatabaseInternal, id: string): Promise => + 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 => withDatabase(db, state => { return Object.values(state.problems).filter(p => !p.deleted) diff --git a/server/db/example-data.ts b/server/db/example-data.ts index 0da67bd..71a35fb 100644 --- a/server/db/example-data.ts +++ b/server/db/example-data.ts @@ -1,5 +1,5 @@ import { UserId } from '../../shared/model' -import { Database } from './database' +import { Database } from '../../shared/database' export const initialDatabaseValue: Database = { users: {}, diff --git a/server/routes.ts b/server/routes.ts index 426e001..f00a186 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -14,6 +14,7 @@ import { createSolution, deleteProblem, deleteSolution, + getJumbotronProblem, getProblem, getProblems, getSolution, @@ -140,6 +141,11 @@ export async function createApiRouter(): Promise<[Router, DatabaseConnection]> { 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) => { type ProblemWithSolutionsCount = ProblemModel & { solutionsCount?: number } diff --git a/shared/database.ts b/shared/database.ts index 1332f34..2d22011 100644 --- a/shared/database.ts +++ b/shared/database.ts @@ -2,6 +2,7 @@ import { Problem, Solution, User } from './model' export type DatabaseConnection = { getProblem(id: string): Promise + getJumbotronProblem(): Promise } export type Database = {