This commit is contained in:
Miki 2023-12-20 16:49:33 +01:00
parent d9a1837f3b
commit ebd2f05ae2
69 changed files with 2335 additions and 451 deletions

View file

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View file

@ -0,0 +1,15 @@
/** @type { import("eslint").Linter.FlatConfig } */
module.exports = {
root: true,
extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
}
};

10
frontend.old/.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
frontend.old/.npmrc Normal file
View file

@ -0,0 +1 @@
engine-strict=true

View file

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

8
frontend.old/.prettierrc Normal file
View file

@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

38
frontend.old/README.md Normal file
View file

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

26
frontend.old/package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "frontend",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev --host",
"build": "vite build",
"preview": "vite preview",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.1.1",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.30.3",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"sass": "^1.69.5",
"svelte": "^4.2.8",
"vite": "^4.5.1"
},
"type": "module"
}

1588
frontend.old/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

12
frontend.old/src/app.html Normal file
View file

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1,24 @@
<script>
// import { Button, Tooltip } from 'flowbite-svelte';
export let pill = false;
export let color = 'blue';
export let size = 'sm';
export let href = "/login";
</script>
<button size={size} class="!p-2" {pill} color={color} href={href}>
<svg
class="w-6 h-6 text-gray-100 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill="currentColor"
d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z"
/>
</svg>
</button>
<!-- <Tooltip id="type-auto" arrow={false} type="custom" defaultClass="" class="p-1 text-sm bg-blue-700 text-gray-100" >Login</Tooltip> -->

View file

@ -0,0 +1,43 @@
<script>
// import { Button, Tooltip } from 'flowbite-svelte';
export let pill = false;
export let color = 'blue';
export let size = 'sm';
</script>
<button {size} class="!p-2" {pill} {color}>
<svg class="w-6 h-6" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path
fill="currentColor"
d="m62.578 41.956 -28.444 21.333a3.556 3.556 0 0 1 -4.267 0l-28.444 -21.333a3.637 3.637 0 0 1 -1.244 -3.982A3.605 3.605 0 0 1 3.556 35.556h7.111V32a3.566 3.566 0 0 1 3.556 -3.556h35.556a3.566 3.566 0 0 1 3.556 3.556v3.556h7.111a3.552 3.552 0 0 1 2.133 6.4Z"
/>
<path
fill="currentColor"
x="3"
y="4"
width="12"
height="2"
rx="1"
ry="1"
d="M14.222 14.222H49.778A3.556 3.556 0 0 1 53.333 17.778V17.778A3.556 3.556 0 0 1 49.778 21.333H14.222A3.556 3.556 0 0 1 10.667 17.778V17.778A3.556 3.556 0 0 1 14.222 14.222z"
/>
<path
fill="currentColor"
x="3"
width="12"
height="2"
rx="1"
ry="1"
d="M14.222 0H49.778A3.556 3.556 0 0 1 53.333 3.556V3.556A3.556 3.556 0 0 1 49.778 7.111H14.222A3.556 3.556 0 0 1 10.667 3.556V3.556A3.556 3.556 0 0 1 14.222 0z"
/></svg
>
</button>
<!-- <Tooltip
id="type-auto"
arrow={false}
type="custom"
defaultClass=""
class="p-1 text-sm bg-blue-700 text-gray-100">Priority</Tooltip
> -->

View file

@ -0,0 +1,32 @@
<script>
// import { Button, Tooltip } from 'flowbite-svelte';
export let pill = false;
export let color = 'blue';
export let size = 'sm';
</script>
<button size={size} class="!p-2" {pill} color={color}>
<svg
class="w-6 h-6"
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 455 455"
style="enable-background:new 0 0 455 455;"
xml:space="preserve"
fill="currentColor"
>
<g>
<rect x="162" y="323" width="293" height="132" />
<rect x="162" y="161" width="293" height="132" />
<rect width="455" height="131" />
<rect y="161" width="132" height="294" />
</g>
</svg>
</button>
<!-- <Tooltip id="type-auto" arrow={false} type="custom" defaultClass="" class="p-1 text-sm bg-blue-700 text-gray-100" >Score</Tooltip> -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -0,0 +1,14 @@
import { writable } from 'svelte/store'
export default function () {
const surfers = writable([]);
async function get() {
const response = await fetch(`/api/surfers`)
surfers.set(await response.json())
}
get();
return surfers;
}

View file

@ -0,0 +1,14 @@
import { writable } from 'svelte/store'
export default function () {
const users = writable([]);
async function get() {
const response = await fetch(`/api/users`)
users.set(await response.json())
}
get();
return users;
}

View file

@ -0,0 +1,3 @@
export const prerender = true;
export const ssr = false;
export const trailingSlash = 'always';

View file

@ -0,0 +1,10 @@
<script>
import TopScorer from "$lib/img/topscorer_logo_web.png";
</script>
<div style="background-color: black;">
<img src={TopScorer} alt="TopScorer">
</div>

View file

