Refactoring and dashboard state in DB

This commit is contained in:
2022-07-01 01:43:11 +02:00
parent 8d8a6ff784
commit fc8bb1c482
13 changed files with 591 additions and 153 deletions
+5
View File
@@ -4,8 +4,13 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Homepage</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,600;1,300;1,400;1,600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles/main.scss">
</head>
<body>
@@ -0,0 +1,13 @@
export const MessageWidget = ({ title, message }) => {
title = title || 'Testo'
message = message || 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Sed, possimus.'
return (
<>
<div class="title">{title}</div>
<div class="content">
<p>{message}</p>
</div>
</>
)
}
@@ -0,0 +1,82 @@
import { useEffect, useRef } from 'preact/hooks'
import { hashCode } from '../util.js'
const CANVAS_SIZE = 350
export const PieChartWidget = ({ title, parts, labels, total }) => {
title = title || 'Grafico a torta'
parts = parts || [1]
labels = labels || []
const canvasRef = useRef()
useEffect(() => {
if (canvasRef.current) {
const $canvas = canvasRef.current
$canvas.style.width = `${CANVAS_SIZE}px`
$canvas.style.height = `${CANVAS_SIZE}px`
const width = $canvas.width / 2
const height = $canvas.height / 2
const g = $canvas.getContext('2d')
g.resetTransform()
g.clearRect(0, 0, width * 2, height * 2)
g.scale(2, 2)
g.translate(width / 2, height / 2)
g.font = `18px 'Open Sans'`
g.textAlign = 'center'
g.textBaseline = 'middle'
total = total || parts.reduce((acc, p) => acc + p)
const anglesAndLabel = parts.map((p, i) => [(p / total) * 2 * Math.PI, labels[i] || ''])
g.fillStyle = `#ededed`
g.beginPath()
g.ellipse(0, 0, width * 0.5 * 0.8, width * 0.5 * 0.8, 0, 0, 2 * Math.PI)
g.fill()
g.strokeStyle = `#00000044`
g.beginPath()
g.ellipse(0, 0, width * 0.5 * 0.8, width * 0.5 * 0.8, 0, 0, 2 * Math.PI)
g.stroke()
let acc = 0
for (const [angle, label] of anglesAndLabel) {
g.fillStyle = `hsl(${((hashCode(label) % 0xff) * 360) / 0xff}, 80%, 65%)`
g.beginPath()
g.moveTo(0, 0)
g.arc(0, 0, width * 0.5 * 0.8, acc - 0.5 * Math.PI, acc + angle - 0.5 * Math.PI)
g.fill()
g.strokeStyle = `#00000044`
g.beginPath()
g.moveTo(0, 0)
g.arc(0, 0, width * 0.5 * 0.8, acc - 0.5 * Math.PI, acc + angle - 0.5 * Math.PI)
g.stroke()
g.fillStyle = '#333'
g.fillText(
label,
Math.cos(acc + angle / 2 - 0.5 * Math.PI) * width * 0.5 * 0.9,
Math.sin(acc + angle / 2 - 0.5 * Math.PI) * width * 0.5 * 0.9
)
acc += angle
}
}
}, [canvasRef, parts])
return (
<>
<div class="title">{title}</div>
<div class="content">
<canvas ref={canvasRef} width={CANVAS_SIZE * 2} height={CANVAS_SIZE * 2}></canvas>
</div>
</>
)
}
+49 -5
View File
@@ -1,9 +1,53 @@
import { render } from 'preact'
import { useEffect, useState } from 'preact/hooks'
import { MessageWidget } from './components/MessageWidget.jsx'
import { PieChartWidget } from './components/PieChartWidget.jsx'
import { useUser } from './util.js'
const App = () => (
<>
<h1>Homepage</h1>
</>
)
const WidgetTypes = {
pie: PieChartWidget,
message: MessageWidget,
}
const Widget = ({ type, value }) => {
const CustomWidget = WidgetTypes[type]
return (
<div class={'widget ' + type}>
<CustomWidget {...value} />
</div>
)
}
const App = () => {
const user = useUser()
const [widgets, setWidgets] = useState([])
useEffect(() => {
fetch('/api/dashboard-state')
.then(res => res.json())
.then(state => setWidgets(state.widgets))
.catch(e => console.error(e))
}, [])
return (
<>
<header>
<div class="logo">Dashboard</div>
<div class="spacer">&bull;</div>
<div class="machine">space.phc.dm.unipi.it</div>
</header>
<p>
(Viewing page as <b>{user}</b>)
</p>
<div class="widgets">
{widgets.map(w => (
<Widget {...w} />
))}
</div>
</>
)
}
render(<App />, document.querySelector('main'))
+30
View File
@@ -0,0 +1,30 @@
import { useEffect, useState } from 'preact/hooks'
export function hashCode(s) {
s = s.toString() + "seed iniziale dell'hash"
let hash = 0
if (s.length === 0) return hash
for (let i = 0; i < s.length; i++) {
const chr = s.charCodeAt(i)
hash = (hash << 5) - hash + chr
hash |= 0
}
return Math.abs(hash)
}
export function useUser() {
const [user, setUser] = useState(null)
useEffect(() => {
fetch('/api/current-user')
.then(res => res.json())
.then(value => setUser(value))
.catch(e => console.error(e))
}, [])
return user
}
+108 -3
View File
@@ -4,19 +4,124 @@
box-sizing: border-box;
}
:root {
--bg-100: #ffffff;
--bg-500: #f0f0f0;
--bg-600: #e0e0e0;
--fg-400: #3d3d3d;
--fg-500: #333;
--ft-sans: 'Open Sans', sans-serif;
--ft-sans-wt-light: 300;
--ft-sans-wt-normal: 400;
--ft-sans-wt-bold: 600;
}
body {
margin: 0;
width: 100%;
min-height: 100vh;
font-family: 'Inter', 'Segoe UI', 'Helvetica', 'Arial', sans-serif;
font-family: var(--ft-sans);
font-size: 16px;
color: var(--fg-500);
background: var(--bg-500);
}
// Headings
//
// Structure
//
$base-font-size: 18px;
main {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
header {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 0.5rem 1rem;
gap: 0.75rem;
background: var(--bg-100);
border-bottom: 1px solid var(--bg-600);
color: var(--fg-400);
vertical-align: middle;
.logo {
font-size: 32px;
font-weight: var(--ft-sans-wt-bold);
}
.machine {
font-size: 24px;
font-weight: var(--ft-sans-wt-normal);
}
}
.widgets {
display: flex;
justify-content: center;
align-items: start;
flex-wrap: wrap;
gap: 2rem;
max-width: 120ch;
.widget {
background: var(--bg-100);
border: 1px solid var(--bg-600);
padding: 1rem;
min-height: 10rem;
max-width: 50ch;
.title {
font-weight: var(--ft-sans-wt-bold);
code {
font-weight: normal;
}
}
&.todo {
width: 300px;
height: 200px;
}
}
}
}
//
// Typography
//
b {
font-weight: var(--ft-sans-wt-bold);
}
p {
margin: 0;
& + p {
margin-top: 0.5rem;
}
}
$base-font-size: 16px;
$heading-scale: 1.33;
@function pow($number, $exponent) {