first commit
This commit is contained in:
commit
22489242c2
7 files changed
+326
No files matched your search
@@ -0,0 +1,3 @@
|
||||
export function verifyAuth(auth: string): boolean{
|
||||
return auth.length === 4;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
})
|
||||
@@ -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 });
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export type Scores = {
|
||||
readme: number,
|
||||
diagrams: number,
|
||||
convinced: number,
|
||||
};
|
||||
|
||||
export type Team = {
|
||||
id: number,
|
||||
name: string,
|
||||
scores: Scores,
|
||||
}
|
||||
Reference in new issue
Block a user