Add a "calorie remaining today" display
This commit is contained in:
		
							
								
								
									
										59
									
								
								frontend/src/lib/components/Energy/EnergyToday.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								frontend/src/lib/components/Energy/EnergyToday.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import { foodStore } from "$lib/store/Energy/foodStore"; | ||||||
|  | 	import { settingsStore } from "$lib/store/settingsStore"; | ||||||
|  | 	import { LerpColor } from "$lib/utils"; | ||||||
|  | 	import type { Color } from "$lib/utils"; | ||||||
|  |  | ||||||
|  | 	let remainingToday: number; | ||||||
|  | 	let color: string; | ||||||
|  | 	const green: Color = { | ||||||
|  | 		h: 137, | ||||||
|  | 		s: 100, | ||||||
|  | 		l: 45, | ||||||
|  | 	}; | ||||||
|  | 	const red: Color = { | ||||||
|  | 		h: 0, | ||||||
|  | 		s: 100, | ||||||
|  | 		l: 45, | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	$: { | ||||||
|  | 		remainingToday = $settingsStore.target; | ||||||
|  | 		computeColor(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const formatter = new Intl.DateTimeFormat("en-GB", { | ||||||
|  | 		year: "numeric", | ||||||
|  | 		month: "2-digit", | ||||||
|  | 		day: "2-digit", | ||||||
|  | 		// Maybe add this to settings... | ||||||
|  | 		timeZone: "Europe/Berlin", | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	$: { | ||||||
|  | 		remainingToday = $settingsStore.target; | ||||||
|  | 		let now = new Date(); | ||||||
|  | 		let todayDate = formatter.format(now); | ||||||
|  | 		const [day, month, year] = todayDate.split('/'); | ||||||
|  | 		todayDate = `${year}-${month}-${day}`; | ||||||
|  |  | ||||||
|  | 		$foodStore.forEach((food) => { | ||||||
|  | 			if (food.date.split("T")[0] == todayDate) { | ||||||
|  | 				remainingToday -= food.energy; | ||||||
|  | 			} else { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 		remainingToday = Math.round(remainingToday); | ||||||
|  | 		computeColor(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	function computeColor() { | ||||||
|  | 		const newcolor = LerpColor(red, green, remainingToday / $settingsStore.target); | ||||||
|  | 		color = `hsl(${newcolor.h}, ${newcolor.s}%, ${newcolor.l}%)`; | ||||||
|  | 	} | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <template> | ||||||
|  | 	<div class="px-4 text-bold text-3xl" style="color: {color}">{remainingToday}</div> | ||||||
|  | </template> | ||||||
| @@ -3,6 +3,7 @@ | |||||||
| 	import { faGear } from "@fortawesome/free-solid-svg-icons"; | 	import { faGear } from "@fortawesome/free-solid-svg-icons"; | ||||||
| 	import Fa from "svelte-fa"; | 	import Fa from "svelte-fa"; | ||||||
| 	import Settings from "./Settings/Settings.svelte"; | 	import Settings from "./Settings/Settings.svelte"; | ||||||
|  | 	import EnergyToday from "./Energy/EnergyToday.svelte"; | ||||||
| 	Fa; | 	Fa; | ||||||
|  |  | ||||||
| 	type Link = { | 	type Link = { | ||||||
| @@ -87,6 +88,7 @@ | |||||||
| 			<Fa icon={faGear} scale={2} /> | 			<Fa icon={faGear} scale={2} /> | ||||||
| 		</button> | 		</button> | ||||||
| 	</div> | 	</div> | ||||||
|  | 	<EnergyToday /> | ||||||
| </header> | </header> | ||||||
|  |  | ||||||
| <Settings bind:showModal /> | <Settings bind:showModal /> | ||||||
|   | |||||||
| @@ -1,106 +1,108 @@ | |||||||
| import { cubicOut } from 'svelte/easing' | import { cubicOut } from "svelte/easing"; | ||||||
| import type { TransitionConfig } from 'svelte/transition' | import type { TransitionConfig } from "svelte/transition"; | ||||||
|  |  | ||||||
| type FlyAndScaleParams = { | type FlyAndScaleParams = { | ||||||
| 	y?: number | 	y?: number; | ||||||
| 	x?: number | 	x?: number; | ||||||
| 	start?: number | 	start?: number; | ||||||
| 	duration?: number | 	duration?: number; | ||||||
| } | }; | ||||||
|  |  | ||||||
| export const flyAndScale = ( | export const flyAndScale = ( | ||||||
| 	node: Element, | 	node: Element, | ||||||
| 	params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } | 	params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } | ||||||
| ): TransitionConfig => { | ): TransitionConfig => { | ||||||
| 	const style = getComputedStyle(node) | 	const style = getComputedStyle(node); | ||||||
| 	const transform = style.transform === 'none' ? '' : style.transform | 	const transform = style.transform === "none" ? "" : style.transform; | ||||||
|  |  | ||||||
| 	const scaleConversion = ( | 	const scaleConversion = (valueA: number, scaleA: [number, number], scaleB: [number, number]) => { | ||||||
| 		valueA: number, | 		const [minA, maxA] = scaleA; | ||||||
| 		scaleA: [number, number], | 		const [minB, maxB] = scaleB; | ||||||
| 		scaleB: [number, number] |  | ||||||
| 	) => { |  | ||||||
| 		const [minA, maxA] = scaleA |  | ||||||
| 		const [minB, maxB] = scaleB |  | ||||||
|  |  | ||||||
| 		const percentage = (valueA - minA) / (maxA - minA) | 		const percentage = (valueA - minA) / (maxA - minA); | ||||||
| 		const valueB = percentage * (maxB - minB) + minB | 		const valueB = percentage * (maxB - minB) + minB; | ||||||
|  |  | ||||||
| 		return valueB | 		return valueB; | ||||||
| 	} | 	}; | ||||||
|  |  | ||||||
| 	const styleToString = ( | 	const styleToString = (style: Record<string, number | string | undefined>): string => { | ||||||
| 		style: Record<string, number | string | undefined> |  | ||||||
| 	): string => { |  | ||||||
| 		return Object.keys(style).reduce((str, key) => { | 		return Object.keys(style).reduce((str, key) => { | ||||||
| 			if (style[key] === undefined) return str | 			if (style[key] === undefined) return str; | ||||||
| 			return str + `${key}:${style[key]};` | 			return str + `${key}:${style[key]};`; | ||||||
| 		}, '') | 		}, ""); | ||||||
| 	} | 	}; | ||||||
|  |  | ||||||
| 	return { | 	return { | ||||||
| 		duration: params.duration ?? 200, | 		duration: params.duration ?? 200, | ||||||
| 		delay: 0, | 		delay: 0, | ||||||
| 		css: t => { | 		css: (t) => { | ||||||
| 			const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]) | 			const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]); | ||||||
| 			const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]) | 			const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]); | ||||||
| 			const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]) | 			const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); | ||||||
|  |  | ||||||
| 			return styleToString({ | 			return styleToString({ | ||||||
| 				transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, | 				transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, | ||||||
| 				opacity: t | 				opacity: t, | ||||||
| 			}) | 			}); | ||||||
| 		}, | 		}, | ||||||
| 		easing: cubicOut | 		easing: cubicOut, | ||||||
| 	} | 	}; | ||||||
| } | }; | ||||||
|  |  | ||||||
| type Color = { | type Color = { | ||||||
| 	h: number | 	h: number; | ||||||
| 	s: number | 	s: number; | ||||||
| 	l: number | 	l: number; | ||||||
| } | }; | ||||||
|  |  | ||||||
| function ColorDistance(color1: Color, color2: Color) { | function ColorDistance(color1: Color, color2: Color) { | ||||||
| 	return Math.abs(color1.h - color2.h) | 	return Math.abs(color1.h - color2.h); | ||||||
| } | } | ||||||
|  |  | ||||||
| function GenerateRandomHSL(): Color { | function GenerateRandomHSL(): Color { | ||||||
| 	const hue = Math.floor(Math.random() * 360) | 	const hue = Math.floor(Math.random() * 360); | ||||||
| 	const saturation = 70 | 	const saturation = 70; | ||||||
| 	const lightness = 60 | 	const lightness = 60; | ||||||
| 	return { h: hue, s: saturation, l: lightness } | 	return { h: hue, s: saturation, l: lightness }; | ||||||
| } | } | ||||||
|  |  | ||||||
| const existingColors: Color[] = [] | const existingColors: Color[] = []; | ||||||
|  |  | ||||||
| function GenerateColor(): string { | function GenerateColor(): string { | ||||||
| 	const minDistance = 15 | 	const minDistance = 15; | ||||||
|  |  | ||||||
| 	let newColor: Color | 	let newColor: Color; | ||||||
| 	let isDistinct = false | 	let isDistinct = false; | ||||||
| 	let iterations = 0 | 	let iterations = 0; | ||||||
| 	while (!isDistinct) { | 	while (!isDistinct) { | ||||||
| 		iterations++ | 		iterations++; | ||||||
| 		if (iterations > 1000) { | 		if (iterations > 100) { | ||||||
| 			console.error('Failed to generate a distinct color after 1000 iterations') | 			console.error("Failed to generate a distinct color after 100 iterations"); | ||||||
| 			break | 			break; | ||||||
| 		} | 		} | ||||||
| 		newColor = GenerateRandomHSL() | 		newColor = GenerateRandomHSL(); | ||||||
| 		isDistinct = true | 		isDistinct = true; | ||||||
|  |  | ||||||
| 		for (const color of existingColors) { | 		for (const color of existingColors) { | ||||||
| 			if (ColorDistance(newColor, color) < minDistance) { | 			if (ColorDistance(newColor, color) < minDistance) { | ||||||
| 				isDistinct = false | 				isDistinct = false; | ||||||
| 				break | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		existingColors.push(newColor) | 		existingColors.push(newColor); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// We can not reach this point without having a color generated | 	// We can not reach this point without having a color generated | ||||||
| 	// @ts-ignore | 	// @ts-ignore | ||||||
| 	return `hsl(${newColor.h}, ${newColor.s}%, ${newColor.l}%)` | 	return `hsl(${newColor.h}, ${newColor.s}%, ${newColor.l}%)`; | ||||||
| } | } | ||||||
|  |  | ||||||
| export { GenerateColor } | function LerpColor(color1: Color, color2: Color, t: number): Color { | ||||||
|  | 	const h = color1.h + (color2.h - color1.h) * t; | ||||||
|  | 	const s = color1.s + (color2.s - color1.s) * t; | ||||||
|  | 	const l = color1.l + (color2.l - color1.l) * t; | ||||||
|  | 	return { h, s, l }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export { GenerateColor, LerpColor }; | ||||||
|  | export type { Color }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user