mirror of
https://github.com/KevinMidboe/brewPi.git
synced 2025-10-29 08:40:13 +00:00
Compare commits
25 Commits
feat/graph
...
1c66c841ab
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c66c841ab | |||
| 6f80aee823 | |||
| 699a67161b | |||
| 20f4a173d4 | |||
| c146ffccfd | |||
| c0a46b8cca | |||
| d5e46af091 | |||
| 2da44956be | |||
| b34b18ad37 | |||
| 0071bd3791 | |||
| 38e42bb37c | |||
| 3a8587b2ad | |||
| c1953517e7 | |||
| f5f759ca0a | |||
| b9a5fde53f | |||
| 0218c048e9 | |||
| 89b17ea714 | |||
| c16bce0a1a | |||
| a60dc726eb | |||
| 0bcc49c8cf | |||
| 3bb08cfbd3 | |||
| f5ada7d711 | |||
| 5575d01e63 | |||
| 94c27b0a8d | |||
| 4008b8aee9 |
24
add_brew.sh
Normal file
24
add_brew.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
brewFile=src/brews.json
|
||||||
|
|
||||||
|
date=$(date +%s)
|
||||||
|
|
||||||
|
# Ask the user for login details
|
||||||
|
read -p 'Name: ' name
|
||||||
|
read -p 'Brewery: ' brewery
|
||||||
|
read -p 'Category: ' category
|
||||||
|
read -p 'By: ' by
|
||||||
|
read -p 'Abv: ' abv
|
||||||
|
read -p 'Description: ' description
|
||||||
|
read -p 'Image filename: ' image
|
||||||
|
read -p 'Recipe url: ' recipe
|
||||||
|
read -p 'Order page url: ' orderPage
|
||||||
|
read -p 'Untapped url: ' untapped
|
||||||
|
read -p 'Primary color: ' colorPrimary
|
||||||
|
|
||||||
|
brew="{\"beer\":{\"name\":\"$name\",\"brewery\":\"$brewery\",\"category\":\"$category\"},\"date\":\"$date\",\"by\":\"$by\",\"abv\":\"$abv\",\"description\":\"$description\",\"image\":\"$image\",\"recipe\":\"$recipe\",\"order_page\":\"$orderPage\",\"untapped\":\"$untapped\",\"color_primary\":\"$colorPrimary\",\"color_secondary\":\"$colorSecondary\"}"
|
||||||
|
|
||||||
|
echo $(cat $brewFile | jq ".brews |= [$brew] + .") > $brewFile
|
||||||
|
|
||||||
|
echo "Brew $name added to $brewFile successfully!"
|
||||||
220
src/brews.json
220
src/brews.json
@@ -1,91 +1,129 @@
|
|||||||
[{
|
{
|
||||||
"beer": {
|
"brews": [
|
||||||
"name": "Kveldsbris",
|
{
|
||||||
"brewery": "Kinn Bryggeri",
|
"beer": {
|
||||||
"category": "Pilsner/Lys Lager",
|
"name": "Ink & Dagger",
|
||||||
"description": ""
|
"brewery": "Amundsen",
|
||||||
},
|
"category": "Hazy Session IPA"
|
||||||
"date": "1682272800",
|
},
|
||||||
"by": ["Alf", "Kevin"],
|
"date": "1687631876",
|
||||||
"abv": "5.6",
|
"by": ["Andreas", "Ingvild", "Kevin"],
|
||||||
"description": "",
|
"abv": "4.2",
|
||||||
"image": "kinn_kveldsbris.png",
|
"description": "Ink & Dagger - Den legendariske IPA`en fra Amundsen Bryggeri er et unikt bryggesett som vil ta deg med på en smaksopplevelse du sent vil glemme. Med et imponerende utvalg av humler, gir dette ølet en kompleks og balansert smak som vil tilfredsstille enhver ølentusiast..",
|
||||||
"recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ",
|
"image": "amundsen_ink-dagger.jpg",
|
||||||
"order_page": "https://oslo.bryggselv.no/finest/104923/finest-originals-utepils-allgrain-ølsett-25-liter",
|
"recipe": "https://www.dropbox.com/s/uqscpf5jsnxg7fv/Ink%20and%20Dagger%2025ltr.pdf?dl=0",
|
||||||
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024"
|
"order_page": "https://www.hopstore.no/products/amundsen-ink-and-dagger-25l-olsett",
|
||||||
}, {
|
"untapped": "https://untappd.com/b/amundsen-bryggeri-ink-and-dagger/1785462",
|
||||||
"beer": {
|
"color_primary": "#76C5BF",
|
||||||
"name": "FUCK YEAH IPA",
|
"color_secondary": "rgba(118, 197, 191, 0.2)"
|
||||||
"brewery": "Finest",
|
},
|
||||||
"category": "American IPA",
|
{
|
||||||
"description": ""
|
"beer": {
|
||||||
},
|
"name": "Kveldsbris",
|
||||||
"date": "1648922400",
|
"brewery": "Kinn Bryggeri",
|
||||||
"by": ["Alf", "Kevin"],
|
"category": "Belgian Pale Ale",
|
||||||
"abv": "7",
|
"description": ""
|
||||||
"description": "",
|
},
|
||||||
"image": "finest_fuck-yeah-IPA.jpg",
|
"date": "1682272800",
|
||||||
"recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ",
|
"by": ["Alf", "Kevin"],
|
||||||
"order_page": "https://web.archive.org/web/20210225043236/https://www.bryggselv.no/finest/105943/fuck-yeah-ipa-ultra-american-west-coast-ipa-25-liter",
|
"abv": "5.6",
|
||||||
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024"
|
"description": "Kveldsbris - Evening Breeze - is offered as a clean, refreshing and not-too-alcoholic Belgian ale. It has a fine balance of Belgian yeastiness with the premier hop varieties from three wildly different districts: East Kent Goldings on England's south coast, Saaz from Zatec in the Czech Republic, and Amarillo from Yakima Valley in the United States of America. Kveldsbris is a most versatile table ale that can cope with more spiciness and richness than a regular wheat beer.",
|
||||||
}, {
|
"image": "kinn_kveldsbris.png",
|
||||||
"beer": {
|
"recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ",
|
||||||
"name": "Love in a canoe",
|
"order_page": "https://oslo.bryggselv.no/finest/104923/finest-originals-utepils-allgrain-ølsett-25-liter",
|
||||||
"brewery": "Finest",
|
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024",
|
||||||
"category": "Mexican Lager",
|
"color_primary": "#FCF28F",
|
||||||
"description": ""
|
"color_secondary": "rgba(252, 242, 143, 0.2)"
|
||||||
},
|
},
|
||||||
"date": "1646420400",
|
{
|
||||||
"by": ["Alf", "Kevin"],
|
"beer": {
|
||||||
"abv": "4.7",
|
"name": "FUCK YEAH IPA",
|
||||||
"description": "",
|
"brewery": "Finest",
|
||||||
"image": "finest_love-in-a-canoe.jpeg",
|
"category": "American IPA",
|
||||||
"recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ",
|
"description": ""
|
||||||
"order_page": "https://oslo.bryggselv.no/finest/104092/love-in-a-canoe-allgrain-ølsett-25-liter",
|
},
|
||||||
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024"
|
"date": "1648922400",
|
||||||
}, {
|
"by": ["Alf", "Kevin"],
|
||||||
"beer": {
|
"abv": "7",
|
||||||
"name": "Utepils",
|
"description": "",
|
||||||
"brewery": "Finest",
|
"image": "finest_fuck-yeah-IPA.jpg",
|
||||||
"category": "",
|
"recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ",
|
||||||
"description": ""
|
"order_page": "https://web.archive.org/web/20210225043236/https://www.bryggselv.no/finest/105943/fuck-yeah-ipa-ultra-american-west-coast-ipa-25-liter",
|
||||||
},
|
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024",
|
||||||
"date": "1637694000",
|
"color_primary": "#0169A6",
|
||||||
"by": ["Alf", "Kevin"],
|
"color_secondary": "rgba(1, 105, 166, 0.2)"
|
||||||
"abv": "5.0",
|
},
|
||||||
"description": "",
|
{
|
||||||
"image": "finest_utepils.jpeg",
|
"beer": {
|
||||||
"recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ",
|
"name": "Love in a canoe",
|
||||||
"order_page": "https://www.bryggselv.no/finest/105932/kinn-kveldsbris-allgrain-ølsett-25-liter",
|
"brewery": "Finest",
|
||||||
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024"
|
"category": "Mexican Lager",
|
||||||
}, {
|
"description": ""
|
||||||
"beer": {
|
},
|
||||||
"name": "HELLES Tysk Lager",
|
"date": "1646420400",
|
||||||
"brewery": "Münchener Helles",
|
"by": ["Alf", "Kevin"],
|
||||||
"category": "Tysk Lager",
|
"abv": "4.7",
|
||||||
"description": ""
|
"description": "",
|
||||||
},
|
"image": "finest_love-in-a-canoe.jpeg",
|
||||||
"date": "1629396000",
|
"recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ",
|
||||||
"by": ["Adrian", "Kevin", "Mats"],
|
"order_page": "https://oslo.bryggselv.no/finest/104092/love-in-a-canoe-allgrain-ølsett-25-liter",
|
||||||
"abv": "5.3",
|
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024",
|
||||||
"description": "",
|
"color_primary": "#F7F0DF",
|
||||||
"image": "helles_tysk-lager.jpeg",
|
"color_secondary": "rgba(247, 240, 223, 0.2)"
|
||||||
"recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ",
|
},
|
||||||
"order_page": "https://oslo.bryggselv.no/finest/106231/finest-helles-allgrain-ølsett-25-liter",
|
{
|
||||||
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024"
|
"beer": {
|
||||||
}, {
|
"name": "Utepils",
|
||||||
"beer": {
|
"brewery": "Finest",
|
||||||
"name": "Lazy Days Weiss",
|
"category": "Pilsner",
|
||||||
"brewery": "Finest",
|
"description": ""
|
||||||
"category": "Weissbier",
|
},
|
||||||
"description": ""
|
"date": "1637694000",
|
||||||
},
|
"by": ["Alf", "Kevin"],
|
||||||
"date": "1621706400",
|
"abv": "5.0",
|
||||||
"by": ["Alf", "Kevin", "Kristian"],
|
"description": "",
|
||||||
"abv": "5.3",
|
"image": "finest_utepils.jpeg",
|
||||||
"description": "",
|
"recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ",
|
||||||
"image": "finest_lazy-days.jpeg",
|
"order_page": "https://www.bryggselv.no/finest/105932/kinn-kveldsbris-allgrain-ølsett-25-liter",
|
||||||
"recipe": "https://docs.google.com/document/u/0/d/1I6qX4l4jDzK51GxBt3IdEv-HyNQHAx8ijc5dMlG1Xkk",
|
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024",
|
||||||
"order_page": "https://oslo.bryggselv.no/finest/106231/finest-helles-allgrain-ølsett-25-liter",
|
"color_primary": "#F4E9D3",
|
||||||
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024"
|
"color_secondary": "rgba(244, 233, 211, 0.2)"
|
||||||
}]
|
},
|
||||||
|
{
|
||||||
|
"beer": {
|
||||||
|
"name": "HELLES Tysk Lager",
|
||||||
|
"brewery": "Münchener Helles",
|
||||||
|
"category": "Tysk Lager",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"date": "1629396000",
|
||||||
|
"by": ["Adrian", "Kevin", "Mats"],
|
||||||
|
"abv": "5.3",
|
||||||
|
"description": "",
|
||||||
|
"image": "helles_tysk-lager.jpeg",
|
||||||
|
"recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ",
|
||||||
|
"order_page": "https://oslo.bryggselv.no/finest/106231/finest-helles-allgrain-ølsett-25-liter",
|
||||||
|
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024",
|
||||||
|
"color_primary": "#C5893D",
|
||||||
|
"color_secondary": "rgba(197, 137, 61, 0.2)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"beer": {
|
||||||
|
"name": "Lazy Days Weiss",
|
||||||
|
"brewery": "Finest",
|
||||||
|
"category": "Weissbier",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"date": "1621706400",
|
||||||
|
"by": ["Alf", "Kevin", "Kristian"],
|
||||||
|
"abv": "5.3",
|
||||||
|
"description": "",
|
||||||
|
"image": "finest_lazy-days.jpeg",
|
||||||
|
"recipe": "https://docs.google.com/document/u/0/d/1I6qX4l4jDzK51GxBt3IdEv-HyNQHAx8ijc5dMlG1Xkk",
|
||||||
|
"order_page": "https://oslo.bryggselv.no/finest/106231/finest-helles-allgrain-ølsett-25-liter",
|
||||||
|
"untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024",
|
||||||
|
"color_primary": "#6C8D9E",
|
||||||
|
"color_secondary": "rgba(108, 141, 158, 0.2)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import brews from '../../brews.json';
|
||||||
|
import ArrowRight from '../icons/ArrowRight.svelte';
|
||||||
|
|
||||||
interface IStep {
|
interface IStep {
|
||||||
icon: string;
|
icon: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -10,43 +14,66 @@
|
|||||||
interface IBrew {
|
interface IBrew {
|
||||||
name: string;
|
name: string;
|
||||||
by: string;
|
by: string;
|
||||||
|
date: string;
|
||||||
|
dates: {
|
||||||
|
brew: string;
|
||||||
|
ferment: string;
|
||||||
|
bottle: string;
|
||||||
|
consume: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const latestBrew = brews?.brews?.[0];
|
||||||
|
const { beer, dates } = latestBrew;
|
||||||
|
|
||||||
const brew: IBrew = {
|
const brew: IBrew = {
|
||||||
name: 'Kveldsbris',
|
name: beer?.name,
|
||||||
by: 'Kinn Bryggeri'
|
date: latestBrew.date,
|
||||||
|
dates: {
|
||||||
|
brew: dates?.brew || 'June 24',
|
||||||
|
ferment: 'July 12',
|
||||||
|
bottle: 'July 12',
|
||||||
|
consume: 'July 26'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let isOpen: number | null;
|
let isOpen: number | null;
|
||||||
|
|
||||||
|
onMount(() =>
|
||||||
|
setTimeout(() => document.querySelector('a.brew svg')?.classList.add('animate'), 1800)
|
||||||
|
);
|
||||||
|
|
||||||
const steps: Array<IStep> = [
|
const steps: Array<IStep> = [
|
||||||
{
|
{
|
||||||
icon: 'M9.94 8.007l3.983 3.983 1.06 1.06 1.06-1.06 6.93-6.93-2.12-2.12-6.93 6.93h2.12l-3.982-3.984-2.12 2.12z',
|
icon: 'M9.94 8.007l3.983 3.983 1.06 1.06 1.06-1.06 6.93-6.93-2.12-2.12-6.93 6.93h2.12l-3.982-3.984-2.12 2.12z',
|
||||||
name: 'Brew',
|
name: 'Brew',
|
||||||
date: 'March 19',
|
date: brew.dates.brew,
|
||||||
state: 'completed',
|
state: 'completed',
|
||||||
description: 'The brew stage involves the creation of the wort, which is the liquid extracted from malted grains (usually barley) through a process called mashing. The grains are crushed and mixed with hot water in a vessel called a mash tun, where enzymes break down the starches into fermentable sugars. The resulting liquid, called the wort, is then separated from the grain husks through a process called lautering. The wort is transferred to a boil kettle where hops are added to provide bitterness, flavor, and aroma. The wort is boiled to sterilize it and to further extract flavors from the hops. After boiling, the wort is cooled rapidly before being transferred to a fermentation vessel.'
|
description:
|
||||||
|
'The brew stage involves the creation of the wort, which is the liquid extracted from malted grains (usually barley) through a process called mashing. The grains are crushed and mixed with hot water in a vessel called a mash tun, where enzymes break down the starches into fermentable sugars. The resulting liquid, called the wort, is then separated from the grain husks through a process called lautering. The wort is transferred to a boil kettle where hops are added to provide bitterness, flavor, and aroma. The wort is boiled to sterilize it and to further extract flavors from the hops. After boiling, the wort is cooled rapidly before being transferred to a fermentation vessel.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'M19.803 5h1.572c.226 0 .443.244.603.404l1.772 1.85c.16.16.25.453.25.68v2.832c0 .015 0 .234-1 .234-.415-.854-1.116-1.287-2.124-1.287-.426 0-1.05.173-1.403.356-.14.072-.473.086-.473-.07V5.803c0-.442.36-.803.803-.803zM9.263 3h7.83c.5 0 .907.406.907.906v6.188c0 .5-.406.906-.906.906h-2.138c-.115 0-.214.206-.26.1-.397-.9-1.297-1.387-2.338-1.387-1.04 0-1.94.418-2.338 1.32-.046.104-.145-.033-.26-.033H8.672c-.37 0-.672-.3-.672-.672V4.265C8 3.57 8.57 3 9.264 3zm11.676 7.978c.828 0 1.5.67 1.5 1.5 0 .828-.672 1.5-1.5 1.5-.83 0-1.5-.672-1.5-1.5 0-.83.67-1.5 1.5-1.5zm-8.582-.07c.828 0 1.5.67 1.5 1.5 0 .828-.672 1.5-1.5 1.5s-1.5-.672-1.5-1.5c0-.83.672-1.5 1.5-1.5z',
|
icon: 'M19.803 5h1.572c.226 0 .443.244.603.404l1.772 1.85c.16.16.25.453.25.68v2.832c0 .015 0 .234-1 .234-.415-.854-1.116-1.287-2.124-1.287-.426 0-1.05.173-1.403.356-.14.072-.473.086-.473-.07V5.803c0-.442.36-.803.803-.803zM9.263 3h7.83c.5 0 .907.406.907.906v6.188c0 .5-.406.906-.906.906h-2.138c-.115 0-.214.206-.26.1-.397-.9-1.297-1.387-2.338-1.387-1.04 0-1.94.418-2.338 1.32-.046.104-.145-.033-.26-.033H8.672c-.37 0-.672-.3-.672-.672V4.265C8 3.57 8.57 3 9.264 3zm11.676 7.978c.828 0 1.5.67 1.5 1.5 0 .828-.672 1.5-1.5 1.5-.83 0-1.5-.672-1.5-1.5 0-.83.67-1.5 1.5-1.5zm-8.582-.07c.828 0 1.5.67 1.5 1.5 0 .828-.672 1.5-1.5 1.5s-1.5-.672-1.5-1.5c0-.83.672-1.5 1.5-1.5z',
|
||||||
name: 'Ferment',
|
name: 'Ferment',
|
||||||
date: 'March 23',
|
date: brew.dates.ferment,
|
||||||
state: 'completed',
|
state: 'completed',
|
||||||
description: `The fermented stage is where the magic happens. The cooled wort is transferred to a fermentation vessel, typically a large container called a fermenter. Yeast is added to the wort, and the fermentation process begins. Yeast consumes the fermentable sugars in the wort and converts them into alcohol and carbon dioxide through a process called fermentation. This stage can last from a few days to several weeks, depending on the beer style and desired characteristics. The temperature and conditions during fermentation play a crucial role in shaping the beer's flavors and aromas.`
|
description: `The fermented stage is where the magic happens. The cooled wort is transferred to a fermentation vessel, typically a large container called a fermenter. Yeast is added to the wort, and the fermentation process begins. Yeast consumes the fermentable sugars in the wort and converts them into alcohol and carbon dioxide through a process called fermentation. This stage can last from a few days to several weeks, depending on the beer style and desired characteristics. The temperature and conditions during fermentation play a crucial role in shaping the beer's flavors and aromas.`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'M23.45 10.99c-.162-.307-.54-.42-.84-.257l-4.582 2.45-6.557-11.93H8.62c-.347 0-.62.283-.62.628 0 .346.273.628.62.628h2.118l4.825 8.783c-.037-.006-.074-.006-.112-.006-1.37 0-2.482 1.123-2.482 2.508s1.11 2.508 2.483 2.508c1.038 0 1.92-.64 2.293-1.543l5.445-2.92c.304-.164.422-.54.26-.847zm-8 3.874c-.59 0-1.06-.476-1.06-1.072 0-.596.47-1.072 1.06-1.072.59 0 1.063.476 1.063 1.072 0 .596-.472 1.072-1.062 1.072zm8.994-6.698l-5.848 3.288-2.718-4.93 5.847-3.287 2.72 4.93zm-4.288-5.482l-4.882 2.744-1.48-2.683L18.675 0l1.48 2.684z',
|
icon: 'M23.45 10.99c-.162-.307-.54-.42-.84-.257l-4.582 2.45-6.557-11.93H8.62c-.347 0-.62.283-.62.628 0 .346.273.628.62.628h2.118l4.825 8.783c-.037-.006-.074-.006-.112-.006-1.37 0-2.482 1.123-2.482 2.508s1.11 2.508 2.483 2.508c1.038 0 1.92-.64 2.293-1.543l5.445-2.92c.304-.164.422-.54.26-.847zm-8 3.874c-.59 0-1.06-.476-1.06-1.072 0-.596.47-1.072 1.06-1.072.59 0 1.063.476 1.063 1.072 0 .596-.472 1.072-1.062 1.072zm8.994-6.698l-5.848 3.288-2.718-4.93 5.847-3.287 2.72 4.93zm-4.288-5.482l-4.882 2.744-1.48-2.683L18.675 0l1.48 2.684z',
|
||||||
name: 'Bottle',
|
name: 'Bottle',
|
||||||
date: 'April 1',
|
date: brew.dates.bottle,
|
||||||
state: 'in-progress',
|
state: 'completed',
|
||||||
description: 'Once fermentation is complete, the beer is ready for packaging. The beer is carefully transferred from the fermentation vessel to a bottling bucket or keg. During this transfer, care is taken to avoid disturbing the sediment, known as trub, that has settled at the bottom of the fermenter. If desired, additional priming sugar can be added at this stage to provide carbonation in the bottles. The beer is then filled into clean and sanitized bottles or kegs, ensuring that the containers are properly sealed to prevent oxygen exposure and contamination.'
|
description:
|
||||||
|
'Once fermentation is complete, the beer is ready for packaging. The beer is carefully transferred from the fermentation vessel to a bottling bucket or keg. During this transfer, care is taken to avoid disturbing the sediment, known as trub, that has settled at the bottom of the fermenter. If desired, additional priming sugar can be added at this stage to provide carbonation in the bottles. The beer is then filled into clean and sanitized bottles or kegs, ensuring that the containers are properly sealed to prevent oxygen exposure and contamination.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'M15.623 5.014l-4.29 3.577c-.196.168-.327.362-.327.62v6.206c0 .322.335.584.656.584h2.004c.32 0 .584-.262.584-.584l-.033-3.115c0-.16.13-.29.29-.29h2.918c.16 0 .292.13.292.29l.033 3.116c0 .322.263.584.584.584h2.09c.322 0 .585-.262.585-.584V9.48c0-.257-.172-.626-.37-.792l-4.263-3.674c-.218-.184-.536-.184-.754 0zm7.17 2.374l-5.967-5.046C16.606 2.122 16.312 2 16 2c-.312 0-.606.123-.79.31L9.207 7.388c-.245.208-.276.576-.068.822.115.136.28.206.446.206.133 0 .266-.044.376-.137l5.69-4.847c.208-.155.49-.157.697-.002 1.286.962 5.693 4.85 5.693 4.85.246.206.614.177.822-.07.208-.246.177-.614-.068-.822z',
|
icon: 'M15.623 5.014l-4.29 3.577c-.196.168-.327.362-.327.62v6.206c0 .322.335.584.656.584h2.004c.32 0 .584-.262.584-.584l-.033-3.115c0-.16.13-.29.29-.29h2.918c.16 0 .292.13.292.29l.033 3.116c0 .322.263.584.584.584h2.09c.322 0 .585-.262.585-.584V9.48c0-.257-.172-.626-.37-.792l-4.263-3.674c-.218-.184-.536-.184-.754 0zm7.17 2.374l-5.967-5.046C16.606 2.122 16.312 2 16 2c-.312 0-.606.123-.79.31L9.207 7.388c-.245.208-.276.576-.068.822.115.136.28.206.446.206.133 0 .266-.044.376-.137l5.69-4.847c.208-.155.49-.157.697-.002 1.286.962 5.693 4.85 5.693 4.85.246.206.614.177.822-.07.208-.246.177-.614-.068-.822z',
|
||||||
name: 'Carbonate',
|
name: 'Ready to drink',
|
||||||
date: 'April 14',
|
date: brew.dates.consume,
|
||||||
state: '',
|
state: 'completed',
|
||||||
description: 'After the beer is bottled or kegged, it enters the carbonation stage. If priming sugar was added during the bottling stage, the remaining yeast in the beer consumes the sugar and produces carbon dioxide, naturally carbonating the beer over time. The sealed bottles or kegs are stored at a controlled temperature for a period of time to allow carbonation to occur. This process typically takes a few weeks, during which the flavors and aromas continue to develop. If kegging, carbonation can also be achieved through forced carbonation, where carbon dioxide is directly injected into the keg under pressure.'
|
description:
|
||||||
|
'After the beer is bottled or kegged, it enters the carbonation stage. If priming sugar was added during the bottling stage, the remaining yeast in the beer consumes the sugar and produces carbon dioxide, naturally carbonating the beer over time. The sealed bottles or kegs are stored at a controlled temperature for a period of time to allow carbonation to occur. This process typically takes a few weeks, during which the flavors and aromas continue to develop. If kegging, carbonation can also be achieved through forced carbonation, where carbon dioxide is directly injected into the keg under pressure.'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
@@ -54,7 +81,18 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h1>Brew progress</h1>
|
<h1>Brew progress</h1>
|
||||||
|
|
||||||
<h2>{brew.name} <span class="company">av {brew.by}</span></h2>
|
<p>View progress of the latest brew, click for more info</p>
|
||||||
|
<a href="{`/brews/${brew.date}`}" class="brew-details">
|
||||||
|
<img src="/images/{latestBrew.image}" alt="Beer label of {beer.name}" />
|
||||||
|
<ul>
|
||||||
|
<li>Name: <span>{beer.name}</span></li>
|
||||||
|
<li>Brewery: <span>{beer.brewery}</span></li>
|
||||||
|
<li>Brewed by: <span>{latestBrew.by.join(', ')}</span></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<i class="arrow"><ArrowRight /></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
<ol class="os-timeline">
|
<ol class="os-timeline">
|
||||||
{#each steps as step, index}
|
{#each steps as step, index}
|
||||||
@@ -82,16 +120,98 @@
|
|||||||
|
|
||||||
{#each steps as step, index}
|
{#each steps as step, index}
|
||||||
<div class="{`description ${isOpen === index && 'isOpen'}`}">
|
<div class="{`description ${isOpen === index && 'isOpen'}`}">
|
||||||
<p>{ step.description }</p>
|
<p>{step.description}</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
<i class="info">{!isOpen ? 'Click icon for description' : 'Click icon to close'}</i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.company {
|
.brew-details {
|
||||||
font-size: 1.25rem;
|
position: relative;
|
||||||
opacity: 0.8;
|
display: flex;
|
||||||
display: inline-block;
|
border-radius: 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 30%;
|
||||||
|
max-width: 140px;
|
||||||
|
margin-right: 1rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 0.75rem);
|
||||||
|
right: 0;
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.brew {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1;
|
||||||
|
margin-left: 6px;
|
||||||
|
opacity: 0.8;
|
||||||
|
display: inline-block;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.brew-details:hover svg, .brew-details svg.animate) {
|
||||||
|
animation: bounce 2s infinite;
|
||||||
|
stroke: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%,
|
||||||
|
20%,
|
||||||
|
50%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
transform: translateX(-5px);
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
transform: translateX(-3px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
@@ -110,6 +230,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i.info {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: -1rem;
|
||||||
|
color: #737373;
|
||||||
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
PointElement,
|
PointElement,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Title,
|
Title,
|
||||||
Legend
|
Legend,
|
||||||
|
Filler
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import { getRelativePosition } from 'chart.js/helpers';
|
import { getRelativePosition } from 'chart.js/helpers';
|
||||||
|
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
PointElement,
|
PointElement,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Title,
|
Title,
|
||||||
Legend
|
Legend,
|
||||||
|
Filler
|
||||||
);
|
);
|
||||||
|
|
||||||
export let name: string;
|
export let name: string;
|
||||||
@@ -41,6 +43,8 @@
|
|||||||
|
|
||||||
// Converts Date to format suitable for the current range displayed
|
// Converts Date to format suitable for the current range displayed
|
||||||
function dateLabelsFormatedBasedOnResolution(dataFrames: IChartFrame[]): string[] {
|
function dateLabelsFormatedBasedOnResolution(dataFrames: IChartFrame[]): string[] {
|
||||||
|
if (dataFrames.length < 2) return ['NO DATA'];
|
||||||
|
|
||||||
const firstFrame = dataFrames[0];
|
const firstFrame = dataFrames[0];
|
||||||
const lastFrame = dataFrames[dataFrames.length - 1];
|
const lastFrame = dataFrames[dataFrames.length - 1];
|
||||||
const deltaSeconds =
|
const deltaSeconds =
|
||||||
@@ -69,42 +73,66 @@
|
|||||||
return dataFrames.map((frame) => scaledDate.format(frame.key));
|
return dataFrames.map((frame) => scaledDate.format(frame.key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hexToRgb(hex: string) {
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return result
|
||||||
|
? {
|
||||||
|
r: parseInt(result[1], 16),
|
||||||
|
g: parseInt(result[2], 16),
|
||||||
|
b: parseInt(result[3], 16)
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLineBackgroundGradient(hex: string, context: CanvasRenderingContext2D) {
|
||||||
|
const gradient = context.createLinearGradient(0, 0, 0, 400);
|
||||||
|
const c = hexToRgb(hex);
|
||||||
|
if (c == null) return;
|
||||||
|
|
||||||
|
gradient.addColorStop(0.2, `rgb(${c.r}, ${c.g}, ${c.b}, 0.8)`);
|
||||||
|
gradient.addColorStop(1, `rgb(${c.r}, ${c.g}, ${c.b}, 0.2)`);
|
||||||
|
return gradient;
|
||||||
|
}
|
||||||
|
|
||||||
// set dataset label & colors matching the name sent as prop
|
// set dataset label & colors matching the name sent as prop
|
||||||
function setDataColorAndName(data: ChartDataset) {
|
function setDataColorAndName(data: ChartDataset, context: CanvasRenderingContext2D) {
|
||||||
if (name === 'Pressure') {
|
if (name === 'Pressure') {
|
||||||
Object.assign(data, {
|
Object.assign(data, {
|
||||||
label: 'Bar of pressure',
|
label: 'Bar of pressure',
|
||||||
borderColor: '#ef5878',
|
borderColor: '#ef5878',
|
||||||
backgroundColor: '#fbd7de'
|
fill: true,
|
||||||
|
backgroundColor: createLineBackgroundGradient('#fbd7de', context)
|
||||||
});
|
});
|
||||||
} else if (name === 'Humidity') {
|
} else if (name === 'Humidity') {
|
||||||
Object.assign(data, {
|
Object.assign(data, {
|
||||||
label: '% humidity',
|
label: '% humidity',
|
||||||
borderColor: '#57d2fb',
|
borderColor: '#57d2fb',
|
||||||
backgroundColor: '#d4f2fe'
|
fill: true,
|
||||||
|
backgroundColor: createLineBackgroundGradient('#d4f2fe', context)
|
||||||
});
|
});
|
||||||
} else if (name === 'Temperature') {
|
} else if (name === 'Temperature') {
|
||||||
Object.assign(data, {
|
Object.assign(data, {
|
||||||
label: '℃ inside',
|
label: '℃ inside',
|
||||||
borderColor: '#10e783',
|
borderColor: '#10e783',
|
||||||
backgroundColor: '#c8f9df'
|
fill: true,
|
||||||
|
backgroundColor: createLineBackgroundGradient('#c8f9df', context)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderChart() {
|
function renderChart() {
|
||||||
const context: CanvasRenderingContext2D | null = chartCanvas.getContext('2d');
|
const context: CanvasRenderingContext2D | null = chartCanvas.getContext('2d');
|
||||||
if (!context) return
|
if (!context) return;
|
||||||
|
|
||||||
// create labels and singular dataset (data)
|
// create labels and singular dataset (data)
|
||||||
const labels: string[] = dateLabelsFormatedBasedOnResolution(dataFrames);
|
const labels = dateLabelsFormatedBasedOnResolution(dataFrames);
|
||||||
const data: ChartDataset = {
|
const data: ChartDataset = {
|
||||||
data: dataFrames.map((frame) => frame.value),
|
data: dataFrames.map((frame) => frame.value),
|
||||||
borderWidth: 3,
|
borderWidth: 3
|
||||||
};
|
};
|
||||||
// based on name, add label and color options to dataset
|
|
||||||
setDataColorAndName(data)
|
|
||||||
|
|
||||||
|
// based on name, add label and color options to dataset
|
||||||
|
setDataColorAndName(data, context);
|
||||||
|
|
||||||
// create chart instance, most here is chart options
|
// create chart instance, most here is chart options
|
||||||
chart = new Chart(context, {
|
chart = new Chart(context, {
|
||||||
@@ -114,9 +142,12 @@
|
|||||||
datasets: [data]
|
datasets: [data]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
animation: {
|
||||||
|
duration: 0
|
||||||
|
},
|
||||||
elements: {
|
elements: {
|
||||||
point: {
|
point: {
|
||||||
radius: 2
|
radius: 0
|
||||||
},
|
},
|
||||||
line: {
|
line: {
|
||||||
tension: 0.5
|
tension: 0.5
|
||||||
@@ -150,7 +181,7 @@
|
|||||||
speed: 0.001
|
speed: 0.001
|
||||||
},
|
},
|
||||||
// pinch: {
|
// pinch: {
|
||||||
// enabled: true
|
// enabled: true
|
||||||
// },
|
// },
|
||||||
mode: 'xy'
|
mode: 'xy'
|
||||||
}
|
}
|
||||||
@@ -172,7 +203,6 @@
|
|||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: false,
|
beginAtZero: false,
|
||||||
offset: true,
|
|
||||||
ticks: {
|
ticks: {
|
||||||
color: 'black'
|
color: 'black'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import Navigation from './Navigation.svelte';
|
import Navigation from './Navigation.svelte';
|
||||||
import GithubIcon from '../icons/Github.svelte';
|
import GithubIcon from '../icons/Github.svelte';
|
||||||
|
|
||||||
let open: boolean = false;
|
let open = false;
|
||||||
|
|
||||||
function toggleMenu() {
|
function toggleMenu() {
|
||||||
open = !open;
|
open = !open;
|
||||||
@@ -17,37 +17,35 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if open}
|
{#if open}
|
||||||
<div class="slideout-menu" transition:fly="{{ x: 550, duration: 300 }}">
|
<div class="slideout-menu" transition:fly="{{ x: 550, duration: 300 }}">
|
||||||
<h1>Navigation</h1>
|
<h1>Page navigation</h1>
|
||||||
|
|
||||||
<Navigation on:click="{close}" />
|
<Navigation on:click="{close}" />
|
||||||
|
|
||||||
<ul class="bottom-content">
|
<ul class="bottom-content">
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/kevinmidboe/brewpi">
|
<a class="link" href="https://github.com/kevinmidboe/brewpi">
|
||||||
<GithubIcon />
|
<GithubIcon />
|
||||||
<span class="meta">View on Github</span>
|
<span class="meta">View on Github</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div on:click="{toggleMenu}" class:open="{open}" aria-label="Open menu" class="menu">
|
<button on:click="{toggleMenu}" class:open="{open}" aria-label="Open menu" class="menu">
|
||||||
{#if !open}
|
{#if !open}
|
||||||
<span class="page-header-buttons__open">
|
<span class="page-header-buttons__open"> <span></span> <span></span> <span></span> </span>
|
||||||
<span></span> <span></span> <span></span>
|
|
||||||
</span>
|
|
||||||
{:else}
|
{:else}
|
||||||
<span class="page-header-buttons__close">
|
<span class="page-header-buttons__close">
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<span class="page-header-text">{headerText}</span>
|
<span class="page-header-text">{headerText}</span>
|
||||||
</div>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<style lang="scss" module="scoped">
|
<style lang="scss" module="scoped">
|
||||||
@@ -55,19 +53,23 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: calc(100vh + 4px);
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
max-width: 550px;
|
max-width: 550px;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: -2px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
border-left: 2px solid white;
|
||||||
|
border-top: 2px solid white;
|
||||||
|
|
||||||
background-color: #fff3f6;
|
background-color: var(--green);
|
||||||
color: black;
|
color: black;
|
||||||
padding: calc(100px + 2rem) 2rem 1rem;
|
padding: calc(100px + 2rem) 2rem 1rem;
|
||||||
border-top-left-radius: 4rem;
|
border-top-left-radius: 4rem;
|
||||||
|
color: var(--background);
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
font-size: 3rem;
|
||||||
padding-bottom: 4rem;
|
padding-bottom: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,12 +121,15 @@
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
-webkit-style: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&.open {
|
&.open {
|
||||||
background-color: salmon;
|
background-color: var(--background);
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
|
|
||||||
<div class="mobile-only">
|
<div class="mobile-only">
|
||||||
<svg
|
<svg
|
||||||
viewBox="545.506 652.94 1308.988 494.119"
|
viewBox="37.55 522.904 2324.9 754.192"
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -104,40 +104,48 @@
|
|||||||
preserveAspectRatio="xMidYMid meet"
|
preserveAspectRatio="xMidYMid meet"
|
||||||
color-interpolation-filters="sRGB"
|
color-interpolation-filters="sRGB"
|
||||||
transform="matrix(7.058824, 0, 0, 7.058824, 0, 17.647059)"
|
transform="matrix(7.058824, 0, 0, 7.058824, 0, 17.647059)"
|
||||||
style=""
|
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
fill="#f4400d"
|
fill="#f4400d"
|
||||||
class="icon-text-wrapper icon-svg-group iconsvg"
|
class="icon-text-wrapper icon-svg-group iconsvg"
|
||||||
transform="translate(77.27999877929688,89.99988174438477)"
|
transform="translate(5.319610595703125,71.57809829711914)"
|
||||||
>
|
>
|
||||||
<g class="iconsvg-imagesvg" transform="translate(0,0)">
|
<g class="iconsvg-imagesvg" transform="translate(0,0)">
|
||||||
<g>
|
<g>
|
||||||
<svg
|
<rect
|
||||||
filter="url(#colors7759109557)"
|
fill="#f4400d"
|
||||||
|
fill-opacity="0"
|
||||||
|
stroke-width="2"
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
width="60"
|
width="106.80000000000004"
|
||||||
height="70.00023333411112"
|
height="106.84380596044383"
|
||||||
filtersec="colorsf9850467616"
|
class="image-rect"></rect>
|
||||||
|
<svg
|
||||||
|
filter="url(#colors8304882580)"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="106.80000000000004"
|
||||||
|
height="106.84380596044383"
|
||||||
|
filtersec="colorsf6570116847"
|
||||||
class="image-svg-svg primary"
|
class="image-svg-svg primary"
|
||||||
style="overflow: visible;"
|
style="overflow: visible;"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
version="1.1"
|
viewBox="4.992629051208496 5 54.01459503173828 54.03697204589844"
|
||||||
x="0px"
|
x="0px"
|
||||||
y="0px"
|
y="0px"
|
||||||
viewBox="14.000142097473145 8 71.99971008300781 84"
|
|
||||||
enable-background="new 0 0 100 100"
|
|
||||||
>
|
>
|
||||||
<path
|
<g>
|
||||||
d="M16.723,52.83c1.909,0.178,2.384,0.161,3.399,0.153c0.682,9.052,4.603,16.156,4.789,16.487 c0.578,1.03,1.697,1.611,2.836,1.522c0.157-0.012,2.061-0.167,4.803-0.859c2.747,13.368,15.326,21.092,15.906,21.44 C48.932,91.858,49.465,92,50,92s1.068-0.142,1.544-0.428c0.58-0.348,13.159-8.072,15.906-21.44c2.741,0.693,4.646,0.848,4.803,0.859 c1.148,0.09,2.263-0.5,2.836-1.522c0.186-0.331,4.107-7.435,4.789-16.487c1.015,0.009,1.49,0.025,3.399-0.153 c1.636-0.151,2.845-1.589,2.713-3.227C84.451,30.394,70.504,15.676,53,14.152V11c0-1.657-1.343-3-3-3s-3,1.343-3,3v3.152 c-17.504,1.524-31.451,16.242-32.99,35.451C13.878,51.241,15.087,52.679,16.723,52.83z M29.265,64.716 c-1.077-2.426-2.801-7.033-3.179-12.309c1.473-0.276,4.102-0.964,6.17-1.783c0.508,3.546,2.048,7.807,4.579,11.62 C33.873,63.666,31.057,64.375,29.265,64.716z M49.994,85.366c-3.182-2.343-10.273-8.453-11.725-17.187 c0.78-0.34,1.572-0.72,2.372-1.146c3.806,4.069,7.498,6.35,7.815,6.54C48.932,73.858,49.465,74,50,74s1.068-0.142,1.544-0.428 c0.317-0.19,4.009-2.471,7.815-6.54c0.799,0.425,1.591,0.806,2.37,1.146C60.274,76.875,53.171,83.014,49.994,85.366z M49.994,67.366 c-6.35-4.679-11.666-11.733-11.96-19.655C42.277,45.046,46.895,40.6,50,35.549c3.106,5.053,7.736,9.505,11.966,12.161 C61.676,55.611,56.319,62.68,49.994,67.366z M70.735,64.716c-1.793-0.341-4.609-1.051-7.57-2.472 c2.503-3.772,4.058-7.986,4.579-11.62c2.068,0.819,4.696,1.507,6.17,1.783C73.537,57.683,71.812,62.289,70.735,64.716z M50,20 c14.518,0,27.018,11.629,29.625,26.996c-11.352-0.211-22.033-7.692-26.859-19.16C52.297,26.724,51.207,26,50,26 s-2.297,0.724-2.766,1.835C42.518,39.041,31.94,46.78,20.375,46.996C22.982,31.629,35.482,20,50,20z"
|
<path
|
||||||
></path>
|
d="M59,26.76l-.14-.56A27.53,27.53,0,0,0,32.69,5c-.46,0-.93,0-1.36,0A27.54,27.54,0,0,0,5.17,26.19L5,26.76a1.53,1.53,0,0,0,0,.3,1,1,0,0,0,.3.71,1,1,0,0,0,.7.29H6a23,23,0,0,0,2.78-.22,39.49,39.49,0,0,0,3.4,14.54l.9,2a1,1,0,0,0,.7.57L14,45a1,1,0,0,0,.67-.25l1-.91A24.24,24.24,0,0,0,16.93,48a22.7,22.7,0,0,0,3.29,5.59,1,1,0,0,0,.72.38H21a1,1,0,0,0,.71-.29l1.65-1.65.21-.23A18.81,18.81,0,0,0,26,55a19.18,19.18,0,0,0,5.09,3.72l.42.21a1,1,0,0,0,.9,0l.42-.21a18.86,18.86,0,0,0,7.56-6.85l.21.23,1.65,1.65A1,1,0,0,0,43,54h.06a1,1,0,0,0,.72-.38A22.7,22.7,0,0,0,47.07,48a24.24,24.24,0,0,0,1.25-4.19l1,.91A1,1,0,0,0,50,45l.21,0a1,1,0,0,0,.7-.57l.9-2a39.49,39.49,0,0,0,3.4-14.54,23.16,23.16,0,0,0,2.78.22.94.94,0,0,0,.71-.29,1,1,0,0,0,.3-.71V27A1,1,0,0,0,59,26.76ZM39.72,33.23l.15.29-.36,1.81a13.53,13.53,0,0,1-2.74,5.86,12.19,12.19,0,0,1-1,1l-.28.29A13.79,13.79,0,0,1,32,44.9a13.79,13.79,0,0,1-3.54-2.39l-.28-.29a13.37,13.37,0,0,1-3.69-6.88l-.36-1.82c.05-.1.1-.2.16-.3.26-.48.52-1,.77-1.45l.27-.53q.45-.93.87-1.89h0l.05,0,.23.21a12.72,12.72,0,0,0,1,.91,4.63,4.63,0,0,0,.42.32c.32.25.65.47,1,.69l.42.27a16.21,16.21,0,0,0,1.49.78l.78.34a1,1,0,0,0,.8,0l.79-.35a13.84,13.84,0,0,0,1.48-.77l.43-.28a11.11,11.11,0,0,0,1-.67c.15-.11.3-.22.44-.34.34-.27.66-.56,1-.86l.27-.25,0,0h0c.28.64.57,1.28.88,1.9.08.17.16.33.25.5C39.18,32.25,39.44,32.74,39.72,33.23ZM37.85,26.1c-.17.25-.33.5-.52.74a12.77,12.77,0,0,1-4.95,3.9l-.38.17-.37-.16a12.89,12.89,0,0,1-5-3.92c-.18-.23-.34-.48-.52-.75L26.08,26A12.69,12.69,0,0,1,24,19.39c4.3-4.36,6.75-8,8-12.06,1.24,4,3.69,7.7,8,12.06A12.84,12.84,0,0,1,37.85,26.1ZM25.72,7.78A26.06,26.06,0,0,1,30,7.08c-1.21,3.65-3.57,7.08-7.68,11.2l-3,2.65a20.25,20.25,0,0,1-6.19,3.78,20.87,20.87,0,0,1-3.56,1,21.19,21.19,0,0,1-2.28.3A25.46,25.46,0,0,1,25.72,7.78ZM14.36,42.34,14,41.56A37.65,37.65,0,0,1,10.77,27.5c.25-.05.5-.13.74-.19l.54-.15c.6-.17,1.19-.36,1.79-.59a22,22,0,0,0,6.8-4.14l1.51-1.34a14.74,14.74,0,0,0,2.28,6l.06.09.26.38-.09.21c-.32.8-.67,1.6-1.05,2.39l-.12.24c-.4.82-.83,1.63-1.3,2.43-.11.2-.23.4-.35.6a37.54,37.54,0,0,1-6.18,7.66c-.25.26-.52.51-.8.75Zm6.75,9.14a20.35,20.35,0,0,1-2.32-4.21,20.62,20.62,0,0,1-1.36-5.06c.48-.47,1-1,1.42-1.47l.11-.12c.49-.54,1-1.09,1.42-1.66.16-.19.31-.38.46-.58.34-.43.68-.88,1-1.33l.45-.64c.09-.14.19-.27.29-.41,0,.17.08.32.12.48s.07.31.12.46.13.43.2.64.09.28.14.42.17.44.26.66l.15.37c.12.26.25.51.38.77l.11.22c.17.32.36.64.55.95l.14.21c.16.23.32.47.49.7l.26.33.42.53.33.36c.11.12.22.25.34.37-.11.29-.23.56-.35.84s-.2.47-.31.69-.32.62-.48.92-.24.45-.37.67-.37.58-.57.87-.27.43-.42.64c-.27.37-.57.74-.87,1.1-.09.1-.16.21-.25.31a13.22,13.22,0,0,1-1,1.13ZM32,56.88a17.14,17.14,0,0,1-4.53-3.32A16.62,16.62,0,0,1,25,50.26c.2-.24.38-.5.57-.75s.19-.25.29-.38c.35-.49.68-1,1-1.52a3.9,3.9,0,0,0,.19-.35c.24-.41.47-.83.68-1.26l.26-.56c.08-.18.17-.35.25-.54l.11.08a14.93,14.93,0,0,0,1.34.9l.39.24c.51.29,1,.56,1.57.8a1,1,0,0,0,.8,0,15.44,15.44,0,0,0,1.57-.8l.4-.24c.44-.27.87-.56,1.28-.87l.16-.11c.08.19.17.36.25.54s.17.38.26.56c.21.42.43.83.66,1.24l.21.38c.31.51.64,1,1,1.5.1.14.21.27.31.4s.36.5.56.75A16.94,16.94,0,0,1,32,56.88Zm13.21-9.61a20.61,20.61,0,0,1-2.32,4.21l-.83-.84A13.61,13.61,0,0,1,41,49.5c-.4-.46-.77-.93-1.12-1.42-.12-.16-.22-.33-.33-.5s-.45-.65-.65-1-.23-.42-.35-.63-.35-.63-.5-1-.21-.46-.31-.69-.24-.55-.35-.83l.24-.26c0-.06.11-.11.16-.18s.34-.38.52-.59A14.33,14.33,0,0,0,39.38,41c.11-.17.19-.35.29-.53a11.3,11.3,0,0,0,.59-1.08c.11-.22.18-.45.28-.67s.29-.69.41-1,.14-.49.21-.73.19-.6.25-.91c.09.14.19.27.29.4s.3.44.46.66c.32.44.65.88,1,1.3l.48.61q.66.81,1.35,1.56l.18.22c.46.5.94,1,1.42,1.46A20.62,20.62,0,0,1,45.21,47.27ZM50,41.56l-.35.78-.5-.46c-.28-.24-.55-.49-.82-.76a37.58,37.58,0,0,1-6.16-7.66l-.37-.61c-.45-.79-.88-1.59-1.28-2.41l-.13-.25c-.37-.78-.72-1.58-1-2.38a1.8,1.8,0,0,1-.08-.21l.25-.39a14.69,14.69,0,0,0,2.34-6.12l1.5,1.34c.37.32.75.63,1.13.93.13.11.27.2.4.3l.78.56.5.32.73.45.56.31.72.39.6.28c.24.11.47.22.72.32l.62.25.73.27c.22.08.43.15.65.21l.75.23.65.16.34.09A37.65,37.65,0,0,1,50,41.56Zm4.44-15.85a20.16,20.16,0,0,1-9.75-4.78l-3-2.65c-4.11-4.12-6.47-7.55-7.68-11.2a26.72,26.72,0,0,1,4.28.7A25.47,25.47,0,0,1,56.72,26,21.38,21.38,0,0,1,54.43,25.71Z"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</svg>
|
</svg>
|
||||||
<defs>
|
<defs>
|
||||||
<filter id="colors7759109557">
|
<filter id="colors8304882580">
|
||||||
<feColorMatrix
|
<feColorMatrix
|
||||||
type="matrix"
|
type="matrix"
|
||||||
values="0 0 0 0 0.953125 0 0 0 0 0.25 0 0 0 0 0.05078125 0 0 0 1 0"
|
values="0 0 0 0 0.953125 0 0 0 0 0.25 0 0 0 0 0.05078125 0 0 0 1 0"
|
||||||
@@ -146,9 +154,9 @@
|
|||||||
</defs>
|
</defs>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g transform="translate(67,5.938072204589844)">
|
<g transform="translate(113.80000305175781,0.5289802551269531)">
|
||||||
<g fill-rule="" class="tp-name iconsvg-namesvg">
|
<g fill-rule="" class="tp-name iconsvg-namesvg">
|
||||||
<g transform="scale(1)">
|
<g transform="scale(1.8200000000000005)">
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
d="M7.5 0.31C11.37 0.31 14.73-1.93 14.73-5.44 14.73-11.69 4.55-11.53 4.55-14.44 4.55-15.67 6.09-16.42 7.5-16.42 9.16-16.42 10.57-15.43 10.57-14.24L14.39-14.24C14.39-18.09 10.85-19.73 7.5-19.73 3.57-19.73 0.75-17.57 0.75-14.55 0.75-8.15 10.93-8.9 10.93-5.44 10.93-4.11 9.73-3.25 7.76-3.25 5.47-3.25 4.37-4.84 4.37-6.3L0.52-6.3C0.52-2.42 3.57 0.31 7.5 0.31ZM16.27-9.76C16.27-4.35 20.48 0.29 25.71 0.29 28 0.29 30.11-0.57 31.75-1.98L29.56-4.87C28.5-4.03 27.25-3.46 25.63-3.46 22.2-3.46 20.14-6.61 20.14-9.76 20.14-12.91 22.2-16.06 25.63-16.06 27.25-16.06 28.52-15.51 29.56-14.65L31.75-17.54C30.11-18.95 28-19.8 25.71-19.8 20.48-19.8 16.27-15.17 16.27-9.76ZM33.88-19.52L33.88 0 37.45 0 37.45-5.75 45.02-5.75 45.02 0 48.59 0 48.59-19.52 45.02-19.52 45.02-9.34 37.45-9.34 37.45-19.52ZM59.21-5.83L68.34-5.83 68.34-9.11 62.46-9.11 62.46-10.93 66.52-10.93 67.07-14.16 62.46-14.16 62.46-16.29 68.34-16.29 68.34-19.57 59.21-19.57ZM51.97 0C57.51-1.54 60.59 0 63.66 0 66.02 0 68.34-0.31 70.08-1.33L68.91-4.48C67.77-3.83 66.31-3.28 63.66-3.28 60.66-3.28 59.96-4.22 55.54-3.7L55.54-19.52 51.97-19.52ZM80.21-19.57L71.65-19.57 71.65 0 75.21 0 75.21-6.53 79.66-6.53C83.88-6.53 86.95-9.19 86.95-13.04 86.95-16.89 84.11-19.57 80.21-19.57ZM83.25-13.04C83.25-10.67 80.96-10.1 79.17-10.1L75.21-10.1 75.21-16.01 79.17-16.01C80.96-16.01 83.25-15.41 83.25-13.04ZM97.98-19.57L89.42-19.57 89.42 0 92.99 0 92.99-6.53 97.44-6.53C101.65-6.53 104.72-9.19 104.72-13.04 104.72-16.89 101.89-19.57 97.98-19.57ZM101.03-13.04C101.03-10.67 98.74-10.1 96.94-10.1L92.99-10.1 92.99-16.01 96.94-16.01C98.74-16.01 101.03-15.41 101.03-13.04ZM118.96-15.95L118.96-19.52 107.07-19.52 107.07 0 118.96 0 118.96-3.57 110.63-3.57 110.63-7.34 116.38-7.34 116.9-10.88 110.63-10.88 110.63-15.95Z"
|
d="M7.5 0.31C11.37 0.31 14.73-1.93 14.73-5.44 14.73-11.69 4.55-11.53 4.55-14.44 4.55-15.67 6.09-16.42 7.5-16.42 9.16-16.42 10.57-15.43 10.57-14.24L14.39-14.24C14.39-18.09 10.85-19.73 7.5-19.73 3.57-19.73 0.75-17.57 0.75-14.55 0.75-8.15 10.93-8.9 10.93-5.44 10.93-4.11 9.73-3.25 7.76-3.25 5.47-3.25 4.37-4.84 4.37-6.3L0.52-6.3C0.52-2.42 3.57 0.31 7.5 0.31ZM16.27-9.76C16.27-4.35 20.48 0.29 25.71 0.29 28 0.29 30.11-0.57 31.75-1.98L29.56-4.87C28.5-4.03 27.25-3.46 25.63-3.46 22.2-3.46 20.14-6.61 20.14-9.76 20.14-12.91 22.2-16.06 25.63-16.06 27.25-16.06 28.52-15.51 29.56-14.65L31.75-17.54C30.11-18.95 28-19.8 25.71-19.8 20.48-19.8 16.27-15.17 16.27-9.76ZM33.88-19.52L33.88 0 37.45 0 37.45-5.75 45.02-5.75 45.02 0 48.59 0 48.59-19.52 45.02-19.52 45.02-9.34 37.45-9.34 37.45-19.52ZM59.21-5.83L68.34-5.83 68.34-9.11 62.46-9.11 62.46-10.93 66.52-10.93 67.07-14.16 62.46-14.16 62.46-16.29 68.34-16.29 68.34-19.57 59.21-19.57ZM51.97 0C57.51-1.54 60.59 0 63.66 0 66.02 0 68.34-0.31 70.08-1.33L68.91-4.48C67.77-3.83 66.31-3.28 63.66-3.28 60.66-3.28 59.96-4.22 55.54-3.7L55.54-19.52 51.97-19.52ZM80.21-19.57L71.65-19.57 71.65 0 75.21 0 75.21-6.53 79.66-6.53C83.88-6.53 86.95-9.19 86.95-13.04 86.95-16.89 84.11-19.57 80.21-19.57ZM83.25-13.04C83.25-10.67 80.96-10.1 79.17-10.1L75.21-10.1 75.21-16.01 79.17-16.01C80.96-16.01 83.25-15.41 83.25-13.04ZM97.98-19.57L89.42-19.57 89.42 0 92.99 0 92.99-6.53 97.44-6.53C101.65-6.53 104.72-9.19 104.72-13.04 104.72-16.89 101.89-19.57 97.98-19.57ZM101.03-13.04C101.03-10.67 98.74-10.1 96.94-10.1L92.99-10.1 92.99-16.01 96.94-16.01C98.74-16.01 101.03-15.41 101.03-13.04ZM118.96-15.95L118.96-19.52 107.07-19.52 107.07 0 118.96 0 118.96-3.57 110.63-3.57 110.63-7.34 116.38-7.34 116.9-10.88 110.63-10.88 110.63-15.95Z"
|
||||||
@@ -178,11 +186,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:global(.mobile-only > svg) {
|
:global(.mobile-only > svg) {
|
||||||
max-width: 340px;
|
max-width: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-only {
|
.desktop-only {
|
||||||
display: grid;
|
display: grid;
|
||||||
max-height: 80px;
|
max-height: 80px;
|
||||||
|
margin-bottom: 4rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ const routes: Array<IRoute> = [{
|
|||||||
<a href={route.path}>
|
<a href={route.path}>
|
||||||
<li>
|
<li>
|
||||||
<span>{ route.name }</span>
|
<span>{ route.name }</span>
|
||||||
|
|
||||||
|
<i class="arrow">
|
||||||
<ArrowRight />
|
<ArrowRight />
|
||||||
|
</i>
|
||||||
</li>
|
</li>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -33,7 +36,8 @@ const routes: Array<IRoute> = [{
|
|||||||
.navigation-cards a {
|
.navigation-cards a {
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
background: var(--green);
|
color: var(--text-color);
|
||||||
|
background: var(--background);
|
||||||
transition: background-color var(--color-transition-duration) ease-in-out, transform 0.2s ease;
|
transition: background-color var(--color-transition-duration) ease-in-out, transform 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -50,8 +54,12 @@ const routes: Array<IRoute> = [{
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 1rem 1.75rem;
|
padding: 1rem 1.75rem;
|
||||||
color: white;
|
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
|
|
||||||
|
i {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,37 +1,39 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// import { toggleRelay } from '$lib/server/relayToggle'
|
// import { toggleRelay } from '$lib/server/relayToggle'
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
import Switch from './Switch.svelte';
|
import Switch from './Switch.svelte';
|
||||||
|
import type { IRelaysDTO } from '../interfaces/IRelaysDTO';
|
||||||
|
|
||||||
export let relays = [];
|
export let relays: IRelaysDTO[] = [];
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
function toggleRelay(location) {
|
function toggleRelay(controls: string) {
|
||||||
const url = `/api/relay/${location}`;
|
const url = `/api/relay/${controls}`;
|
||||||
const options = {
|
const options = {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch(url, options)
|
fetch(url, options)
|
||||||
.then((resp) => resp.json())
|
.then((resp) => resp.json())
|
||||||
.then(console.log);
|
.then((response) => {
|
||||||
}
|
const changedRelay = relays.findIndex((relay) => relay.pin === response.pin);
|
||||||
|
relays[changedRelay] = response;
|
||||||
|
});
|
||||||
|
|
||||||
function handleChange(event) {
|
dispatch('relaySwitched');
|
||||||
let isChecked = event.detail.checked;
|
|
||||||
// Perform any desired actions based on the new value
|
|
||||||
console.log('New value:', isChecked);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h1>Manual relay controls</h1>
|
<h1>Manual fridge controls</h1>
|
||||||
|
|
||||||
<div class="vertical-sensor-display">
|
<div class="vertical-sensor-display">
|
||||||
{#each relays as relay}
|
{#each relays as relay}
|
||||||
<div>
|
<div>
|
||||||
<h2>{relay.location} relay</h2>
|
<h2>{relay.controls} relay</h2>
|
||||||
|
|
||||||
<div class="sensor-reading">
|
<div class="sensor-reading">
|
||||||
<Switch checked="{relay.state}" on:change="{() => toggleRelay(relay.location)}" />
|
<Switch checked="{relay.state}" on:change="{() => toggleRelay(relay.controls)}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -1,22 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte';
|
||||||
import CardButton from "./CardButton.svelte";
|
import CardButton from './CardButton.svelte';
|
||||||
import Activity from "../icons/Activity.svelte";
|
import Activity from '../icons/Activity.svelte';
|
||||||
|
import { ISensorDTO } from '../interfaces/ISensorDTO';
|
||||||
|
import { IRelaysDTO } from '../interfaces/IRelaysDTO';
|
||||||
|
import { IStateDTO } from '../interfaces/IStateDTO';
|
||||||
|
|
||||||
export let inside;
|
export let inside: ISensorDTO;
|
||||||
export let outside;
|
export let outside: ISensorDTO;
|
||||||
|
export let relays: IRelaysDTO[] = [];
|
||||||
|
export let state: IStateDTO;
|
||||||
|
|
||||||
let loadedTime: number = new Date().getTime();
|
let loadedTime: number = new Date().getTime();
|
||||||
let currentTime: number = new Date().getTime();
|
let currentTime: number = new Date().getTime();
|
||||||
let autoReload = false;
|
let autoReload = false;
|
||||||
const currentGoal = 4;
|
|
||||||
|
|
||||||
function updateTime() {
|
function updateTime() {
|
||||||
currentTime = new Date().getTime();
|
currentTime = new Date().getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
function flipCard(): void {
|
function flipCard(): void {
|
||||||
console.log("flip-a-delphia")
|
console.log('flip-a-delphia');
|
||||||
|
}
|
||||||
|
|
||||||
|
function tempToStateClass(temp: number | undefined) {
|
||||||
|
if (temp === undefined || !!isNaN(temp)) return 'idle';
|
||||||
|
return Number(temp) > 14 ? 'heating' : 'cooling';
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => setInterval(updateTime, 1000));
|
onMount(() => setInterval(updateTime, 1000));
|
||||||
@@ -24,41 +33,47 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
<h1>Fridge sensors</h1>
|
||||||
<CardButton>
|
<CardButton>
|
||||||
<Activity on:click={flipCard} />
|
<Activity on:click="{flipCard}" />
|
||||||
</CardButton>
|
</CardButton>
|
||||||
|
|
||||||
<h2>Current target temperature</h2>
|
<h2>Current target temperature</h2>
|
||||||
<div class="sensor-reading">
|
<div class="sensor-reading">
|
||||||
<div class="blue">
|
<div class="{state?.state}">
|
||||||
<span class="value">{currentGoal}</span>
|
<span class="value">{state?.goal || 0}</span>
|
||||||
<span class="unit">°C</span>
|
<span class="unit">°C</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Inside temperature</h2>
|
|
||||||
<div class="sensor-reading">
|
|
||||||
<div class="blue">
|
|
||||||
<span class="value">{inside?.temperature}</span>
|
|
||||||
<span class="unit">{inside?.temperature_unit}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span class="value">{Math.floor(inside?.humidity)}</span>
|
<span class="value"></span>
|
||||||
|
<span class="unit">{state?.state || 'unknown'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Inside frigde temperature</h2>
|
||||||
|
<div class="sensor-reading">
|
||||||
|
<div class="{tempToStateClass(inside?.temperature)}">
|
||||||
|
<span class="value">{inside?.temperature || 0}</span>
|
||||||
|
<span class="unit">{inside?.temperature_unit || '°C'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="value">{Math.floor(inside?.humidity || 0)}</span>
|
||||||
<span class="unit">{inside?.humidity_unit || '%'}</span>
|
<span class="unit">{inside?.humidity_unit || '%'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Outside temperature</h2>
|
<h2>Outside temperature</h2>
|
||||||
<div class="sensor-reading">
|
<div class="sensor-reading">
|
||||||
<div class="red">
|
<div class="{tempToStateClass(outside?.temperature)}">
|
||||||
<span class="value">{outside?.temperature}</span>
|
<span class="value">{outside?.temperature || 0}</span>
|
||||||
<span class="unit">{outside?.temperature_unit}</span>
|
<span class="unit">{outside?.temperature_unit || '°C'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span class="value">{Math.floor(outside?.humidity)}</span>
|
<span class="value">{Math.floor(outside?.humidity || 0)}</span>
|
||||||
<span class="unit">{outside?.humidity_unit}</span>
|
<span class="unit">{outside?.humidity_unit || '%'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -70,14 +85,16 @@
|
|||||||
|
|
||||||
<div class="button-timer">
|
<div class="button-timer">
|
||||||
<span>Updated {secondsSinceUpdate === 0 ? 'now' : secondsSinceUpdate + 's ago'}</span>
|
<span>Updated {secondsSinceUpdate === 0 ? 'now' : secondsSinceUpdate + 's ago'}</span>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss" module="scoped">
|
<style lang="scss" module="scoped">
|
||||||
@import '../../styles/media-queries.scss';
|
@import '../../styles/media-queries.scss';
|
||||||
|
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
@@ -92,6 +109,7 @@
|
|||||||
.sensor-reading {
|
.sensor-reading {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
margin-bottom: 1.75rem;
|
margin-bottom: 1.75rem;
|
||||||
|
|
||||||
font-size: 2.2rem;
|
font-size: 2.2rem;
|
||||||
@@ -117,13 +135,24 @@
|
|||||||
margin-left: 0.3rem;
|
margin-left: 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.red {
|
.info {
|
||||||
|
font-size: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heating {
|
||||||
color: var(--red);
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blue {
|
.cooling {
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.idle {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-timer {
|
.button-timer {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<svg on:click xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
|
<svg on:click xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 323 B After Width: | Height: | Size: 328 B |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" fill="white"/></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 814 B After Width: | Height: | Size: 828 B |
17
src/lib/interfaces/IRelaysDTO.ts
Normal file
17
src/lib/interfaces/IRelaysDTO.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
enum Controls {
|
||||||
|
heating = 'heating',
|
||||||
|
cooling = 'cooling',
|
||||||
|
fan = 'fan',
|
||||||
|
lights = 'lights'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRelaysDTO {
|
||||||
|
controls: Controls;
|
||||||
|
pin: number;
|
||||||
|
state: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
IRelaysDTO,
|
||||||
|
Controls
|
||||||
|
}
|
||||||
19
src/lib/interfaces/ISensorDTO.ts
Normal file
19
src/lib/interfaces/ISensorDTO.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
enum Location {
|
||||||
|
inside = 'inside',
|
||||||
|
outside = 'outside'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISensorDTO {
|
||||||
|
location: Location;
|
||||||
|
temperature?: number
|
||||||
|
temperature_unit?: string
|
||||||
|
humidity?: number
|
||||||
|
humidity_unit?: string
|
||||||
|
pressure?: number
|
||||||
|
pressure_unit?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ISensorDTO,
|
||||||
|
Location
|
||||||
|
}
|
||||||
15
src/lib/interfaces/IStateDTO.ts
Normal file
15
src/lib/interfaces/IStateDTO.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
enum State {
|
||||||
|
idle = 'idle',
|
||||||
|
cooling = 'cooling',
|
||||||
|
heating = 'heating'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IStateDTO {
|
||||||
|
goal: number;
|
||||||
|
state: State
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
IStateDTO,
|
||||||
|
State
|
||||||
|
}
|
||||||
@@ -4,24 +4,31 @@
|
|||||||
import '../styles/global.css';
|
import '../styles/global.css';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<HeaderComponent />
|
<div class="app">
|
||||||
<!-- <Darkmode/> -->
|
<HeaderComponent />
|
||||||
|
<!-- <Darkmode/> -->
|
||||||
|
|
||||||
<div class="page-content">
|
<main>
|
||||||
<slot />
|
<slot />
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../styles/media-queries.scss';
|
@import '../styles/media-queries.scss';
|
||||||
|
|
||||||
.page-content {
|
.app {
|
||||||
|
min-height: calc(100vh - var(--header-height));
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: calc(100vh - var(--header-height));
|
min-height: 95vh;
|
||||||
margin: var(--header-height) 2.5rem 0;
|
margin: var(--header-height) 2.5rem 0;
|
||||||
|
|
||||||
@include mobile {
|
@include mobile {
|
||||||
margin: var(--header-height) 1rem 0;
|
margin: var(--header-height) 1rem 0;
|
||||||
|
min-height: 85vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
import { BREWLOGGER_HOST } from '$env/static/private';
|
import { BREWLOGGER_HOST } from '$env/static/private';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
import type { ISensorDTO } from '../lib/interfaces/ISensorDTO'
|
||||||
|
import { IRelaysDTO } from '../lib/interfaces/IRelaysDTO';
|
||||||
|
import { IStateDTO } from '../lib/interfaces/IStateDTO';
|
||||||
|
|
||||||
const sensorsUrl = `${BREWLOGGER_HOST}/api/sensors`;
|
const sensorsUrl = `${BREWLOGGER_HOST}/api/sensors`;
|
||||||
const relaysUrl = `${BREWLOGGER_HOST}/api/relays`;
|
const relaysUrl = `${BREWLOGGER_HOST}/api/relays`;
|
||||||
|
const stateUrl = `${BREWLOGGER_HOST}/api/regulator`;
|
||||||
|
|
||||||
async function getSensors() {
|
async function getFromEndpoint(endpoint: string) {
|
||||||
return fetch(sensorsUrl)
|
return fetch(endpoint)
|
||||||
.then((resp) => resp.json())
|
.then((resp) => resp.json())
|
||||||
.then((response) => {
|
.catch((error) => {
|
||||||
return response?.sensors;
|
console.error('Failed to fetch endpoint:', endpoint);
|
||||||
});
|
console.error(error);
|
||||||
}
|
return null
|
||||||
|
|
||||||
async function getRelays() {
|
|
||||||
return fetch(relaysUrl)
|
|
||||||
.then((resp) => resp.json())
|
|
||||||
.then((response) => {
|
|
||||||
return response?.relays || [];
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const load: PageServerLoad = async () => {
|
export const load: PageServerLoad = async () => {
|
||||||
const [sensors, relays] = await Promise.all([getSensors(), getRelays()]);
|
const [sensorsResp, relaysResp, stateResp] = await Promise.all([getFromEndpoint(sensorsUrl), getFromEndpoint(relaysUrl), getFromEndpoint(stateUrl)]);
|
||||||
|
const sensors: ISensorDTO[] = sensorsResp?.sensors || []
|
||||||
|
const relays: IRelaysDTO[] = relaysResp?.relays || []
|
||||||
|
const state: IStateDTO = stateResp
|
||||||
|
|
||||||
const inside = sensors.find((sensor) => sensor.location === 'inside');
|
const inside = sensors.find((sensor: ISensorDTO) => sensor.location === 'inside');
|
||||||
const outside = sensors.find((sensor) => sensor.location === 'outside');
|
const outside = sensors.find((sensor: ISensorDTO) => sensor.location === 'outside');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inside: inside || null,
|
inside: inside || null,
|
||||||
outside: outside || null,
|
outside: outside || null,
|
||||||
relays
|
relays,
|
||||||
|
state
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Logo from '../lib/components/Logo.svelte';
|
import Logo from '../lib/components/Logo.svelte';
|
||||||
import VerticalSensorDisplay from '../lib/components/VerticalSensorDisplay.svelte';
|
import VerticalSensorDisplay from '../lib/components/VerticalSensorDisplay.svelte';
|
||||||
// import Livestream from '$lib/components/Livestream.svelte'
|
|
||||||
import BrewProgress from '../lib/components/BrewProgress.svelte';
|
import BrewProgress from '../lib/components/BrewProgress.svelte';
|
||||||
import type { PageData } from './$types';
|
|
||||||
import RelayControls from '../lib/components/RelayControls.svelte';
|
import RelayControls from '../lib/components/RelayControls.svelte';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
import type { IStateDTO } from '../lib/interfaces/IStateDTO';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
const { inside, outside, relays } = data;
|
const { inside, outside } = data;
|
||||||
|
let { relays, state } = data;
|
||||||
|
|
||||||
|
const updateState = () =>
|
||||||
|
setTimeout(() => {
|
||||||
|
fetch('/api/state')
|
||||||
|
.then((resp) => resp.json())
|
||||||
|
.then((response: IStateDTO) => (state = response));
|
||||||
|
}, 100);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Logo />
|
<Logo />
|
||||||
@@ -15,11 +23,9 @@
|
|||||||
<div class="vertical-grid">
|
<div class="vertical-grid">
|
||||||
<BrewProgress />
|
<BrewProgress />
|
||||||
|
|
||||||
<VerticalSensorDisplay inside="{inside}" outside="{outside}" />
|
<VerticalSensorDisplay {inside} {outside} {relays} {state} />
|
||||||
|
|
||||||
<RelayControls relays="{relays}" />
|
<RelayControls bind:relays="{relays}" on:relaySwitched="{updateState}" />
|
||||||
|
|
||||||
<!-- <Livestream /> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -36,5 +42,15 @@
|
|||||||
grid-template-columns: 2fr 2fr 3fr;
|
grid-template-columns: 2fr 2fr 3fr;
|
||||||
margin: 2rem;
|
margin: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 900px) and (max-width: 1550px) {
|
||||||
|
grid-template-columns: 2fr 2fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.vertical-grid > div:nth-child(3)) {
|
||||||
|
@media (min-width: 900px) and (max-width: 1550px) {
|
||||||
|
margin-top: -185px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
9
src/routes/api/state/+server.ts
Normal file
9
src/routes/api/state/+server.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { json, RequestEvent } from '@sveltejs/kit';
|
||||||
|
import { BREWLOGGER_HOST } from '$env/static/private';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
|
export const GET = (async (event: RequestEvent) => {
|
||||||
|
return fetch(BREWLOGGER_HOST + '/api/regulator')
|
||||||
|
.then((resp) => resp.json())
|
||||||
|
.then((response) => json(response));
|
||||||
|
}) satisfies RequestHandler;
|
||||||
@@ -2,5 +2,5 @@ import brews from '../../brews.json';
|
|||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
export const load: PageServerLoad = async () => {
|
export const load: PageServerLoad = async () => {
|
||||||
return { brews };
|
return { brews: brews.brews };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import ArrowRight from '../../lib/icons/ArrowRight.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
const brews = data?.brews || [];
|
const brews = data?.brews || [];
|
||||||
const path = (date: string) => '/brews/' + String(date);
|
const path = (date: string) => '/brews/' + String(date);
|
||||||
|
|
||||||
const dateFormat = { year: 'numeric', month: 'short', day: 'numeric' };
|
const dateFormat = { year: 'numeric', month: 'short', day: 'numeric' };
|
||||||
const dateString = (date: number) => new Date(date * 1000).toLocaleDateString('no-NB', dateFormat);
|
const dateString = (date: number) =>
|
||||||
|
new Date(date * 1000).toLocaleDateString('no-NB', dateFormat);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="card">
|
<main class="card">
|
||||||
@@ -14,26 +16,130 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each brews as brew}
|
{#each brews as brew}
|
||||||
<li><a href="{path(brew.date)}">{ brew.beer.name } av { brew.beer.brewery }</a> - {dateString(brew.date)}</li>
|
<li class="brew">
|
||||||
|
<a href="{path(brew.date)}">
|
||||||
|
<img src="/images/{brew.image}" alt="Beer label of {brew.beer.name}" />
|
||||||
|
|
||||||
|
<div class="details">
|
||||||
|
<h2>
|
||||||
|
{brew.beer.name}
|
||||||
|
<span>{brew.beer.category}</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>By: {brew.by.join(', ')}</p>
|
||||||
|
<p>Date: {dateString(brew.date)}</p>
|
||||||
|
<p>Recipe by: {brew.beer.brewery}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-arrow"><ArrowRight /></div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.card {
|
@import '../../styles/media-queries.scss';
|
||||||
height: calc(100vh - var(--header-height) * 2);
|
|
||||||
|
|
||||||
ul {
|
main.card {
|
||||||
margin-left: 1.2em;
|
min-height: calc(100vh - var(--header-height) * 2);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
font-size: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul li {
|
ul {
|
||||||
list-style-type: disc;
|
margin: 0;
|
||||||
line-height: 1.5;
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
.brew {
|
||||||
font-size: 1.2rem;
|
list-style-type: none;
|
||||||
color: #19A786;
|
line-height: 1.5;
|
||||||
|
padding: 1.5rem;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: inherit;
|
||||||
|
|
||||||
|
&:not(:last-of-type) {
|
||||||
|
border-bottom: 1px solid #f5f5f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
// transform: scale(1.01);
|
||||||
|
scale: 1.01;
|
||||||
|
will-change: transform;
|
||||||
|
box-shadow: 0px 2px 15px -3px rgba(25, 167, 134, 0.2);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #19a786;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 160px 1fr 30px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
color: var(--green);
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 160px;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.details {
|
||||||
|
margin-left: 2rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.nav-arrow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include mobile {
|
||||||
|
.brew {
|
||||||
|
padding-left: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brew a {
|
||||||
|
grid-template-columns: 80px 1fr;
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
background-color: pink;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.details {
|
||||||
|
margin-left: 1rem;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.nav-arrow {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ async function fetchGraphData(brew) {
|
|||||||
|
|
||||||
export const load = (async ({ params }) => {
|
export const load = (async ({ params }) => {
|
||||||
const { date } = params;
|
const { date } = params;
|
||||||
const brew = brews.find((b) => b?.date === date);
|
const brew = brews.brews.find((b) => b?.date === date);
|
||||||
|
|
||||||
if (!brew) {
|
if (!brew) {
|
||||||
throw error(404, 'Brew not found');
|
throw error(404, 'Brew not found');
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Graph from '../../../lib/components/Graph.svelte';
|
import Graph from '../../../lib/components/Graph.svelte';
|
||||||
|
import ArrowRight from '../../../lib/icons/ArrowRight.svelte';
|
||||||
import IChartFrame from '../../../lib/interfaces/IChartFrame';
|
import IChartFrame from '../../../lib/interfaces/IChartFrame';
|
||||||
let height: number;
|
let height: number;
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
let brew = data.brew;
|
let brew = data.brew;
|
||||||
let temperatureData: IChartFrame[] = data.graphData.temperature;
|
let temperatureData: IChartFrame[] = data?.graphData?.temperature;
|
||||||
let humidityData: IChartFrame[] = data.graphData.humidity;
|
let humidityData: IChartFrame[] = data?.graphData?.humidity;
|
||||||
|
|
||||||
const dateFormat: Intl.DateTimeFormatOptions = {
|
const dateFormat: Intl.DateTimeFormatOptions = {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
@@ -19,12 +20,23 @@
|
|||||||
const wizards = brew.by.join(', ');
|
const wizards = brew.by.join(', ');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<a href="/brews" class="nav-back">
|
||||||
|
<ArrowRight />
|
||||||
|
</a>
|
||||||
|
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<div class="desktop-only image-container" style="height: {height}px">
|
<div
|
||||||
|
class="desktop-only image-container"
|
||||||
|
style="height: {height}px; background-color: {brew.color_primary || '#93a4a0'}"
|
||||||
|
>
|
||||||
<img src="/images/{brew.image}" alt="Tuborg Sommerøl" aria-label="Tuborg Sommerøl" />
|
<img src="/images/{brew.image}" alt="Tuborg Sommerøl" aria-label="Tuborg Sommerøl" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="beer-container" bind:clientHeight="{height}">
|
<div
|
||||||
|
class="beer-container"
|
||||||
|
bind:clientHeight="{height}"
|
||||||
|
style="background-color: {brew.color_secondary || '#DFE6E5'}"
|
||||||
|
>
|
||||||
<h1>{brew.beer.name}</h1>
|
<h1>{brew.beer.name}</h1>
|
||||||
|
|
||||||
<div class="links">
|
<div class="links">
|
||||||
@@ -57,29 +69,27 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="mobile-only image-container">
|
<div
|
||||||
|
class="mobile-only image-container"
|
||||||
|
style="background-color: {brew.color_primary || '#93a4a0'}"
|
||||||
|
>
|
||||||
<img src="/images/{brew.image}" alt="Tuborg Sommerøl" aria-label="Tuborg Sommerøl" />
|
<img src="/images/{brew.image}" alt="Tuborg Sommerøl" aria-label="Tuborg Sommerøl" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Historie</h3>
|
<h3>Beskrivelse</h3>
|
||||||
<p>
|
<p>{brew.description}</p>
|
||||||
I 1873 ble Tuborg Bryggeri grunnlagt av Carl Frederik Tietgen på Hellerud i Danmark. I 1970
|
|
||||||
ble Tuborg Bryggeri en del av Carlsberg.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="graph-container">
|
<div class="graph-container">
|
||||||
{#if temperatureData}
|
{#if temperatureData && temperatureData?.length}
|
||||||
<div class="graph">
|
<div class="graph">
|
||||||
<h3>Temperature during fermentation</h3>
|
<h3>Temperature during fermentation</h3>
|
||||||
<Graph dataFrames="{temperatureData}" name="Temperature" hideTitle="{true}" />
|
<Graph dataFrames="{temperatureData}" name="Temperature" hideTitle="{true}" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if} {#if humidityData && temperatureData?.length}
|
||||||
|
<div class="graph">
|
||||||
{#if humidityData}
|
<h3>Humidity during carbonation</h3>
|
||||||
<div class="graph">
|
<Graph dataFrames="{humidityData}" name="Humidity" hideTitle="{true}" />
|
||||||
<h3>Humidity during carbonation</h3>
|
</div>
|
||||||
<Graph dataFrames="{humidityData}" name="Humidity" hideTitle="{true}" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -104,6 +114,24 @@
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../../styles/media-queries.scss';
|
@import '../../../styles/media-queries.scss';
|
||||||
|
|
||||||
|
.nav-back {
|
||||||
|
position: fixed;
|
||||||
|
top: 1.5rem;
|
||||||
|
left: 1rem;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
scale: 1.3;
|
||||||
|
will-change: transform;
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Epilogue:wght@200;300;400;500;600;700;800&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Epilogue:wght@200;300;400;500;600;700;800&display=swap');
|
||||||
font-family: 'Epilogue', sans-serif;
|
font-family: 'Epilogue', sans-serif;
|
||||||
@@ -134,7 +162,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
background-color: #93a4a0;
|
|
||||||
padding: 3rem 1rem;
|
padding: 3rem 1rem;
|
||||||
|
|
||||||
@include tablet {
|
@include tablet {
|
||||||
@@ -144,7 +171,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include mobile {
|
@include mobile {
|
||||||
margin: 2rem 0;
|
width: calc(100% + 2rem);
|
||||||
|
margin: 2rem 0 2rem -1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -234,4 +262,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(canvas) {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0 0.2rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import Graph from '../../lib/components/Graph.svelte';
|
import Graph from '../../lib/components/Graph.svelte';
|
||||||
import type IChartFrame from '../../lib/interfaces/IChartFrame';
|
import type IChartFrame from '../../lib/interfaces/IChartFrame';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
@@ -21,7 +21,14 @@
|
|||||||
return fetch(`/api/graph/${unit}`, options).then((resp) => resp.json());
|
return fetch(`/api/graph/${unit}`, options).then((resp) => resp.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttonMinutes = [
|
interface IButtonMinute {
|
||||||
|
value: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout: ReturnType<typeof setTimeout>;
|
||||||
|
const buttonMinutes: Array<IButtonMinute> = [
|
||||||
|
{ value: 2.4, name: 'Realtime' },
|
||||||
{ value: 15, name: 'Last 15 minutes' },
|
{ value: 15, name: 'Last 15 minutes' },
|
||||||
{ value: 60, name: 'Last hour' },
|
{ value: 60, name: 'Last hour' },
|
||||||
{ value: 360, name: 'Last 6 hours' },
|
{ value: 360, name: 'Last 6 hours' },
|
||||||
@@ -33,8 +40,9 @@
|
|||||||
{ value: 518400, name: 'Last year' }
|
{ value: 518400, name: 'Last year' }
|
||||||
];
|
];
|
||||||
|
|
||||||
function reload(mins: number) {
|
function reload(button: IButtonMinute) {
|
||||||
minutes = mins;
|
minutes = button.value;
|
||||||
|
clearTimeout(timeout);
|
||||||
const to: Date = new Date();
|
const to: Date = new Date();
|
||||||
const from = new Date(to.getTime() - minutes * 60 * 1000);
|
const from = new Date(to.getTime() - minutes * 60 * 1000);
|
||||||
const size = 40;
|
const size = 40;
|
||||||
@@ -42,6 +50,10 @@
|
|||||||
fetchData('temperature', from, to, size).then((resp) => (temperatureData = resp?.data));
|
fetchData('temperature', from, to, size).then((resp) => (temperatureData = resp?.data));
|
||||||
fetchData('humidity', from, to, size).then((resp) => (humidityData = resp?.data));
|
fetchData('humidity', from, to, size).then((resp) => (humidityData = resp?.data));
|
||||||
fetchData('pressure', from, to, size).then((resp) => (pressureData = resp?.data));
|
fetchData('pressure', from, to, size).then((resp) => (pressureData = resp?.data));
|
||||||
|
|
||||||
|
if (button.name === 'Realtime') {
|
||||||
|
timeout = setTimeout(() => reload(button), 2000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollSelectedButtonIntoView() {
|
function scrollSelectedButtonIntoView() {
|
||||||
@@ -59,13 +71,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(scrollSelectedButtonIntoView);
|
onMount(scrollSelectedButtonIntoView);
|
||||||
|
onDestroy(() => clearTimeout(timeout))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="button-wrapper">
|
<div class="button-wrapper">
|
||||||
{#each buttonMinutes as button}
|
{#each buttonMinutes as button}
|
||||||
<button
|
<button on:click="{() => reload(button)}" class="{button.value === minutes ? 'selected' : ''}"
|
||||||
on:click="{() => reload(button.value)}"
|
>{button.name}</button
|
||||||
class="{button.value === minutes ? 'selected' : ''}">{button.name}</button
|
|
||||||
>
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
--text-color: black;
|
--text-color: black;
|
||||||
--red: #ff97a3;
|
--red: #ff97a3;
|
||||||
--blue: #9ad9ff;
|
--blue: #9ad9ff;
|
||||||
|
--orange: orange;
|
||||||
--green: #19a786;
|
--green: #19a786;
|
||||||
|
|
||||||
--header-height: 80px;
|
--header-height: 80px;
|
||||||
@@ -39,6 +40,21 @@ a {
|
|||||||
text-decoration: inherit; /* no underline */
|
text-decoration: inherit; /* no underline */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.link {
|
||||||
|
--color-text: white;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
-webkit-transition: -webkit-transform 0.15s linear;
|
||||||
|
transition: transform 0.15s linear;
|
||||||
|
-webkit-transform-origin: 50% 80%;
|
||||||
|
transform-origin: 50% 80%;
|
||||||
|
border-bottom: 2px solid var(--green);
|
||||||
|
}
|
||||||
|
a.link:hover {
|
||||||
|
border-color: white;
|
||||||
|
transform: skew(-15deg);
|
||||||
|
}
|
||||||
|
|
||||||
ul,
|
ul,
|
||||||
li {
|
li {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
$tablet-width: 1200px;
|
$tablet-width: 1200px;
|
||||||
$mobile-width: 768px;
|
$mobile-width: 768px;
|
||||||
|
|
||||||
@mixin tablet {
|
@mixin mobile {
|
||||||
@media (min-width: #{$mobile-width}) {
|
@media (max-width: #{$mobile-width}) {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin mobile {
|
@mixin tablet {
|
||||||
@media (max-width: #{$mobile-width}) {
|
@media (min-width: #{$mobile-width}) {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,4 +29,4 @@ $mobile-width: 768px;
|
|||||||
@include tablet {
|
@include tablet {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
static/images/amundsen_hazyday-hero.jpg
Normal file
BIN
static/images/amundsen_hazyday-hero.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 572 KiB |
BIN
static/images/amundsen_ink-dagger.jpg
Normal file
BIN
static/images/amundsen_ink-dagger.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Reference in New Issue
Block a user