@ -0,0 +1,240 @@
<script>
// import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import { dev } from '$app/environment';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
let surfers = [
{ name: 'Kanoa Igarashi', color: 'red', score: '4.50', priority: '3' },
{ name: 'Griffin Colapinto', color: 'white', score: '5.60', priority: 'P' },
{ name: 'Jack Robinson', color: 'blue', score: '6.10', priority: '5' },
{ name: 'Gabriel Medina', color: 'green', score: '4.30', priority: '2' },
{ name: 'Italo Ferreira', color: 'black', score: '6.50', priority: '4' }
];
let width;
// $: activeUrl = $page.url.pathname;
let event = 'Semifinal';
let category = 'U16 man';
let heat = 'Heat 1';
const pad2 = (number) => `00${number}`.slice(-2);
$: min = 10;
$: sec = 25;
let end = false;
function updateRemainingTime() {
if ((min === 0) & (sec === 0)) {
clearInterval(timer);
end = true;
} else if (sec === 0) {
min -= 1;
sec = 59;
} else {
sec -= 1;
}
}
updateRemainingTime();
const timer = setInterval(updateRemainingTime, 1000);
function Subscribe() {
const sse = new EventSource(`/api/sse`);
console.log('subscribe');
sse.onmessage = (e) => {
let Msg = JSON.parse(e.data);
console.log(`received: ${JSON.stringify(Msg)}`);
if (Msg.mode === 'priority') {
console.log(`priority: ${Msg.priority}`);
for (let i in surfers) {
surfers[i].priority = Msg.priority[i];
}
}
};
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
onMount(() => {
const unsub = Subscribe();
return unsub;
});
onDestroy(() => {
clearInterval(timer); // Pulisci il timer quando il componente viene distrutto
});
</script>
<svelte:window bind:innerWidth={width} />
<div class="header">
{#if !end}
<div class="timer">{pad2(min)}:{pad2(sec)}</div>
{:else}
<div class="timer" style="color: red">{pad2(min)}:{pad2(sec)}</div>
{/if}
</div>
<div class="container">
{#each surfers as surfer, id}
<div class="box">
<div class="square" style="background-color: {surfer.color};">
{#if surfer.priority != ''}
{#if surfer.priority === 'P'}
{#if surfer.color === 'white'}
<span class="priority_white">{surfer.priority}</span>
{:else}
<span class="priority">{surfer.priority}</span>
{/if}
{:else}
<span class="priority_small">{surfer.priority}</span>
{/if}
{/if}
</div>
<div class="score">{surfer.score}</div>
</div>
{/each}
</div>
<style>
:root {
--backColor: #334;
--textColor: white;
--maxWidth: (100% - 15px);
}
.header {
/* padding: 15px; */
height: 10vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
margin-top: 2px;
margin-bottom: 2px;
width: calc(var(--maxWidth));
color: var(--textColor);
display: flex;
justify-content: space-between;
}
.header .timer {
font-size: 13vh;
padding-left: 10px;
padding-right: 20px;
font-weight: bold;
flex: 2 2 auto;
align-self: center;
text-align: center;
}
.container {
height: 85vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 4px;
width: calc(var(--maxWidth));
color: var(--textColor);
}
.box {
width: 100%;
display: flex;
align-items: center;
padding: 1px;
margin-bottom: 1px;
}
.priority {
width: 15%;
height: 100%;
border-radius: 20%;
font-size: 8vh;
animation: blink 2s 3;
margin-right: auto;
background-color: white;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.priority_white {
width: 15%;
height: 100%;
border-radius: 20%;
font-size: 8vh;
animation: blink_white 2s 3;
margin-right: auto;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
padding-bottom: 4px;
}
.priority_small {
width: 10%;
height: 80%;
border-radius: 20%;
font-size: 8vh;
/* animation: blink 2s infinite; */
margin-right: auto;
margin-left: 30px;
background-color: #bbb;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.square {
width: 100%;
height: 16vh;
border-radius: 5px;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 2px;
margin-bottom: 2px;
}
.score {
padding-right: 20px;
width: 20%;
font-size: 12vh;
float: right;
text-align: center;
font-weight: bold;
}
@keyframes blink {
0% {
background-color: white;
}
50% {
background-color: rgba(255, 255, 255, 0.6);
}
100% {
background-color: white;
}
}
</style>

View file

@ -0,0 +1,256 @@
<script>
// import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import { dev } from '$app/environment';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
let surfers = [
// { name: 'Kanoa Igarashi', color: 'red', score: '4.50', priority: '3' },
// { name: 'Griffin Colapinto', color: 'white', score: '5.60', priority: 'P' },
// { name: 'Jack Robinson', color: 'blue', score: '6.10', priority: '5' },
// { name: 'Gabriel Medina', color: 'green', score: '4.30', priority: '2' },
// { name: 'Italo Ferreira', color: 'black', score: '6.50', priority: '4' }
];
let width;
// $: activeUrl = $page.url.pathname;
let heat = {};
const pad2 = (number) => `00${number}`.slice(-2);
$: min = 0;
$: sec = 0;
let end = false;
loadRunning();
async function loadRunning() {
const res = await fetch(`/api/runningheat`);
const data = await res.json();
heat = data;
console.log(`retval: ${JSON.stringify(heat)}`);
if (heat != null) {
console.log(`heat: ${JSON.stringify(heat)}`);
min = heat.timer;
surfers = heat.surfers;
}
}
// function updateRemainingTime() {
// if ((min === 0) & (sec === 0)) {
// clearInterval(timer);
// end = true;
// } else if (sec === 0) {
// min -= 1;
// sec = 59;
// } else {
// sec -= 1;
// }
// }
// updateRemainingTime();
// const timer = setInterval(updateRemainingTime, 1000);
function Subscribe() {
const sse = new EventSource(`/api/sse`);
console.log('subscribe');
sse.onmessage = (e) => {
let Msg = JSON.parse(e.data);
console.log(`received: ${JSON.stringify(Msg)}`);
if (Msg.mode === 'priority') {
console.log(`priority: ${Msg.priority}`);
for (let i in surfers) {
surfers[i].priority = Msg.priority[i];
}
} else if (Msg.mode === 'time') {
// console.log(`duration: ${Msg.duration}`);
let min_sec = Msg.duration.split(":");
min = min_sec[0];
sec = min_sec[1];
// console.log(`min & sec = ${min} & ${sec}`);
if (!start) {
start = true;
}
} else if (Msg.mode === 'stop') {
console.log(`stop duration: ${Msg.duration}`);
end = true;
start = false;
}
};
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
onMount(() => {
if (!dev) {
const unsub = Subscribe();
return unsub;
}
});
onDestroy(() => {
clearInterval(timer); // Pulisci il timer quando il componente viene distrutto
});
</script>
<svelte:window bind:innerWidth={width} />
<div class="header">
{#if !end}
<div class="timer">{pad2(min)}:{pad2(sec)}</div>
{:else}
<div class="timer" style="color: red">{pad2(min)}:{pad2(sec)}</div>
{/if}
</div>
<div class="container">
{#each surfers as surfer, id}
<div class="box">
<div class="square" style="background-color: {surfer.color};">
{#if surfer.priority != ''}
{#if surfer.priority === 'P'}
{#if surfer.color === 'white'}
<span class="priority_white">{surfer.priority}</span>
{:else}
<span class="priority">{surfer.priority}</span>
{/if}
{:else}
<span class="priority_small">{surfer.priority}</span>
{/if}
{/if}
</div>
<div class="score">{surfer.score}</div>
</div>
{/each}
</div>
<style>
:root {
--backColor: #334;
--textColor: white;
--maxWidth: (100% - 15px);
}
.header {
/* padding: 15px; */
height: 10vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
margin-top: 2px;
margin-bottom: 2px;
width: calc(var(--maxWidth));
color: var(--textColor);
display: flex;
justify-content: space-between;
}
.header .timer {
font-size: 13vh;
padding-left: 10px;
padding-right: 20px;
font-weight: bold;
flex: 2 2 auto;
align-self: center;
text-align: center;
}
.container {
height: 85vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 4px;
width: calc(var(--maxWidth));
color: var(--textColor);
}
.priority {
width: 90%;
height: 20%;
border-radius: 20%;
font-size: 8vh;
animation: blink 2s 3;
margin-top: 50%;
background-color: white;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.priority_white {
width: 90%;
height: 20%;
border-radius: 20%;
font-size: 8vh;
animation: blink_white 2s 3;
margin-top: 50%;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-weight: bold;
padding-bottom: 4px;
}
.priority_small {
width: 80%;
height: 15%;
border-radius: 20%;
font-size: 8vh;
/* animation: blink 2s infinite; */
margin-top: 60%;
margin-left: 20px;
background-color: #bbb;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.score {
padding-right: 20px;
width: 20%;
font-size: 12vh;
float: right;
text-align: center;
font-weight: bold;
}
.box {
height: 100%;
width: 19%;
float: left;
margin-left: 1px;
margin-right: 1px;
}
.square {
height: 100%;
width: 100%;
float: left;
align-items: center;
justify-content: center;
text-align: center;
}
</style>

View file

@ -0,0 +1,96 @@
<script>
import Logo from "$lib/img/topscorer_logo_web.png"
$: heats = [];
loadHeats();
async function loadHeats() {
const res = await fetch(`/api/loadheats`);
const data = await res.json();
for (let i in data) {
heats = [...heats, data[i]];
console.log(`${i} retval: ${JSON.stringify(data[i])}`);
}
}
</script>
<div class="header">
<img class="img" src={Logo} alt="logo">
<span class="title" style="color: aliceblue;">Heat Draws</span>
</div>
<hr>
{#each heats as heat}
<table>
<tr>
{#if heat.status === 'running'}
<th colspan="2" class="running">
{heat.name} ({heat.number}) {heat.category}
</th>
{:else if heat.status === 'ended'}
<th colspan="2" class="ended">
{heat.name} ({heat.number}) {heat.category}
</th>
{:else}
<th colspan="2">
{heat.name} ({heat.number}) {heat.category}
</th>
{/if}
</tr>
{#each heat.surfers as surfer }
<tr>
<td>
{surfer.name}
</td>
<td style="background-color: {surfer.color};">
{surfer.color}
</td>
</tr>
{/each}
</table>
<hr>
{/each}
<style>
.header {
background-color: black;
display: flex;
width: 100%;
justify-content: space-between;
text-align: center;
align-items: center;
}
.header .img {
height: 3rem;
}
.header .title {
font-size: 3rem;
margin: 0 auto;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th.running {
background-color: green;
animation: blinker 2s linear infinite;
}
th.ended {
background-color: lightcoral;
text-decoration: line-through 1px lightyellow;
}
@keyframes blinker {
50% {
opacity: 0.5;
}
}
</style>

View file

@ -0,0 +1,353 @@
<script>
// import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import { dev } from '$app/environment';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
let surfers = [
{ name: 'Kanoa Igarashi', color: 'red', score: '4.50', priority: '3' },
{ name: 'Griffin Colapinto', color: 'white', score: '5.60', priority: 'P' },
{ name: 'Jack Robinson', color: 'blue', score: '6.10', priority: '5' },
{ name: 'Gabriel Medina', color: 'green', score: '4.30', priority: '2' },
{ name: 'Italo Ferreira', color: 'black', score: '6.50', priority: '4' }
];
let width;
// $: activeUrl = $page.url.pathname;
let event = 'Semifinal';
let category = 'U16 man';
let heat = 'Heat 1';
const pad2 = (number) => `00${number}`.slice(-2);
$: min = 10;
$: sec = 25;
let end = false;
function updateRemainingTime() {
if ((min === 0) & (sec === 0)) {
clearInterval(timer);
end = true;
} else if (sec === 0) {
min -= 1;
sec = 59;
} else {
sec -= 1;
}
}
updateRemainingTime();
const timer = setInterval(updateRemainingTime, 1000);
function Subscribe() {
const sse = new EventSource(`/api/sse`);
console.log('subscribe');
sse.onmessage = (e) => {
let Msg = JSON.parse(e.data);
console.log(`received: ${JSON.stringify(Msg)}`);
if (Msg.mode === 'priority') {
console.log(`priority: ${Msg.priority}`);
for (let i in surfers) {
surfers[i].priority = Msg.priority[i];
}
}
};
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
onMount(() => {
const unsub = Subscribe();
return unsub;
});
onDestroy(() => {
clearInterval(timer); // Pulisci il timer quando il componente viene distrutto
});
</script>
<svelte:window bind:innerWidth={width} />
<div class="header">
<span class="title">{event}</span>
<span class="title">{category}</span>
{#if !end}
<span class="timer">{pad2(min)}:{pad2(sec)}</span>
{:else}
<span class="timer" style="color: red">{pad2(min)}:{pad2(sec)}</span>
{/if}
<span class="heat">{heat}</span>
</div>
<div class="container">
{#each surfers as surfer, id}
<div class="box">
<div class="square" style="background-color: {surfer.color};">
{#if surfer.priority != ''}
<span class="priority">{surfer.priority}</span>
{/if}
</div>
<div class="text">{surfer.name}</div>
<div class="score">{surfer.score}</div>
</div>
{/each}
</div>
{#if width < 768}
<div class="footer">
<div>waiting for scores</div>
<!-- <div class="score">6.00</div>
<div class="score">6.00</div>
<div class="score">6.00</div>
<div class="score">6.00</div>
<div class="score">6.00</div> -->
</div>
{/if}
<style>
:root {
--backColor: #334;
--textColor: white;
--maxWidth: (100% - 15px);
}
.header {
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
margin-top: 2px;
margin-bottom: 2px;
width: calc(var(--maxWidth));
color: var(--textColor);
display: flex;
justify-content: space-between;
}
.header .title {
font-size: 2vh;
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
flex: 1 1 0;
align-self: center;
}
.header .heat {
font-size: 2vh;
padding-left: 0px;
padding-right: 10px;
font-weight: bold;
flex: 1 1 auto;
align-self: center;
text-align: right;
}
.header .timer {
font-size: 5vh;
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
flex: 2 2 auto;
align-self: center;
text-align: center;
}
.container {
background-color: var(--backColor);
padding: 10px;
width: calc(var(--maxWidth));
color: var(--textColor);
}
.box {
width: 100%;
display: flex;
align-items: center;
padding: 4px;
margin-bottom: 2px;
}
.priority {
width: 60%;
height: 60%;
background-color: white;
color: black;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.8rem;
font-weight: bold;
}
.square {
width: 60px;
height: 60px;
border-radius: 5px;
margin-right: 20px;
display: flex;
align-items: center;
justify-content: center;
position: relative; /* Per il posizionamento del testo */
}
.text {
flex: 1;
font-size: 1.5rem;
font-weight: lighter;
}
.score {
float: right;
padding-right: 10px;
text-align: center;
width: 20%;
font-size: 2.5rem;
font-weight: bold;
}
.footer {
background-color: var(--backColor);
padding: 10px;
width: calc(var(--maxWidth));
color: var(--textColor);
display: flex;
font-size: larger;
font-weight: bold;
align-items: center;
justify-content: space-around;
align-content: center;
margin-top: 2px;
}
/* .footer .score {
float: right;
padding-right: 10px;
text-align: center;
width: 20%;
font-size: 1.5rem;
font-weight: lighter;
} */
/* @media screen and (min-width: 768px) {
.container {
height: 85vh;
}
.priority {
width: 15%;
height: 100%;
border-radius: 20%;
font-size: 8vh;
animation: blink 2s infinite;
margin-right: auto;
}
@keyframes blink {
0% {
background-color: white;
}
50% {
background-color: rgba(255, 255, 255, 0.6);
}
100% {
background-color: white;
}
}
.square {
width: 100%;
height: 15vh;
margin-right: 20px;
}
.score {
padding-right: 40px;
width: 20%;
font-size: 14vh;
}
.text {
flex: 0;
font-size: 0;
}
.header {
padding: 15px;
margin-bottom: 2px;
height: 10vh;
width: calc(100% - 15px);
}
.header .timer {
font-size: 6rem;
padding-left: 10px;
padding-right: 20px;
}
} */
/* @media screen and (min-width: 1280px) {
.priority {
width: 120px;
height: 90%;
font-size: 6rem;
}
.square {
width: 100%;
height: 125px;
margin-right: 20px;
}
.score {
float: right;
padding-right: 40px;
width: 20%;
font-size: 6rem;
}
.header .timer {
font-size: 6rem;
padding-left: 10px;
padding-right: 20px;
}
}
@media screen and (min-width: 1920px) {
.priority {
width: 200px;
font-size: 10rem;
}
.square {
width: 100%;
height: 205px;
border-radius: 5px;
margin-right: 20px;
}
.score {
padding-right: 40px;
width: 20%;
font-size: 11rem;
}
.header .timer {
font-size: 8rem;
padding-left: 10px;
padding-right: 20px;
}
} */
</style>

View file

@ -0,0 +1,399 @@
<script>
// import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import { dev } from '$app/environment';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
let heat_number;
let heats = [];
$: surfers = [];
// { name: 'Kanoa Igarashi', color: 'red', score: '4.50', priority: '3' },
// { name: 'Griffin Colapinto', color: 'white', score: '5.60', priority: 'P' },
// { name: 'Jack Robinson', color: 'blue', score: '6.10', priority: '5' },
// { name: 'Gabriel Medina', color: 'green', score: '4.30', priority: '2' },
// { name: 'Italo Ferreira', color: 'black', score: '6.50', priority: '4' }
// ];
let width;
// $: activeUrl = $page.url.pathname;
const pad2 = (number) => `00${number}`.slice(-2);
$: min = 0;
$: sec = 0;
let end = false;
let start = false;
loadHeats();
function Subscribe() {
const sse = new EventSource(`/api/sse`);
console.log('subscribe');
sse.onmessage = (e) => {
let Msg = JSON.parse(e.data);
console.log(`received: ${JSON.stringify(Msg)}`);
if (Msg.mode === 'priority') {
console.log(`priority: ${Msg.priority}`);
for (let i in surfers) {
surfers[i].priority = Msg.priority[i];
}
} else if (Msg.mode === 'time') {
// console.log(`duration: ${Msg.duration}`);
let min_sec = Msg.duration.split(":");
min = min_sec[0];
sec = min_sec[1];
// console.log(`min & sec = ${min} & ${sec}`);
if (!start) {
start = true;
}
} else if (Msg.mode === 'stop') {
console.log(`stop duration: ${Msg.duration}`);
end = true;
start = false;
}
};
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
async function dblclick(id) {
console.log(`dblclick = ${surfers[id]}`);
if (surfers[id] === 'P') {
console.log('pressed P');
} else {
console.log(`pressed: [${id}] ${surfers[id].priority}`);
}
const res = await fetch(`/api/stopheat`);
const data = await res.json();
console.log(`stop: ${JSON.stringify(data)}`);
}
async function startHeat() {
const res = await fetch(`/api/startheat`, {
method: 'POST',
body: JSON.stringify({
duration: min+"m",
mode: 'time'
}),
headers: {
'Content-Type': 'application/json'
}
});
console.log(`retval: ${JSON.stringify(res)}`);
start = true;
end = false;
}
async function loadHeats() {
const res = await fetch(`/api/loadheats`);
const data = await res.json();
for (let i in data) {
heats[i] = data[i];
console.log(`${i} retval: ${JSON.stringify(data[i])}`);
}
}
function setHeat(id) {
console.log(`setHeat: ${id}`);
if (id === "99") {
min = 0;
surfers = []
return;
}
min = heats[id].timer;
surfers = heats[id].surfers;
}
async function click(id) {
let max = surfers.length;
console.log(surfers[id]);
if (surfers[id].priority === 'P') {
for (let i in surfers) {
if (i != id) {
let pos = parseInt(surfers[i].priority) - 1;
if (pos === 1) {
surfers[i].priority = 'P';
} else {
surfers[i].priority = pos.toString();
}
}
}
surfers[id].priority = max.toString();
} else if (surfers[id].priority === '') {
console.log(`priority empty; pressed: [${id}] ${surfers[id].priority}`);
for (let i in surfers) {
console.log(`looping(${id}): ${i} - ${surfers[i].priority}`);
if (i != id) {
if (surfers[i].priority === '') {
console.log(`empty: [${i}] ${surfers[i].priority}`);
continue;
} else {
console.log(`not empty: [${i}] ${surfers[i].priority}`);
let pos = parseInt(surfers[i].priority) - 1;
if (pos === 1) {
surfers[i].priority = 'P';
} else {
surfers[i].priority = pos.toString();
}
}
}
}
surfers[id].priority = max.toString();
} else {
console.log(`pressed: [${id}] ${surfers[id].priority}`);
let oldpos = parseInt(surfers[id].priority);
for (let i in surfers) {
if (i != id) {
console.log(`pos: [${i}] ${surfers[i].priority} ${surfers[i].priority > oldpos}`);
if (surfers[i].priority != 'P' && surfers[i].priority > oldpos) {
let pos = parseInt(surfers[i].priority);
if (pos > oldpos) {
surfers[i].priority = (pos - 1).toString();
console.log(`newpos: ${surfers[i].priority}`);
}
}
} else {
surfers[i].priority = max.toString();
console.log(`last: [${i}] ${surfers[i].priority}`);
}
}
}
const res = await fetch(`/api/priority`, {
method: 'POST',
body: JSON.stringify({
priority: surfers.map((obj) => obj.priority),
mode: 'priority'
}),
headers: {
'Content-Type': 'application/json'
}
});
console.log(
JSON.stringify({
priority: surfers.map((obj) => obj.priority),
mode: 'priority'
})
);
console.log(`retval: ${JSON.stringify(res)}`);
}
onMount(() => {
const unsub = Subscribe();
return unsub;
});
// onDestroy(() => {
// clearInterval(timer); // Pulisci il timer quando il componente viene distrutto
// });
</script>
<svelte:window bind:innerWidth={width} />
<div class="header">
<!-- <button class="button" on:click={() => loadHeats()} disabled={start}>Load</button> -->
<select name="heats" id="heats" bind:value={heat_number} on:change={() => setHeat(heat_number)}>
<option value="99">Select Heat</option>
{#each heats as heat, id}
<option value={id}>{heat.name} {heat.number} {heat.category}</option>
{/each}
</select>
{#if !end}
<div class="timer">{pad2(min)}:{pad2(sec)}</div>
{:else}
<div class="timer" style="color: red">{pad2(min)}:{pad2(sec)}</div>
{/if}
<button class="button" on:click={() => startHeat()} disabled={start}>Start</button>
</div>
<div class="container">
{#each surfers as surfer, id}
<div class="box">
<div
class="square"
{id}
style="background-color: {surfer.color};"
on:click={() => click(id)}
on:contextmenu={(e) => {
e.preventDefault();
dblclick(id);
}}
on:keypress={console.log('keypress')}
role="button"
tabindex={id}
>
{#if surfer.priority != ''}
{#if surfer.priority === 'P'}
{#if surfer.color === 'white'}
<span class="priority_white">{surfer.priority}</span>
{:else}
<span class="priority">{surfer.priority}</span>
{/if}
{:else}
<span class="priority_small">{surfer.priority}</span>
{/if}
{/if}
</div>
<div class="score">{surfer.score}</div>
</div>
{/each}
</div>
<style>
:root {
--backColor: #334;
--textColor: white;
--maxWidth: (100% - 15px);
}
.header {
/* padding: 15px; */
height: 10vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
margin-top: 2px;
margin-bottom: 2px;
width: calc(var(--maxWidth));
color: var(--textColor);
display: flex;
justify-content: space-between;
align-items: center;
}
.header .timer {
font-size: 13vh;
padding-left: 10px;
padding-right: 20px;
font-weight: bold;
flex: 2 2 auto;
align-self: center;
text-align: center;
}
.header .button {
height: 60%;
align-items: center;
background-color: yellow;
border-radius: 10%;
}
.container {
height: 85vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 4px;
width: calc(var(--maxWidth));
color: var(--textColor);
}
.box {
width: 100%;
display: flex;
align-items: center;
padding: 1px;
margin-bottom: 1px;
}
.priority {
width: 15%;
height: 100%;
border-radius: 20%;
font-size: 8vh;
/* animation: blink 2s 2; */
margin-right: auto;
background-color: white;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.priority_white {
width: 15%;
height: 100%;
border-radius: 20%;
font-size: 8vh;
/* animation: blink_white 2s 3; */
margin-right: auto;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
padding-bottom: 4px;
}
.priority_small {
width: 13%;
height: 85%;
border-radius: 20%;
font-size: 8vh;
/* animation: blink 2s 3; */
margin-right: auto;
margin-left: 30px;
background-color: #ccc;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.square {
width: 100%;
height: 16vh;
border-radius: 5px;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 2px;
margin-bottom: 2px;
}
.score {
padding-right: 20px;
width: 20%;
font-size: 12vh;
float: right;
text-align: center;
font-weight: bold;
}
@keyframes blink {
0% {
background-color: white;
}
50% {
background-color: rgba(255, 255, 255, 0.6);
}
100% {
background-color: white;
}
}
@keyframes blink_white {
0% {
background-color: black;
}
50% {
background-color: rgba(0, 0, 0, 0.6);
}
100% {
background-color: black;
}
}
</style>

View file

@ -0,0 +1,376 @@
<script>
import { onMount } from 'svelte';
import Logo from "$lib/img/topscorer_logo_web.png"
let colors = [
"black",
"blue",
"red",
"green",
"yellow",
"pink",
"white",
];
let rounds = [
"Qualifying",
"Opening",
"Elimination",
"Round of 48",
"Round of 32",
"Round of 16",
"Quarterfinal",
"Semifinal",
"Final",
];
let categories = [
"Under 12 Women",
"Under 12 Men",
"Under 14 Women",
"Under 14 Men",
"Under 16 Women",
"Under 16 Men",
"Under 18 Women",
"Under 18 Men",
];
$: surfers = 2;
$: heats = [];
let surfer_list = [];
$: heat = {};
resetHeat();
loadHeats();
function resetHeat() {
surfers = 2;
surfer_list = new Array();
surfer_list.push({
name: '',
color: '',
score: '',
priority: ''
});
surfer_list.push({
name: '',
color: '',
score: '',
priority: ''
});
heat = {
number: 1,
name: '',
category: '',
timer: 20,
surfers: surfer_list
}
}
async function loadHeats() {
const res = await fetch(`/api/loadheats`);
const data = await res.json();
for (let i in data) {
heats[i] = data[i];
console.log(`${i} retval: ${JSON.stringify(data[i])}`);
}
}
async function deleteHeat(id) {
const res = await fetch(`/api/deleteheat`, {
method: 'POST',
body: JSON.stringify(heats[id]),
headers: {
'Content-Type': 'application/json'
}
});
console.log(`retval: ${JSON.stringify(res)}`);
console.log(JSON.stringify(heats[id]));
resetHeat();
loadHeats();
}
function setHeat(id) {
resetHeat();
console.log(`setHeat: ${id}`);
console.log(heats[id]);
heat.number = heats[id].number;
heat.name = heats[id].name;
heat.category = heats[id].category;
heat.timer = heats[id].timer;
surfer_list = heats[id].surfers;
surfers = surfer_list.length;
}
function addSurfers() {
surfers++;
surfer_list.push({
name: '',
color: '',
score: '',
priority: ''
});
}
function removeSurfers() {
surfers--;
if (surfers < 2) surfers = 2;
surfer_list.pop();
}
async function save() {
if(hasDuplicateColors(surfer_list)) {
alert('Colors must be unique');
return;
}
if(surfer_list.length < 2) {
alert('Must have at least 2 surfers');
return;
}
if(heat.name === '') {
alert('Must have a name');
return;
}
if(heat.category === '') {
alert('Must have a category');
return;
}
if(heat.number === '') {
alert('Must have a number');
return;
}
if(heat.timer === '') {
alert('Must have a timer');
return;
}
heat.surfers = surfer_list;
const res = await fetch(`/api/saveheat`, {
method: 'POST',
body: JSON.stringify(heat),
headers: {
'Content-Type': 'application/json'
}
});
console.log(`retval: ${JSON.stringify(res)}`);
console.log(JSON.stringify(heat));
resetHeat();
loadHeats();
}
function hasDuplicateColors(arr) {
const colors = [];
console.log(JSON.stringify(arr));
for(let i = 0; i < arr.length; i++) {
const color = arr[i].color;
if(colors.includes(color)) {
console.log(`duplicate color: ${color}`);
return true;
}
colors.push(color);
}
return false;
}
function capitalize(element, elementName) {
element[elementName] = element[elementName].charAt(0).toUpperCase() + element[elementName].slice(1);
console.log(`element: ${element[elementName]}`);
}
onMount(() => {
resetHeat();
loadHeats();
});
</script>
<div class="header">
<img class="img" src={Logo} alt="logo">
<span class="title" style="color: aliceblue;">Heat setup</span>
</div>
<div class="container">
<div class="heat">
<label class="label" for="heat">Heat</label>
<select name="heat" id="heat" bind:value={heat.name}>
{#each rounds as round}
<option value={round}>{round}</option>
{/each}
</select>
<!-- <input bind:value={heat.name} on:change={capitalize(heat, "name")} id="name" type="text"> -->
<label class="label" for="number">Number</label>
<input bind:value={heat.number} id="number" type="number" min="1" max="20">
<label class="label" for="category">Category</label>
<select name="category" id="category" bind:value={heat.category}>
{#each categories as category}
<option value={category}>{category}</option>
{/each}
</select>
<!-- <input bind:value={heat.category} on:change={capitalize(heat, "category")} id="category" type="text"> -->
<label class="label" for="timer">Duration</label>
<input bind:value={heat.timer} id="timer" type="number" min="5" max="60" step="5"> <!-- on:keydown={(event) => {event.preventDefault()}} -->
</div>
<hr>
<button class="plus" on:click={() => {addSurfers();}}>+</button>
<span class="surfers">{surfers}</span>
<button class="plus" on:click={() => {removeSurfers();}}>-</button>
{#each Array(surfers) as _, surfer}
<div class="surfer">
<label class="label" for="name{surfer}">Name</label>
<input bind:value={surfer_list[surfer].name} on:change={capitalize(surfer_list[surfer], "name")} id="name{surfer}" type="text">
<label class="label" for="color{surfer}">Color</label>
<select name="color" id="color{surfer}" bind:value={surfer_list[surfer].color} style="background-color: {surfer_list[surfer].color};">
{#each colors as color}
<option value={color} style="background-color: {color};">{color}</option>
{/each}
<!-- <option value="red" style="background-color: red;">Select color</option> -->
<!-- <option value="red" style="background-color: red;">Red</option>
<option value="blue" style="background-color: blue;">Blue</option>
<option value="green" style="background-color: green;">Green</option>
<option value="yellow" style="background-color: yellow;">Yellow</option>
<option value="orange" style="background-color: orange;">Orange</option>
<option value="violet" style="background-color: violet;">Violet</option> -->
</select>
<!-- <input bind:value={surfer_list[surfer].color} type="color" id="color{surfer}"> -->
</div>
{/each}
</div>
<button class="plus" on:click={() => {save();}}>SAVE</button>
<button class="plus" on:click={() => {resetHeat();}}>RESET</button>
<hr>
{#each heats as h, id}
<div class="surfer">
<button on:click={() => {setHeat(id);}}>{h.name} {h.number} {h.category}</button>
<button class="plus" on:click={() => {deleteHeat(id);}}>X</button>
</div>
{/each}
<!-- <hr> -->
<!-- <h2>{JSON.stringify(surfer_list)}</h2> -->
<style>
.container {
display: block;
}
.header {
background-color: black;
display: flex;
width: 100%;
justify-content: space-between;
text-align: center;
align-items: center;
}
.header .img {
height: 3rem;
}
.header .title {
font-size: 3rem;
margin: 0 auto;
}
.label {
border: 2px solid #555;
background-color: gray;
border-radius: 8px;
padding: 2px;
}
.heat {
font-size: 1.3rem;
margin-top: 8px;
margin-bottom: 2px;
width: 100%;
margin-left: auto;
margin-right: auto;
/* color: lightcyan; */
display: inline-block;
}
.heat input {
font-size: 1.2rem;
border-radius: 6px;
margin-left: 0.1rem;
margin-right: 0.1rem;
padding-top: 2px;
padding-bottom: 2px;
}
.heat select {
font-size: 1.2rem;
border-radius: 6px;
margin-left: 0.1rem;
margin-right: 0.1rem;
padding-top: 2px;
padding-bottom: 2px;
}
.surfer {
font-size: 1.3rem;
margin-top: 2px;
margin-bottom: 2px;
/* color: lightcyan; */
display: inline-flex;
width: 100%;
margin-left: auto;
margin-right: auto;
border-radius: 8px;
}
.surfer input {
font-size: 1.2rem;
border-radius: 6px;
margin-left: 0.1rem;
margin-right: 0.1rem;
padding-top: 2px;
padding-bottom: 2px;
}
.surfer select {
font-size: 1.2rem;
}
.plus {
border-radius: 8px;
margin-left: 0.2rem;
margin-right: 0.2rem;
margin-bottom: 8px;
margin-top: 8px;
}
.surfers {
border: 1px solid #111;
background-color: yellow;
border-radius: 8px;
padding: 5px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,22 @@
import { vitePreprocess } from '@sveltejs/kit/vite';
// import adapter from '@sveltejs/adapter-auto';
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({
pages: '../backend/static',
assets: '../backend/static',
fallback: 'index.html',
precompress: true,
strict: true
})
},
preprocess: [vitePreprocess({})]
};
export default config;

View file

@ -0,0 +1,14 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
css: {
preprocessorOptions: {
sass: {
additionalData: '@use "src/variables.sass" as *'
}
}
}
});