first commit
This commit is contained in:
commit
22489242c2
7 files changed
+326
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
.idea
|
||||||
|
.idea
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -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