Refactor, move all pages to separate folder

This commit is contained in:
Guntis Smaukstelis
2025-03-14 11:31:47 +02:00
parent 968a06c0f0
commit a2d1608cb0
54 changed files with 95 additions and 104 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ import { Chart } from "chart.js";
import { CityLargeChart } from "./CityLargeChart";
import { createCustomChart } from "./CustomChart";
import { DataQuery, formatDateString } from "../../cities/helpers";
import { DataQuery, formatDateString } from "../../pages/cities/helpers";
export const CityChart: Component<{
city: () => string;
+1 -1
View File
@@ -3,7 +3,7 @@ import { Chart } from "chart.js";
import "../../css/overlay.css"
import { createCustomChart } from "./CustomChart";
import { DataQuery, formatDateString } from "../../cities/helpers";
import { DataQuery, formatDateString } from "../../pages/cities/helpers";
export const CityLargeChart: Component<{
city: () => string;
+99
View File
@@ -0,0 +1,99 @@
import { Component, createEffect, createSignal, onMount } from "solid-js";
import { ResultKeyVal } from "../../consts";
import { WindInputs, WindSignals } from "./WindInputs";
import arrowUrl from "../../assets/arrow.webp";
import { cityCoords } from "../cityCoords";
import { MapResolution, resolutionProps, windProps, WindProps } from "./mapConsts";
import { CityData, drawOnMap } from "./canvasDraw";
import { IconInputs } from "../weatherIcons/IconInputs";
export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[] }> = ({ type, data }) => {
const [getCanvas, setCanvas] = createSignal<HTMLCanvasElement>();
const [getImg, setImg] = createSignal(new Image());
const weatherIconsSingal = createSignal<{[key: string]: string;}>({});
const [getWeatherIcons] = weatherIconsSingal;
let lastCoords: CityData[] = [];
const props = resolutionProps[type];
const arrowImg = new Image();
arrowImg.src = arrowUrl;
const windSignals: WindSignals = {
direction: createSignal(""),
speed: createSignal(""),
gusts: createSignal(""),
roundValues: createSignal(false),
}
function getWindData(): [string, string, string, boolean, WindProps] {
return [
windSignals.direction[0](),
windSignals.speed[0](),
windSignals.gusts[0](),
windSignals.roundValues[0](),
windProps[type],
];
}
onMount(() => {
const ctx = getCanvas()!.getContext("2d")!;
const img = new Image();
img.onload = () => {
setImg(img);
drawOnMap(
ctx,
[img, arrowImg],
lastCoords,
getWindData(),
props,
);
};
img.src = props.map;
});
createEffect(() => {
const ctx = getCanvas()!.getContext("2d")!;
const weatherIcons = getWeatherIcons();
const coordsAndData: CityData[] = data()
.map(([city, value]) => [
city,
cityCoords[city as keyof typeof cityCoords],
typeof value === "number" ? value : -99,
weatherIcons[city],
]);
lastCoords = coordsAndData;
drawOnMap(
ctx,
[getImg(), arrowImg],
coordsAndData,
getWindData(),
props,
);
});
const getStyleSize = () => {
return props.width === 1920
? { "width": "1000px", "height": "562px" }
: { "width": "1000px", "height": "374px" };
}
return (
<>
<div class="grid-1-2">
<div>
{ props.showWind && <WindInputs signals={windSignals} /> }
</div>
<div><IconInputs weatherIconsSingal={weatherIconsSingal} /></div>
</div>
<canvas
ref={setCanvas}
width={props.width+"px"}
height={props.height+"px"}
style={getStyleSize()}
/>
</>
);
}
+53
View File
@@ -0,0 +1,53 @@
import { Component, Signal } from "solid-js";
export interface WindSignals {
direction: Signal<string>;
speed: Signal<string>;
gusts: Signal<string>;
roundValues: Signal<boolean>;
}
export const WindInputs: Component<{signals: WindSignals}> = (
{ signals: {
direction: [getDirection, setDirection],
speed: [getSpeed, setSpeed],
gusts: [getGusts, setGusts],
roundValues: [getRoundValues, setRoundValues],
}}
) => {
return (
<>
<div>
<label>
<input
type="checkbox"
checked={getRoundValues()}
onChange={e => setRoundValues(e.target.checked)}
/>
Round values
</label>
</div>
<div>
<input
type="text"
onInput={e => setDirection(e.target.value)}
/>
Wind direction
</div>
<div>
<input
type="text"
onInput={e => setSpeed(e.target.value)}
/>
Wind speed
</div>
<div>
<input
type="text"
onInput={e => setGusts(e.target.value)}
/>
Gusts
</div>
</>
);
}
+78
View File
@@ -0,0 +1,78 @@
import { ResolutionPropsValue, WindProps, windProps } from "./mapConsts";
import { drawRotatedImage, getAngleFromString } from "./windAngles";
// cityName, {x, y}, weatherValue, icon
export type CityData = [cityName: string, {x: number, y: number }, weatherValue: number, icon: string | undefined]
export function drawOnMap(
ctx: CanvasRenderingContext2D,
imgArr: [HTMLImageElement, HTMLImageElement],
cityData: CityData[],
windData: [string, string, string, boolean, WindProps],
props: ResolutionPropsValue,
): void {
const [windDirection, windSpeed, windGusts, roundValues, windProp] = windData;
const [bgImg, arrowImg] = imgArr;
// bg
ctx.drawImage(bgImg, 0, 0);
// city boxes
ctx.fillStyle = "#FFFFFF";
cityData.forEach(([, {x, y}]) => {
const localX = props.offsetX + x * props.scale;
const localY = props.offsetY + y * props.scale;
ctx.beginPath();
ctx.fillStyle = "#FFFFFF";
ctx.rect(localX-props.boxSize/2, localY-props.boxSize/2, props.boxSize, props.boxSize);
ctx.fill();
});
ctx.fill();
// weather values
cityData.forEach(([, {x, y}, value, icon]) => {
const localX = props.offsetX + x * props.scale;
const localY = props.offsetY + y * props.scale;
ctx.font = `bold ${props.fontSize}px Rubik`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#000000";
const numericValue = roundValues ? Math.round(value) : value;
const stringValue = numericValue.toString().replace(".", ",");
ctx.fillText(stringValue, localX, localY);
if (icon) {
ctx.fillStyle = "#FFFFFF";
ctx.font = `normal ${props.fontSize * 5}px Daira by LTV grafika`;
const iconSize = ctx.measureText(icon);
ctx.fillText(icon, localX + props.boxSize/2 + 10 + iconSize.width/2, localY);
}
});
if (!props.showWind) return;
// wind values
ctx.font = `bold ${props.windFontSize}px Rubik`;
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillStyle = "#FFFFFF";
const boxMiddleX = 1675;
const directionWidth = ctx.measureText(windDirection).width;
ctx.fillText(windDirection, windProp.offsetX + boxMiddleX - directionWidth / 2 + 30, (windProp.offsetY + 370)*windProp.scaleY);
const angleInDegrees = getAngleFromString(windDirection);
drawRotatedImage(ctx, arrowImg, windProp.offsetX + boxMiddleX - directionWidth / 2 - 30, (windProp.offsetY + 370)*windProp.scaleY, angleInDegrees);
const speedWidth = ctx.measureText(windSpeed).width;
ctx.fillText(windSpeed, windProp.offsetX + boxMiddleX - speedWidth / 2 - 30, (windProp.offsetY + 575)*windProp.scaleY);
ctx.font = "bold 25px Rubik";
ctx.fillText("M/S", windProp.offsetX + boxMiddleX + speedWidth / 2 - 22, (windProp.offsetY + 590)*windProp.scaleY);
ctx.font = `bold ${props.windFontSize}px Rubik`;
const gustsWidth = ctx.measureText(windGusts).width;
ctx.fillText(windGusts, windProp.offsetX + boxMiddleX - gustsWidth / 2 - 30, (windProp.offsetY + 790)*windProp.scaleY);
ctx.font = "bold 25px Rubik";
ctx.fillText("M/S", windProp.offsetX + boxMiddleX + gustsWidth / 2 - 22, (windProp.offsetY + 805)*windProp.scaleY);
}
+37
View File
@@ -0,0 +1,37 @@
import map_1920x1080 from "../../assets/map_1920x1080.webp";
import map_1920x1080_wind from "../../assets/map_1920x1080_wind.webp";
import map_3840x1440 from "../../assets/map_3840x1440.webp";
import map_3840x1440_wind from "../../assets/map_3840x1440_wind.webp";
export type MapResolution = "map_1920x1080" | "map_1920x1080_wind" | "map_3840x1440" | "map_3840x1440_wind";
export type ResolutionPropsValue = {
offsetX: number;
offsetY: number;
width: number;
height: number;
scale: number;
showWind: boolean;
map: string;
fontSize: number;
boxSize: number;
windFontSize: number;
};
export const resolutionProps: Record<string, ResolutionPropsValue> = {
"map_1920x1080": { offsetX: 290, offsetY: 120, width: 1920, height: 1080, scale: 1.35, fontSize: 30, boxSize: 80, showWind: false, map: map_1920x1080, windFontSize: 45, },
"map_1920x1080_wind": { offsetX: 90, offsetY: 120, width: 1920, height: 1080, scale: 1.35, fontSize: 30, boxSize: 80, showWind: true, map: map_1920x1080_wind, windFontSize: 45, },
"map_3840x1440": { offsetX: 800, offsetY: 120, width: 3840, height: 1440, scale: 2.2, fontSize: 68, boxSize: 140, showWind: false, map: map_3840x1440, windFontSize: 60, },
"map_3840x1440_wind": { offsetX: 800, offsetY: 120, width: 3840, height: 1440, scale: 2.2, fontSize: 68, boxSize: 140, showWind: true, map: map_3840x1440_wind, windFontSize: 60, },
}
export type WindProps = {
offsetX: number;
offsetY: number;
scaleY: number;
};
export const windProps: Record<string, WindProps> = {
"map_1920x1080_wind": { offsetX: 0, offsetY: 0, scaleY: 1, },
"map_3840x1440_wind": { offsetX: 1610, offsetY: 75, scaleY: 1.24, },
}
+27
View File
@@ -0,0 +1,27 @@
const windAngles: {[key: string]: number} = {
"R": 0,
"ZR": 45,
"Z": 90,
"ZA": 135,
"A": 180,
"DA": 225,
"D": 270,
"DR": 315,
}
export function getAngleFromString(str: string): number {
return windAngles[str] ?? 0;
}
export function drawRotatedImage(
ctx: CanvasRenderingContext2D,
img: HTMLImageElement,
x: number,
y: number,
angleInDegrees: number): void {
const angleInRadians = angleInDegrees * (Math.PI / 180);
ctx.translate(x + img.width / 2, y + img.height / 2);
ctx.rotate(angleInRadians);
ctx.drawImage(img, -img.width / 2, -img.height / 2, img.width, img.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
@@ -1,10 +1,12 @@
import { Component } from "solid-js";
import { Component } from 'solid-js'
import styles from './spinner.module.css'
export const LoadingSpinner: Component<{ text: string; }> = ({ text }) => {
return (
<div>
<span class="spinner"></span>
<span class={styles.spinner}></span>
<span style={{ "padding-left": "16px" }}>{ text }</span>
</div>
);
)
}
@@ -0,0 +1,26 @@
.spinner {
display: inline-block;
width: 16px;
height: 16px;
}
.spinner:after {
content: " ";
display: block;
width: 14px;
height: 14px;
margin: 0px;
border-radius: 50%;
border: 6px solid #333;
border-color: #333 transparent #333 transparent;
animation: spinner 1.2s linear infinite;
}
@keyframes spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}