mirror of
				https://github.com/KevinMidboe/brewPi.git
				synced 2025-10-29 16:50:12 +00:00 
			
		
		
		
	Feat: Colored brew pages (#4)
* Removed graph animation * Smoother graph by removing data point bubbles * Added relatime button which keeps reloading every 2 sec * Clear timeout when destroying/navigating away from page * Added primary & secondary colors to match label per brew * Only render graph if dataset is list of length * Also color mobile image container primary color of brew * Add white background on graphs because of low contrast secondary background color
This commit is contained in:
		| @@ -1,19 +1,23 @@ | ||||
| [{ | ||||
| [ | ||||
|   { | ||||
|     "beer": { | ||||
|       "name": "Kveldsbris", | ||||
|       "brewery": "Kinn Bryggeri", | ||||
|     "category": "Pilsner/Lys Lager", | ||||
|       "category": "Belgian Pale Ale", | ||||
|       "description": "" | ||||
|     }, | ||||
|     "date": "1682272800", | ||||
|     "by": ["Alf", "Kevin"], | ||||
|     "abv": "5.6", | ||||
|   "description": "", | ||||
|     "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", | ||||
|     "recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ", | ||||
|     "order_page": "https://oslo.bryggselv.no/finest/104923/finest-originals-utepils-allgrain-ølsett-25-liter", | ||||
|   "untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024" | ||||
| }, { | ||||
|     "untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024", | ||||
|     "color_primary": "#FCF28F", | ||||
|     "color_secondary": "rgba(252, 242, 143, 0.2)" | ||||
|   }, | ||||
|   { | ||||
|     "beer": { | ||||
|       "name": "FUCK YEAH IPA", | ||||
|       "brewery": "Finest", | ||||
| @@ -27,8 +31,11 @@ | ||||
|     "image": "finest_fuck-yeah-IPA.jpg", | ||||
|     "recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ", | ||||
|     "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" | ||||
| }, { | ||||
|     "untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024", | ||||
|     "color_primary": "#0169A6", | ||||
|     "color_secondary": "rgba(1, 105, 166, 0.2)" | ||||
|   }, | ||||
|   { | ||||
|     "beer": { | ||||
|       "name": "Love in a canoe", | ||||
|       "brewery": "Finest", | ||||
| @@ -42,8 +49,11 @@ | ||||
|     "image": "finest_love-in-a-canoe.jpeg", | ||||
|     "recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ", | ||||
|     "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" | ||||
| }, { | ||||
|     "untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024", | ||||
|     "color_primary": "#F7F0DF", | ||||
|     "color_secondary": "rgba(247, 240, 223, 0.2)" | ||||
|   }, | ||||
|   { | ||||
|     "beer": { | ||||
|       "name": "Utepils", | ||||
|       "brewery": "Finest", | ||||
| @@ -57,8 +67,11 @@ | ||||
|     "image": "finest_utepils.jpeg", | ||||
|     "recipe": "https://docs.google.com/document/d/1FL7ibXxW1r_zFNLK338pyjfMiCCaTOi2fzuMoInA3dQ", | ||||
|     "order_page": "https://www.bryggselv.no/finest/105932/kinn-kveldsbris-allgrain-ølsett-25-liter", | ||||
|   "untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024" | ||||
| }, { | ||||
|     "untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024", | ||||
|     "color_primary": "#F4E9D3", | ||||
|     "color_secondary": "rgba(244, 233, 211, 0.2)" | ||||
|   }, | ||||
|   { | ||||
|     "beer": { | ||||
|       "name": "HELLES Tysk Lager", | ||||
|       "brewery": "Münchener Helles", | ||||
| @@ -72,8 +85,11 @@ | ||||
|     "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" | ||||
| }, { | ||||
|     "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", | ||||
| @@ -87,5 +103,8 @@ | ||||
|     "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" | ||||
| }] | ||||
|     "untapped": "https://untappd.com/b/kinn-bryggeri-kveldsbris/695024", | ||||
|     "color_primary": "#6C8D9E", | ||||
|     "color_secondary": "rgba(108, 141, 158, 0.2)" | ||||
|   } | ||||
| ] | ||||
|   | ||||
| @@ -94,17 +94,16 @@ | ||||
|  | ||||
|   function renderChart() { | ||||
|     const context: CanvasRenderingContext2D | null = chartCanvas.getContext('2d'); | ||||
|     if (!context) return | ||||
|     if (!context) return; | ||||
|  | ||||
|     // create labels and singular dataset (data) | ||||
|     const labels: string[] = dateLabelsFormatedBasedOnResolution(dataFrames); | ||||
|     const data: ChartDataset = { | ||||
|       data: dataFrames.map((frame) => frame.value), | ||||
|       borderWidth: 3, | ||||
|       borderWidth: 3 | ||||
|     }; | ||||
|     // based on name, add label and color options to dataset | ||||
|     setDataColorAndName(data) | ||||
|  | ||||
|     setDataColorAndName(data); | ||||
|  | ||||
|     // create chart instance, most here is chart options | ||||
|     chart = new Chart(context, { | ||||
| @@ -114,9 +113,12 @@ | ||||
|         datasets: [data] | ||||
|       }, | ||||
|       options: { | ||||
|         animation: { | ||||
|           duration: 0 | ||||
|         }, | ||||
|         elements: { | ||||
|           point: { | ||||
|             radius: 2 | ||||
|             radius: 0 | ||||
|           }, | ||||
|           line: { | ||||
|             tension: 0.5 | ||||
|   | ||||
| @@ -5,8 +5,8 @@ | ||||
|  | ||||
|   export let data; | ||||
|   let brew = data.brew; | ||||
|   let temperatureData: IChartFrame[] = data.graphData.temperature; | ||||
|   let humidityData: IChartFrame[] = data.graphData.humidity; | ||||
|   let temperatureData: IChartFrame[] = data?.graphData?.temperature; | ||||
|   let humidityData: IChartFrame[] = data?.graphData?.humidity; | ||||
|  | ||||
|   const dateFormat: Intl.DateTimeFormatOptions = { | ||||
|     weekday: 'long', | ||||
| @@ -20,11 +20,11 @@ | ||||
| </script> | ||||
|  | ||||
| <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" /> | ||||
|   </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> | ||||
|  | ||||
|     <div class="links"> | ||||
| @@ -57,25 +57,22 @@ | ||||
|       </tbody> | ||||
|     </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" /> | ||||
|     </div> | ||||
|  | ||||
|     <h3>Historie</h3> | ||||
|     <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> | ||||
|     <h3>Beskrivelse</h3> | ||||
|     <p>{brew.description}</p> | ||||
|  | ||||
|     <div class="graph-container"> | ||||
|       {#if temperatureData} | ||||
|       {#if temperatureData && temperatureData?.length} | ||||
|         <div class="graph"> | ||||
|           <h3>Temperature during fermentation</h3> | ||||
|           <Graph dataFrames="{temperatureData}" name="Temperature" hideTitle="{true}" /> | ||||
|         </div> | ||||
|       {/if} | ||||
|  | ||||
|       {#if humidityData} | ||||
|       {#if humidityData && temperatureData?.length} | ||||
|         <div class="graph"> | ||||
|           <h3>Humidity during carbonation</h3> | ||||
|           <Graph dataFrames="{humidityData}" name="Humidity" hideTitle="{true}" /> | ||||
| @@ -134,7 +131,6 @@ | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     min-height: 1px; | ||||
|     background-color: #93a4a0; | ||||
|     padding: 3rem 1rem; | ||||
|  | ||||
|     @include tablet { | ||||
| @@ -144,7 +140,8 @@ | ||||
|     } | ||||
|  | ||||
|     @include mobile { | ||||
|       margin: 2rem 0; | ||||
|       width: calc(100% + 2rem); | ||||
|       margin: 2rem 0 2rem -1rem; | ||||
|     } | ||||
|  | ||||
|     img { | ||||
| @@ -234,4 +231,10 @@ | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   :global(canvas) { | ||||
|     background-color: white; | ||||
|     border-radius: 0.5rem; | ||||
|     padding: 0 0.2rem; | ||||
|   } | ||||
| </style> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <script lang="ts"> | ||||
|   import { onMount } from 'svelte'; | ||||
|   import { onDestroy, onMount } from 'svelte'; | ||||
|   import Graph from '../../lib/components/Graph.svelte'; | ||||
|   import type IChartFrame from '../../lib/interfaces/IChartFrame'; | ||||
|   import type { PageData } from './$types'; | ||||
| @@ -21,7 +21,14 @@ | ||||
|     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: 60, name: 'Last hour' }, | ||||
|     { value: 360, name: 'Last 6 hours' }, | ||||
| @@ -33,8 +40,9 @@ | ||||
|     { value: 518400, name: 'Last year' } | ||||
|   ]; | ||||
|  | ||||
|   function reload(mins: number) { | ||||
|     minutes = mins; | ||||
|   function reload(button: IButtonMinute) { | ||||
|     minutes = button.value; | ||||
|     clearTimeout(timeout); | ||||
|     const to: Date = new Date(); | ||||
|     const from = new Date(to.getTime() - minutes * 60 * 1000); | ||||
|     const size = 40; | ||||
| @@ -42,6 +50,10 @@ | ||||
|     fetchData('temperature', from, to, size).then((resp) => (temperatureData = resp?.data)); | ||||
|     fetchData('humidity', from, to, size).then((resp) => (humidityData = resp?.data)); | ||||
|     fetchData('pressure', from, to, size).then((resp) => (pressureData = resp?.data)); | ||||
|  | ||||
|     if (button.name === 'Realtime') { | ||||
|       timeout = setTimeout(() => reload(button), 2000); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   function scrollSelectedButtonIntoView() { | ||||
| @@ -59,13 +71,13 @@ | ||||
|   } | ||||
|  | ||||
|   onMount(scrollSelectedButtonIntoView); | ||||
|   onDestroy(() => clearTimeout(timeout)) | ||||
| </script> | ||||
|  | ||||
| <div class="button-wrapper"> | ||||
|   {#each buttonMinutes as button} | ||||
|     <button | ||||
|       on:click="{() => reload(button.value)}" | ||||
|       class="{button.value === minutes ? 'selected' : ''}">{button.name}</button | ||||
|     <button on:click="{() => reload(button)}" class="{button.value === minutes ? 'selected' : ''}" | ||||
|       >{button.name}</button | ||||
|     > | ||||
|   {/each} | ||||
| </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user