Feat/prerender and state (#8)

* pre-load data behind link elements on hover

* Enable SSR and prerendering of pages in 'auto' mode

* merge relay & regulator state in /api/state

This helps when updating relays and receiving state of relays and regulator state (idle,
heating or cooling) after emitting relay change.
This commit is contained in:
2025-01-12 18:34:16 +01:00
committed by GitHub
parent 34018285a0
commit 4d77088764
8 changed files with 86 additions and 36 deletions

View File

@@ -20,7 +20,7 @@ const routes: Array<IRoute> = [{
<ul class="navigation-cards" on:click> <ul class="navigation-cards" on:click>
{#each routes as route} {#each routes as route}
<a href={route.path}> <a href={route.path} data-sveltekit-preload-data="hover">
<li> <li>
<span>{ route.name }</span> <span>{ route.name }</span>

View File

@@ -7,13 +7,13 @@
export let relays: IRelaysDTO[] = []; export let relays: IRelaysDTO[] = [];
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
function toggleRelay(controls: string) { async function toggleRelay(controls: string) {
const url = `/api/relay/${controls}`; const url = `/api/relay/${controls}`;
const options = { const options = {
method: 'POST' method: 'POST'
}; };
fetch(url, options) await fetch(url, options)
.then((resp) => resp.json()) .then((resp) => resp.json())
.then((response) => { .then((response) => {
const changedRelay = relays.findIndex((relay) => relay.pin === response.pin); const changedRelay = relays.findIndex((relay) => relay.pin === response.pin);

16
src/routes/+layout.ts Normal file
View File

@@ -0,0 +1,16 @@
// if you want to generate a static html file
// for your page.
// Documentation: https://kit.svelte.dev/docs/page-options#prerender
export const prerender = 'auto';
// if you want to Generate a SPA
// you have to set ssr to false.
// This is not the case (so set as true or comment the line)
// Documentation: https://kit.svelte.dev/docs/page-options#ssr
export const ssr = true;
// How to manage the trailing slashes in the URLs
// the URL for about page witll be /about with 'ignore' (default)
// the URL for about page witll be /about/ with 'always'
// https://kit.svelte.dev/docs/page-options#trailingslash
export const trailingSlash = 'ignore';

View File

@@ -1,28 +1,51 @@
import { BREWLOGGER_HOST } from '$env/static/private'; import { BREWLOGGER_HOST } from '$env/static/private';
import type { PageServerLoad } from './$types'; import type { PageServerLoad, RequestEvent } from './$types';
import type { ISensorDTO } from '../lib/interfaces/ISensorDTO' import type { ISensorDTO } from '../lib/interfaces/ISensorDTO';
import { IRelaysDTO } from '../lib/interfaces/IRelaysDTO'; import { IRelaysDTO } from '../lib/interfaces/IRelaysDTO';
import { IStateDTO } from '../lib/interfaces/IStateDTO'; import { IStateDTO } from '../lib/interfaces/IStateDTO';
const sensorsUrl = `${BREWLOGGER_HOST}/api/sensors`; const sensorsUrl = `${BREWLOGGER_HOST}/api/sensors`;
const relaysUrl = `${BREWLOGGER_HOST}/api/relays`; export const prerender = true; // explicitly pre-render
const stateUrl = `${BREWLOGGER_HOST}/api/regulator`;
async function getFromEndpoint(endpoint: string) { async function fetchSensors(fetch: Fetch): Promise<ISensorDTO[]> {
return fetch(endpoint) return fetch(sensorsUrl)
.then((resp) => resp.json()) .then((resp) => resp.json())
.then((data) => data?.sensors || [])
.catch((error) => { .catch((error) => {
console.error('Failed to fetch endpoint:', endpoint); console.error('failed to fetch sensors.');
console.error(error); console.error(error);
return null return null;
}); });
} }
export const load: PageServerLoad = async () => { // calls internal sveltekit api endpoint.
const [sensorsResp, relaysResp, stateResp] = await Promise.all([getFromEndpoint(sensorsUrl), getFromEndpoint(relaysUrl), getFromEndpoint(stateUrl)]); // this allows unified response in svelte app, even
const sensors: ISensorDTO[] = sensorsResp?.sensors || [] // though it requires multiple calls to regulator server
const relays: IRelaysDTO[] = relaysResp?.relays || [] async function fetchState(fetch: Fetch) {
const state: IStateDTO = stateResp return fetch('/api/state')
.then((resp) => resp.json())
.catch((error) => {
console.error('failed to fetch state');
console.error(error);
return null;
});
}
type Fetch = typeof fetch;
type HandleFetch = {
event: RequestEvent;
request: Request;
fetch: Fetch;
};
export const load: PageServerLoad = async (input: HandleFetch) => {
const [stateResponse, sensorsResponse] = await Promise.all([
fetchState(input.fetch),
fetchSensors(input.fetch)
]);
const sensors: ISensorDTO[] = sensorsResponse || [];
const relays: IRelaysDTO[] = stateResponse?.relays || [];
const regulator: IStateDTO = stateResponse?.regulator;
const inside = sensors.find((sensor: ISensorDTO) => sensor.location === 'inside'); const inside = sensors.find((sensor: ISensorDTO) => sensor.location === 'inside');
const outside = sensors.find((sensor: ISensorDTO) => sensor.location === 'outside'); const outside = sensors.find((sensor: ISensorDTO) => sensor.location === 'outside');
@@ -30,7 +53,6 @@ export const load: PageServerLoad = async () => {
return { return {
inside: inside || null, inside: inside || null,
outside: outside || null, outside: outside || null,
relays, state: { regulator, relays }
state
}; };
}; };

View File

@@ -8,14 +8,13 @@
export let data: PageData; export let data: PageData;
const { inside, outside } = data; const { inside, outside } = data;
let { relays, state } = data; let { relays, regulator } = data.state;
const updateState = () => const updateState = () => {
setTimeout(() => { fetch('/api/state')
fetch('/api/state') .then((resp) => resp.json())
.then((resp) => resp.json()) .then((response: IStateDTO) => ({ relays, regulator } = response));
.then((response: IStateDTO) => (state = response)); }
}, 100);
</script> </script>
<Logo /> <Logo />
@@ -23,7 +22,12 @@
<div class="vertical-grid"> <div class="vertical-grid">
<BrewProgress /> <BrewProgress />
<VerticalSensorDisplay {inside} {outside} {relays} {state} /> <VerticalSensorDisplay
inside="{inside}"
outside="{outside}"
relays="{relays}"
state="{regulator}"
/>
<RelayControls bind:relays="{relays}" on:relaySwitched="{updateState}" /> <RelayControls bind:relays="{relays}" on:relaySwitched="{updateState}" />
</div> </div>

View File

@@ -1,9 +1,19 @@
import { json, RequestEvent } from '@sveltejs/kit'; import { json, RequestEvent } from '@sveltejs/kit';
import { BREWLOGGER_HOST } from '$env/static/private'; import { BREWLOGGER_HOST } from '$env/static/private';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { IRelaysDTO } from '../../../lib/interfaces/IRelaysDTO';
export const GET = (async (event: RequestEvent) => { async function fetchRegulator(): Promise<Response | IRelaysDTO[]> {
return fetch(BREWLOGGER_HOST + '/api/regulator') return fetch(BREWLOGGER_HOST + '/api/regulator').then((resp) => resp.json());
}
async function fetchRelays(): Promise<Response | IRelaysDTO[]> {
return fetch(BREWLOGGER_HOST + '/api/relays')
.then((resp) => resp.json()) .then((resp) => resp.json())
.then((response) => json(response)); .then((data) => data?.relays || []);
}
export const GET = (async () => {
const [regulatorState, relaysState] = await Promise.all([fetchRegulator(), fetchRelays()]);
return json({ regulator: regulatorState, relays: relaysState });
}) satisfies RequestHandler; }) satisfies RequestHandler;

View File

@@ -17,7 +17,7 @@
<ul> <ul>
{#each brews as brew} {#each brews as brew}
<li class="brew"> <li class="brew">
<a href="{path(brew.date)}"> <a href="{path(brew.date)}" data-sveltekit-preload-data="hover">
<img src="/images/{brew.image}" alt="Beer label of {brew.beer.name}" /> <img src="/images/{brew.image}" alt="Beer label of {brew.beer.name}" />
<div class="details"> <div class="details">

View File

@@ -4,8 +4,9 @@ import { fetchHumidity, fetchTemperature } from '../../../lib/server/graphQueryG
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
async function fetchGraphData(brew) { async function fetchGraphData(brew) {
const start = new Date(brew.date * 1000 - 86400000); const { date } = brew;
const end = new Date(brew.date * 1000 + 4838400000); const start = new Date(date * 1000 - 86400000);
const end = new Date(date * 1000 + 4838400000);
const size = 200; const size = 200;
const [temperature, humidity] = await Promise.all([ const [temperature, humidity] = await Promise.all([
@@ -13,10 +14,7 @@ async function fetchGraphData(brew) {
fetchHumidity(start, end, size) fetchHumidity(start, end, size)
]); ]);
return { return { temperature, humidity };
temperature,
humidity
};
} }
export const load = (async ({ params }) => { export const load = (async ({ params }) => {