Refactor, move all pages to separate folder
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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, },
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
+5
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user