Refactoring and dashboard state in DB
parent
8d8a6ff784
commit
fc8bb1c482
@ -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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,9 +1,53 @@
|
|||||||
import { render } from 'preact'
|
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 = () => (
|
const WidgetTypes = {
|
||||||
<>
|
pie: PieChartWidget,
|
||||||
<h1>Homepage</h1>
|
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">•</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'))
|
render(<App />, document.querySelector('main'))
|
||||||
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue