2024-06-02 15:40:04 +02:00
|
|
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
|
|
|
/** @type {HTMLCanvasElement} */
|
|
|
|
|
const $canvas = document.getElementById('canvas')
|
|
|
|
|
|
|
|
|
|
const TYPES = {
|
|
|
|
|
TEXT: 0,
|
|
|
|
|
LINE: 1,
|
|
|
|
|
CIRCLE: 2,
|
|
|
|
|
CIRCLE_FILL: 3,
|
|
|
|
|
RECT: 4,
|
|
|
|
|
RECT_FILL: 5,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ALIGN = {
|
|
|
|
|
START: 0,
|
|
|
|
|
CENTER: 1,
|
|
|
|
|
END: 2,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FONTS = {
|
|
|
|
|
0: '24px FreeSans',
|
|
|
|
|
1: '48px FreeSans',
|
|
|
|
|
2: 'bold 124px "Fira Code"',
|
|
|
|
|
3: '18px FreeSans',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {{ x: number; y: number; t: number; c?: number; va?: number; ha?: number; f?: number; x2?: number; y2?: number; w?: number; h?: number; r?: number }[]} data
|
|
|
|
|
*/
|
|
|
|
|
function render(data) {
|
|
|
|
|
const ctx = $canvas.getContext('2d')
|
|
|
|
|
|
|
|
|
|
if (!ctx) {
|
|
|
|
|
throw new Error('Canvas 2D context is not available')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx.clearRect(0, 0, $canvas.width * 2, $canvas.height * 2)
|
|
|
|
|
ctx.textBaseline = 'alphabetic'
|
|
|
|
|
ctx.fillStyle = '#222'
|
|
|
|
|
ctx.strokeStyle = '#222'
|
2024-06-30 09:06:59 +02:00
|
|
|
ctx.imageSmoothingEnabled = false
|
2024-06-02 15:40:04 +02:00
|
|
|
|
|
|
|
|
for (const item of data) {
|
|
|
|
|
switch (item.t) {
|
|
|
|
|
case TYPES.TEXT: {
|
|
|
|
|
const content = item.c
|
|
|
|
|
const x = item.x
|
|
|
|
|
const y = item.y
|
|
|
|
|
const verticalAlign = item.va ?? 0
|
|
|
|
|
const horizontalAlign = item.ha ?? 0
|
|
|
|
|
const font = item.f ?? 0
|
|
|
|
|
|
|
|
|
|
ctx.font = FONTS[font]
|
|
|
|
|
|
|
|
|
|
const measured = ctx.measureText(content)
|
|
|
|
|
|
|
|
|
|
let xx = x
|
|
|
|
|
let yy = y
|
|
|
|
|
|
|
|
|
|
switch (verticalAlign) {
|
|
|
|
|
case ALIGN.START:
|
|
|
|
|
yy = y + measured.actualBoundingBoxAscent
|
|
|
|
|
break
|
|
|
|
|
case ALIGN.CENTER:
|
|
|
|
|
yy = y + measured.actualBoundingBoxAscent / 2
|
|
|
|
|
break
|
|
|
|
|
case ALIGN.END:
|
|
|
|
|
yy = y
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (horizontalAlign) {
|
|
|
|
|
case ALIGN.START:
|
|
|
|
|
xx = x
|
|
|
|
|
break
|
|
|
|
|
case ALIGN.CENTER:
|
|
|
|
|
xx = x - measured.width / 2
|
|
|
|
|
break
|
|
|
|
|
case ALIGN.END:
|
|
|
|
|
xx = x - measured.width
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx.fillText(content, Math.round(xx), Math.round(yy))
|
|
|
|
|
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case TYPES.LINE: {
|
|
|
|
|
const x = item.x
|
|
|
|
|
const y = item.y
|
|
|
|
|
const x2 = item.x2
|
|
|
|
|
const y2 = item.y2
|
|
|
|
|
|
2024-06-30 09:06:59 +02:00
|
|
|
ctx.translate(0.5, 0.5)
|
2024-06-02 15:40:04 +02:00
|
|
|
ctx.moveTo(x, y)
|
|
|
|
|
ctx.lineTo(x2, y2)
|
|
|
|
|
ctx.stroke()
|
2024-06-30 09:06:59 +02:00
|
|
|
ctx.resetTransform()
|
2024-06-02 15:40:04 +02:00
|
|
|
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case TYPES.CIRCLE: {
|
|
|
|
|
const x = item.x
|
|
|
|
|
const y = item.y
|
|
|
|
|
const radius = item.r
|
|
|
|
|
|
2024-06-30 09:06:59 +02:00
|
|
|
ctx.translate(0.5, 0.5)
|
2024-06-02 15:40:04 +02:00
|
|
|
ctx.beginPath()
|
|
|
|
|
ctx.arc(x, y, radius, 0, 2 * Math.PI)
|
|
|
|
|
ctx.stroke()
|
2024-06-30 09:06:59 +02:00
|
|
|
ctx.resetTransform()
|
2024-06-02 15:40:04 +02:00
|
|
|
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case TYPES.CIRCLE_FILL: {
|
|
|
|
|
const x = item.x
|
|
|
|
|
const y = item.y
|
|
|
|
|
const radius = item.r
|
|
|
|
|
|
|
|
|
|
ctx.beginPath()
|
|
|
|
|
ctx.arc(x, y, radius, 0, 2 * Math.PI)
|
|
|
|
|
ctx.fill()
|
|
|
|
|
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case TYPES.RECT: {
|
|
|
|
|
const x = item.x
|
|
|
|
|
const y = item.y
|
|
|
|
|
const w = item.x2
|
|
|
|
|
const h = item.y2
|
|
|
|
|
|
2024-06-30 09:06:59 +02:00
|
|
|
ctx.translate(0.5, 0.5)
|
2024-06-02 15:40:04 +02:00
|
|
|
ctx.strokeRect(x, y, w, h)
|
2024-06-30 09:06:59 +02:00
|
|
|
ctx.resetTransform()
|
2024-06-02 15:40:04 +02:00
|
|
|
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case TYPES.RECT_FILL: {
|
|
|
|
|
const x = item.x
|
|
|
|
|
const y = item.y
|
|
|
|
|
const w = item.x2
|
|
|
|
|
const h = item.y2
|
|
|
|
|
|
|
|
|
|
ctx.fillRect(x, y, w, h)
|
|
|
|
|
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const WIDTH = 648
|
|
|
|
|
const HEIGHT = 480
|
|
|
|
|
|
|
|
|
|
const hours = new Date().getHours().toString().padStart(2, '0')
|
|
|
|
|
const minutes = new Date().getMinutes().toString().padStart(2, '0')
|
|
|
|
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
fetch('/api/dow').then((r) => r.json().then((data) => render(data)))
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
|
|
render([
|
|
|
|
|
{
|
|
|
|
|
x: Math.round(WIDTH / 2),
|
|
|
|
|
y: 20,
|
|
|
|
|
t: TYPES.TEXT,
|
|
|
|
|
c: `...`,
|
|
|
|
|
va: ALIGN.START,
|
|
|
|
|
ha: ALIGN.CENTER,
|
|
|
|
|
f: 2,
|
|
|
|
|
},
|
|
|
|
|
])
|