improved UI
This commit is contained in:
1 parent
520fab5c93
commit
e9da24b54d
13 files changed
+491
-32
No files matched your search
+10
-32
@@ -2,12 +2,6 @@ import m from "https://esm.sh/mithril@2.2.2"
|
|||||||
import colormap from "https://esm.sh/colormap@2.3.2"
|
import colormap from "https://esm.sh/colormap@2.3.2"
|
||||||
import {range} from "./util.js";
|
import {range} from "./util.js";
|
||||||
|
|
||||||
|
|
||||||
// const second = 1000;
|
|
||||||
|
|
||||||
// const canvasWidth = Number.parseFloat(document.querySelector("svg").getAttribute("width"));
|
|
||||||
// const canvasHeight = Number.parseFloat(document.querySelector("svg").getAttribute("height"));
|
|
||||||
|
|
||||||
function gaussianRandom(mean=0, stdev=1) {
|
function gaussianRandom(mean=0, stdev=1) {
|
||||||
const u = 1 - Math.random(); // Converting [0,1) to (0,1]
|
const u = 1 - Math.random(); // Converting [0,1) to (0,1]
|
||||||
const v = Math.random();
|
const v = Math.random();
|
||||||
@@ -45,28 +39,6 @@ function* keyFrames(duration, framePerSeconds){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const duration = 60;
|
|
||||||
// const timeline = anime.timeline({
|
|
||||||
// duration: duration * second,
|
|
||||||
// autoplay: false,
|
|
||||||
// easing: 'linear'});
|
|
||||||
//
|
|
||||||
// for(let i = 1; i <= 12; i++){
|
|
||||||
// timeline.add({
|
|
||||||
// targets: `#rect-${i}`,
|
|
||||||
// keyframes: Array.from(keyFrames(duration, 30)),
|
|
||||||
// }, 0)
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// document.querySelector('#play').onclick = timeline.play;
|
|
||||||
// document.querySelector('#pause').onclick = timeline.pause;
|
|
||||||
// document.querySelector('#seek').onclick = function(){
|
|
||||||
// const seekVal = Number.parseFloat(second * document.querySelector('#seek-value').value);
|
|
||||||
// timeline.pause();
|
|
||||||
// timeline.seek(seekVal);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
function generatePoints(points, frames){
|
function generatePoints(points, frames){
|
||||||
const lst = [];
|
const lst = [];
|
||||||
@@ -105,7 +77,15 @@ function getCourtLines(){
|
|||||||
m("line", {x1: 600, x2: 600, y1: 0, y2: 450, class: "return-line"})
|
m("line", {x1: 600, x2: 600, y1: 0, y2: 450, class: "return-line"})
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class WorkingTabs{
|
class WorkingTabs{
|
||||||
|
#datasets = []
|
||||||
|
|
||||||
|
async oninit(){
|
||||||
|
const url = "/dodgeball-tracker/outputs/index.json"
|
||||||
|
this.#datasets = (await m.request({method: "GET", url: url}))?.index ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
tabs(){
|
tabs(){
|
||||||
return m("div.tabs",
|
return m("div.tabs",
|
||||||
@@ -115,16 +95,14 @@ class WorkingTabs{
|
|||||||
}
|
}
|
||||||
|
|
||||||
info(){
|
info(){
|
||||||
const options = [
|
const options = this.#datasets.map(ds => m('option', {value: ds.id}, ds.label));
|
||||||
m("option", "one"),
|
|
||||||
m("option", "two"),
|
|
||||||
]
|
|
||||||
return m("div.field",
|
return m("div.field",
|
||||||
m("label.label", "Dataset"),
|
m("label.label", "Dataset"),
|
||||||
m("div.control",
|
m("div.control",
|
||||||
m("div.select",
|
m("div.select",
|
||||||
m("select", options))));
|
m("select", options))));
|
||||||
}
|
}
|
||||||
|
|
||||||
view(){
|
view(){
|
||||||
return m("div",
|
return m("div",
|
||||||
this.tabs(),
|
this.tabs(),
|
||||||
|
|||||||
Whitespace-only changes.
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"preact": "https://esm.sh/preact@10.19.2",
|
||||||
|
"preact/": "https://esm.sh/preact@10.19.2/",
|
||||||
|
"@preact/signals": "https://esm.sh/*@preact/signals@1.2.1",
|
||||||
|
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.0",
|
||||||
|
"$std/": "https://deno.land/std@0.208.0/"
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "preact"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 630 630">
|
||||||
|
<rect width="630" height="630" fill="#f7df1e"/>
|
||||||
|
<path d="m423.2 492.19c12.69 20.72 29.2 35.95 58.4 35.95 24.53 0 40.2-12.26 40.2-29.2 0-20.3-16.1-27.49-43.1-39.3l-14.8-6.35c-42.72-18.2-71.1-41-71.1-89.2 0-44.4 33.83-78.2 86.7-78.2 37.64 0 64.7 13.1 84.2 47.4l-46.1 29.6c-10.15-18.2-21.1-25.37-38.1-25.37-17.34 0-28.33 11-28.33 25.37 0 17.76 11 24.95 36.4 35.95l14.8 6.34c50.3 21.57 78.7 43.56 78.7 93 0 53.3-41.87 82.5-98.1 82.5-54.98 0-90.5-26.2-107.88-60.54zm-209.13 5.13c9.3 16.5 17.76 30.45 38.1 30.45 19.45 0 31.72-7.61 31.72-37.2v-201.3h59.2v202.1c0 61.3-35.94 89.2-88.4 89.2-47.4 0-74.85-24.53-88.81-54.075z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 687 B |
@@ -0,0 +1,33 @@
|
|||||||
|
const sessionLocalStorageKey = 'dgt-session';
|
||||||
|
|
||||||
|
let cachedPathVariables = null;
|
||||||
|
|
||||||
|
function getPathVariables(){
|
||||||
|
if (cachedPathVariables !== null) {
|
||||||
|
return cachedPathVariables;
|
||||||
|
}
|
||||||
|
const raw = document.getElementById("url-placeholders")?.textContent || "{}"
|
||||||
|
cachedPathVariables = Object.entries(JSON.parse(raw)?.pathVariables || {})
|
||||||
|
return cachedPathVariables
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('login', function (evt) {
|
||||||
|
localStorage.setItem(sessionLocalStorageKey, evt.detail.value);
|
||||||
|
document.location.href = "/";
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('logout', function (evt) {
|
||||||
|
window.localStorage.removeItem(sessionLocalStorageKey);
|
||||||
|
document.location.href = "/login.html";
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('htmx:configRequest', function(evt) {
|
||||||
|
evt.detail.headers['dgt-session'] = localStorage.getItem(sessionLocalStorageKey);
|
||||||
|
|
||||||
|
function reducer(path, [key, value]){
|
||||||
|
return path.replace(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.detail.path = getPathVariables().reduce(reducer, evt.detail.path)
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import {app} from "./src/server.ts";
|
||||||
|
import Logger from "https://deno.land/x/logger@v1.1.5/logger.ts";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const logger = new Logger()
|
||||||
|
const port = 8000
|
||||||
|
logger.info(`Starting web server on port ${port}`)
|
||||||
|
await app.listen({ port });
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import {Router} from "https://deno.land/x/oak/mod.ts";
|
||||||
|
import {FileMatchService} from "../services/matches.ts";
|
||||||
|
import {ViewTabService} from "./tab.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
export const apiRouter = new Router( {prefix: '/api/v1'});
|
||||||
|
|
||||||
|
async function getMatchService(): Promise<FileMatchService> {
|
||||||
|
const f = "/home/didier/alldressed-programming/dodgeball-tracker/outputs/index.json"
|
||||||
|
return new FileMatchService(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiRouter.get('/ping', (ctx) => {
|
||||||
|
ctx.response.body = <span>pong</span>
|
||||||
|
})
|
||||||
|
|
||||||
|
apiRouter.get("/match/:id/title", async function(ctx){
|
||||||
|
const service = await getMatchService()
|
||||||
|
const match = await service.getMatchInfoById(ctx.params.id)
|
||||||
|
ctx.response.body = (<span>Match: {match.label}</span>)
|
||||||
|
});
|
||||||
|
|
||||||
|
apiRouter.get("/match/:id/pane/info", async function(ctx){
|
||||||
|
const service = await getMatchService()
|
||||||
|
const match = await service.getMatchInfoById(ctx.params.id)
|
||||||
|
const viewService = new ViewTabService();
|
||||||
|
ctx.response.body = viewService.infoTab(match)
|
||||||
|
});
|
||||||
|
|
||||||
|
apiRouter.get("/match/:id/pane/player", async function(ctx){
|
||||||
|
const service = await getMatchService()
|
||||||
|
const match = await service.getMatchInfoById(ctx.params.id)
|
||||||
|
const viewService = new ViewTabService();
|
||||||
|
ctx.response.body = viewService.playerTab(match)
|
||||||
|
});
|
||||||
|
|
||||||
|
apiRouter.get("/match/:id/pane/player/data", async function(ctx){
|
||||||
|
ctx.response.body = <svg:rect width="100" height="100" />
|
||||||
|
});
|
||||||
|
|
||||||
|
apiRouter.get('/list-matches', async (ctx) => {
|
||||||
|
const matchService = await getMatchService()
|
||||||
|
const rows = [];
|
||||||
|
for await (const match of matchService.getAllMatchInfo()){
|
||||||
|
rows.push(
|
||||||
|
<tr>
|
||||||
|
<td><a href={`/match/${match.id}/info`}>{ match.label }</a></td>
|
||||||
|
<td><a href={match.originalVideoUrl}>{ match.originalVideoUrl }</a></td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ctx.response.body = (<div>
|
||||||
|
<table class="table is-bordered is-striped is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Label</th>
|
||||||
|
<th>Video Link</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>)
|
||||||
|
})
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import {MatchInfo} from "../services/matches.ts";
|
||||||
|
|
||||||
|
export class ViewTabService {
|
||||||
|
|
||||||
|
infoTab(matchInfo: MatchInfo){
|
||||||
|
return (<table class="table is-bordered is-striped is-fullwidth">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Label</td>
|
||||||
|
<td>{matchInfo.label}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Id</td>
|
||||||
|
<td>{matchInfo.id}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Date</td>
|
||||||
|
<td>{matchInfo.matchDate}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Video URL</td>
|
||||||
|
<td>
|
||||||
|
<a href={matchInfo.originalVideoUrl}>{matchInfo.originalVideoUrl}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
playerTab(matchInfo: MatchInfo){
|
||||||
|
return (<div>
|
||||||
|
<div id="foo"></div>
|
||||||
|
<form hx-get={"/api/v1/match/:id/pane/player/data"}
|
||||||
|
hx-target="#court-content">
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Min Time</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-danger" type="range" min="0" max="100" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Max Time</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-danger" type="range" min="0" max="100" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<button class="button"> Show </button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Router } from "https://deno.land/x/oak/mod.ts";
|
||||||
|
|
||||||
|
|
||||||
|
export const loginRouter = new Router();
|
||||||
|
|
||||||
|
export function isLoggedIn(ctx){
|
||||||
|
return ctx.request.headers.get('dgt-session')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
loginRouter.post("/api/v1/login", async (ctx)=> {
|
||||||
|
const username = (await ctx.request.body().value).get("username");
|
||||||
|
ctx.response.headers.append("HX-Trigger", JSON.stringify({'login': username}));
|
||||||
|
ctx.response.body = username;
|
||||||
|
});
|
||||||
|
|
||||||
|
loginRouter.post("/api/v1/logout", (ctx)=> {
|
||||||
|
ctx.response.headers.append("HX-Trigger", "logout");
|
||||||
|
ctx.response.body = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Application } from "https://deno.land/x/oak/mod.ts";
|
||||||
|
import staticFiles from "https://deno.land/x/static_files@1.1.6/mod.ts";
|
||||||
|
import {loginRouter, isLoggedIn} from "./login.ts";
|
||||||
|
import {apiRouter} from "./api/mod.tsx";
|
||||||
|
import {viewRouter} from "./views/mod.tsx";
|
||||||
|
import {renderToString} from "https://esm.sh/preact-render-to-string@5.1.19?deps=preact@10.5.15";
|
||||||
|
import Logger from "https://deno.land/x/logger@v1.1.5/logger.ts";
|
||||||
|
|
||||||
|
const logger = new Logger();
|
||||||
|
export const app = new Application();
|
||||||
|
|
||||||
|
function isReactComponent(x){
|
||||||
|
return !!x && Object.hasOwn(x, "type") && Object.hasOwn(x, "props")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reactRendererMiddleware(ctx, next){
|
||||||
|
await next();
|
||||||
|
if(isReactComponent(ctx.response.body)){
|
||||||
|
logger.info("Converting react component to string.")
|
||||||
|
ctx.response.body = renderToString(ctx.response.body)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loggingMiddleware(ctx, next){
|
||||||
|
if(!isLoggedIn(ctx)){
|
||||||
|
logger.info("user not logged in.")
|
||||||
|
ctx.response.headers.append("HX-Redirect", "/login.html");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.info('user logged in')
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
app.use(loginRouter.routes());
|
||||||
|
app.use(viewRouter.routes());
|
||||||
|
app.use(staticFiles("public", {brotli: true, fetch: true}));
|
||||||
|
app.use(loggingMiddleware)
|
||||||
|
app.use(reactRendererMiddleware)
|
||||||
|
app.use(apiRouter.routes());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
export type MatchInfo = {
|
||||||
|
id: string,
|
||||||
|
originalVideoUrl: string,
|
||||||
|
matchDate: string,
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MatchService {
|
||||||
|
|
||||||
|
getAllMatchInfo(): AsyncIterator<MatchInfo>;
|
||||||
|
getMatchInfoById(id: string): Promise<MatchInfo|undefined>;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FileMatchService implements MatchService{
|
||||||
|
|
||||||
|
readonly #indexFile: string
|
||||||
|
|
||||||
|
constructor(indexFile: string) {
|
||||||
|
this.#indexFile = indexFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async *getAllMatchInfo(): AsyncIterator<MatchInfo>{
|
||||||
|
const data = await Deno.readTextFile(this.#indexFile)
|
||||||
|
const ret = JSON.parse(data) as {matches: MatchInfo[]}
|
||||||
|
yield* ret.matches
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMatchInfoById(id: string): Promise<MatchInfo|undefined>{
|
||||||
|
for await(const mi of this.getAllMatchInfo()){
|
||||||
|
if(mi.id === id){
|
||||||
|
return mi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
export function Head(){
|
||||||
|
return (<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||||
|
<title>JS Reading App</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css"/>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/default.min.css"/>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-light.min.css"/>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.9"></script>
|
||||||
|
</head>)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function NavBar(){
|
||||||
|
return (<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="navbar-menu is-active">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<span class="navbar-item">
|
||||||
|
<a href="/">
|
||||||
|
<img src="/images/logo.png" alt="js logo" style="height: 100%"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="navbar-end">
|
||||||
|
<a class="navbar-item" href="#" hx-post="/api/v1/logout">
|
||||||
|
Way Out
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Body({pathVariables, children}){
|
||||||
|
const urlPlaceHolders = {
|
||||||
|
pathVariables: Object.fromEntries(Object.entries(pathVariables ?? {}).map(([k, v]) => [`:${k}`, v]))
|
||||||
|
}
|
||||||
|
return (<body>
|
||||||
|
{ children }
|
||||||
|
<script src="/scripts/auth.js" type="module"> </script>
|
||||||
|
<script
|
||||||
|
type="json"
|
||||||
|
id="url-placeholders"
|
||||||
|
dangerouslySetInnerHTML={{__html: JSON.stringify(urlPlaceHolders, null, 4)}}></script>
|
||||||
|
</body>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { Router } from "https://deno.land/x/oak/mod.ts";
|
||||||
|
import {renderToString} from "https://esm.sh/preact-render-to-string@5.1.19?deps=preact@10.5.15";
|
||||||
|
import * as base from './base.tsx';
|
||||||
|
import {Head} from "./base.tsx";
|
||||||
|
export const viewRouter = new Router();
|
||||||
|
|
||||||
|
function renderPageToString(page){
|
||||||
|
return `<!DOCTYPE html>${renderToString(page)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Court(){
|
||||||
|
const margin = 50;
|
||||||
|
const returnLine = {stroke: 'blue', strokeWidth: '2px'}
|
||||||
|
const courtBorder = {stroke: 'red', strokeWidth: '4px', fill: 'gray' }
|
||||||
|
const lines = [
|
||||||
|
<line x1={450} x2={450} y1={0} y2={450} style={courtBorder}></line>,
|
||||||
|
<line x1={300} x2={300} y1={0} y2={450} style={returnLine}></line>,
|
||||||
|
<line x1={600} x2={600} y1={0} y2={450} style={returnLine}></line>,
|
||||||
|
]
|
||||||
|
return (<svg class="court-side"
|
||||||
|
style={{width: 900 + 2 * margin, height: 450 + 2 * margin, backgroundColor: 'pink'}}>
|
||||||
|
<g class="court-border" id="court" transform = {`translate(${margin} ${margin})`}>
|
||||||
|
<rect width={900} height={450} style={courtBorder}/>
|
||||||
|
{ lines }
|
||||||
|
<g id="court-content"></g>
|
||||||
|
</g>
|
||||||
|
</svg>)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewRouter.get("/match/:id/:tab", (ctx) => {
|
||||||
|
const page = (<html lang="en">
|
||||||
|
<base.Head/>
|
||||||
|
<base.Body pathVariables={ctx.params}>
|
||||||
|
<base.NavBar></base.NavBar>
|
||||||
|
<div class="columns is-centered">
|
||||||
|
|
||||||
|
<div class="column is-two-thirds">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column">
|
||||||
|
<h4 class="title is-4"
|
||||||
|
hx-get="/api/v1/match/:id/title"
|
||||||
|
hx-trigger="load"> </h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column" style={{textAlign: "center"}}>
|
||||||
|
<Court></Court>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column" style={{textAlign: "center"}}>
|
||||||
|
<div class="tabs">
|
||||||
|
<ul>
|
||||||
|
<li className={ctx.params.tab === 'info' ? "is-active": ""}>
|
||||||
|
<a href={`/match/${ctx.params.id}/info`}>Information</a>
|
||||||
|
</li>
|
||||||
|
<li className={ctx.params.tab === 'player' ? "is-active": ""}>
|
||||||
|
<a href={`/match/${ctx.params.id}/player`}>Player</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column"
|
||||||
|
hx-get={`/api/v1/match/${ctx.params.id}/pane/${ctx.params.tab}`}
|
||||||
|
hx-trigger="load">
|
||||||
|
foo bar toto
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</base.Body>
|
||||||
|
</html>)
|
||||||
|
ctx.response.body = renderPageToString(page)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
viewRouter.get("/index.html", (ctx) => {
|
||||||
|
const page = (<html lang="en">
|
||||||
|
<base.Head/>
|
||||||
|
<base.Body>
|
||||||
|
<base.NavBar></base.NavBar>
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-two-thirds"
|
||||||
|
id="main-block"
|
||||||
|
hx-get="/api/v1/list-matches"
|
||||||
|
hx-trigger="load">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</base.Body>
|
||||||
|
</html>)
|
||||||
|
ctx.response.body = renderPageToString(page)
|
||||||
|
})
|
||||||
|
|
||||||
|
viewRouter.redirect("/", "/index.html")
|
||||||
|
|
||||||
|
viewRouter.get("/login.html", (ctx) => {
|
||||||
|
const page = (<html lang="en">
|
||||||
|
<base.Head/>
|
||||||
|
<base.Body>
|
||||||
|
<base.NavBar></base.NavBar>
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-half">
|
||||||
|
<form hx-post="/api/v1/login" hx-swap="none">
|
||||||
|
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" id="username" type="text" placeholder="bob.l.eponge@ocean.com" name="username" required />
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<input type="submit" class="button" value="Login" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</base.Body>
|
||||||
|
</html>)
|
||||||
|
ctx.response.body = renderPageToString(page)
|
||||||
|
})
|
||||||
Reference in new issue
Block a user