first commit

This commit is contained in:
didier amyot committed 2023-11-20 07:38:50 -05:00
commit 22489242c2
7 files changed
+326

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
.idea
.idea
+1
View File
@@ -0,0 +1 @@
{}
+3
View File
@@ -0,0 +1,3 @@
export function verifyAuth(auth: string): boolean{
return auth.length === 4;
}
+70
View File
@@ -0,0 +1,70 @@
/**
* This part of the code is based on the way `fresh` is bundling the code.
* See https://github.com/denoland/fresh/blob/main/src/server/bundle.ts
*/
// @deno-types="https://deno.land/x/esbuild@v0.14.51/mod.d.ts"
import * as esbuildNative from "https://deno.land/x/esbuild@v0.14.51/mod.js";
import * as esbuildWasm from "https://deno.land/x/esbuild@v0.14.51/wasm.js";
import { denoPlugin } from "https://deno.land/x/esbuild_deno_loader@0.5.2/mod.ts";
const esbuild: typeof esbuildWasm = Deno.run === undefined
? esbuildWasm
: esbuildNative;
let esbuildInitialized = false;
async function verifyEsbuildInitialized() {
if (esbuildInitialized) {
return;
}
console.log(new Date(), "start initializing esbuild");
if (Deno.run === undefined) {
const wasmURL = new URL("./esbuild@v0.14.51.wasm", import.meta.url).href;
const resp = await fetch(wasmURL);
const wasm = new Response(resp.body, {
headers: { "Content-Type": "application/wasm" },
});
const wasmModule = await WebAssembly.compileStreaming(wasm);
await esbuild.initialize({
wasmModule,
worker: false,
});
} else {
await esbuild.initialize({});
}
esbuildInitialized = true;
console.log(new Date(), "esbuild initialized");
}
export async function createBundle(
entryPoint: URL,
importMapURL: URL,
isDev?: boolean,
) {
await verifyEsbuildInitialized();
console.log(new Date(), "Bundling", entryPoint.href);
const absWorkingDir = Deno.cwd();
const res = await esbuild.build({
bundle: true,
entryPoints: [entryPoint.href],
platform: "neutral",
treeShaking: true,
minify: !isDev,
sourcemap: isDev ? "inline" : false,
outfile: "",
absWorkingDir,
outdir: ".",
target: ["chrome99", "firefox99", "safari15"],
write: false,
format: "esm",
plugins: [denoPlugin({ importMapURL })],
splitting: false,
jsx: "automatic",
jsxImportSource: "react",
});
if (res.errors.length > 0) {
throw res.errors;
}
console.log(new Date(), "Finished bundling", entryPoint.href);
return res.outputFiles[0].text;
}
+150
View File
@@ -0,0 +1,150 @@
import m from "https://esm.sh/mithril@2.2.2"
import {verifyAuth} from "./auth.ts";
import {Scores, Team} from "./shared.ts";
class ProgressBar {
closestFivePercent(n: number): number{
let ret = 0;
let distRet = 101;
for(let i = 0; i < 101; i += 5){
if(Math.abs(i - n) < distRet) {
ret = i;
distRet = Math.abs(i - n);
}
}
return ret;
}
computeClickedScored(event: any){
const total = event.target.clientWidth;
const score = event.x - event.target.offsetLeft;
return this.closestFivePercent(( score / total ) * 100);
}
view({attrs}){
const onclickFn = (e) => {
attrs.scores[attrs.key] = this.computeClickedScored(e);
attrs.onchange();
}
const value = attrs.scores[attrs.key];
return m("progress.progress.is-primary",
{value: value, max: 100, onclick: onclickFn, title: `${value}%`},
`${value}%`)
}
}
const scores: Scores = {
readme: 25,
diagrams: 50,
convinced: 75
};
const teams: Team[] = [
{id: 8787, name: "Les chacals de laval", scores: {...scores}},
{id: 18167, name: "Les rapides de blainville", scores: {...scores}},
{id: 17165, name: "Les pas-beaux du plateau", scores: {...scores}},
]
class VoteView {
voteFor(team: Team, title: string, key: string, onchange: () => void){
return m("div.columns",
m("div.column", title, ":"),
m("div.column.is-two-thirds",
m(new ProgressBar(), {scores: team.scores, key: key, onchange: onchange})));
}
view(vnode){
const teamId = vnode.attrs.teamId;
const onchange = vnode.attrs.onchange ?? function(){};
const team = teams.find(team => team.id.toString() === teamId);
return [
this.voteFor(team, "Qualité du README", 'readme', onchange),
this.voteFor(team, "Qualité des diagrammes", 'diagrams', onchange),
this.voteFor(team, "Êtes-vous convaincu?", 'convinced', onchange)]
}
}
class Submit{
view(){
return m("div.columns",
m("div.column",
m(m.route.Link, {class: "button", href: "/voter-home/"}, `Votez!`),
));
}
}
class MainVoteView{
async persistTeams(): Promise<void>{
const params: RequestInit = {
method: 'POST',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(teams)
}
const resp = await fetch("/api/v1/vote/kkk", params);
console.log(resp.status)
}
view(vnode){
const name = "Les chacals de laval"
return m("div.columns.is-centered",
m("div.column.is-two-thirds",
m("h1.title.is-1", m(m.route.Link, {href: "/"}, `Votons - ${name}`)),
m(new VoteView(), {teamId: vnode.attrs.teamId, onchange: this.persistTeams}),
m(new Submit())));
}
}
class MainView{
createTeamRow(team){
return m('tr', [
m('td', m(m.route.Link, {href: `${m.route.get()}vote/${team.id}/`}, team.name)),
m('td', team.scores.readme),
m('td', team.scores.diagrams),
m('td', team.scores.convinced),
])
}
votesTable(teams){
return m('table.table.is-striped.is-fullwidth',
m('thead',
m('tr', ['Équipe', 'Readme', 'Diagrammes', 'Idée'].map(title => m('th', title)))),
m('tbody', teams.map(team => this.createTeamRow(team))))
}
view(){
return m("div.columns.is-centered",
m("div.column.is-two-thirds",
this.votesTable(teams)))
}
}
class PasscodeView{
verifyPasscode(event): void {
let password = event.target.value;
if(verifyAuth(password)){
window.localStorage.setItem("userId", password);
m.route.set(`/voter-home/`);
}
}
view(){
const change = (e) => this.verifyPasscode(e);
return m('div.hero.is-fullheight',
m("div.hero-body.has-text-centered",
m("div.container",
m("div.box", m("input.input", {placeholder: "passcode", onkeyup: change})))));
}
}
m.route(window.document.body, "/", {
"/": PasscodeView,
"/voter-home/": MainView,
"/voter-home/vote/:teamId/": MainVoteView
})
+89
View File
@@ -0,0 +1,89 @@
import { Application, Router } from "https://deno.land/x/oak/mod.ts";
import {createBundle} from "./bundler.ts";
import {Context} from "https://deno.land/x/oak@v12.2.0/context.ts";
import {Team} from "./shared.ts";
const router = new Router();
const clientBundle = createBundle(
new URL("./client.ts", import.meta.url),
new URL("../import_map.json", import.meta.url),
false,
);
router.get("/", (context) => {
context.response.body = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>title</title>
<link href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css" rel="stylesheet">
</head>
<body>
<script type="module" src="client.js"></script>
</body>
</html>
`;
});
function getUserIndex(userId: string, ballotId: string, questionId: string){
return ["tatoo-vote",
"v1",
"results",
"user",
userId,
"ballot",
ballotId,
"question",
questionId]
}
function persistTeams(userId: any, ballots: Team[]) {
const kv = await Deno.openKv();
for(const ballot of ballots){
for(const question of Object.keys(ballot.scores)){
const k = getUserIndex(userId, ballot.id, question);
const v = {value: ballot.scores[question]};
await kv.set(k, v);
}
}
}
router.post("/api/v1/vote/:userid", async (ctx: Context)=> {
const userId = ctx?.params?.userid??"";
if(!ctx.request.hasBody){
ctx.throw(415);
}
const body = await ctx.request.body().value;
const teams = body as Team[];
await persistTeams(userId, teams);
console.log(body);
ctx.response.body = userId;
});
router.get("/client.js", async (context): Promise<void> => {
context.response.body = await createBundle(
new URL("./client.ts", import.meta.url),
new URL("../import_map.json", import.meta.url),
false,
);
context.response.type = "application/javascript";
});
const app = new Application();
app.use(router.routes());
app.use((ctx) => {
ctx.response.body = "Hello World!";
});
await app.listen({ port: 3333 });
+11
View File
@@ -0,0 +1,11 @@
export type Scores = {
readme: number,
diagrams: number,
convinced: number,
};
export type Team = {
id: number,
name: string,
scores: Scores,
}