✨ Improve location selection for weather
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { Center, Group, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useElementSize } from '@mantine/hooks';
|
||||
import { IconArrowDownRight, IconArrowUpRight, IconCloudRain } from '@tabler/icons-react';
|
||||
import { api } from '~/utils/api';
|
||||
import { defineWidget } from '../helper';
|
||||
import { IWidget } from '../widgets';
|
||||
import { useWeatherForCity } from './useWeatherForCity';
|
||||
import { WeatherIcon } from './WeatherIcon';
|
||||
|
||||
const definition = defineWidget({
|
||||
@@ -15,8 +15,12 @@ const definition = defineWidget({
|
||||
defaultValue: false,
|
||||
},
|
||||
location: {
|
||||
type: 'text',
|
||||
defaultValue: 'Paris',
|
||||
type: 'location',
|
||||
defaultValue: {
|
||||
name: 'Paris',
|
||||
latitude: 48.85341,
|
||||
longitude: 2.3488,
|
||||
},
|
||||
},
|
||||
},
|
||||
gridstack: {
|
||||
@@ -35,8 +39,8 @@ interface WeatherTileProps {
|
||||
}
|
||||
|
||||
function WeatherTile({ widget }: WeatherTileProps) {
|
||||
const { data: weather, isLoading, isError } = useWeatherForCity(widget.properties.location);
|
||||
const { width, height, ref } = useElementSize();
|
||||
const { data: weather, isLoading, isError } = api.weather.at.useQuery(widget.properties.location);
|
||||
const { width, ref } = useElementSize();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -77,10 +81,10 @@ function WeatherTile({ widget }: WeatherTileProps) {
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
>
|
||||
<Group align="center" position="center" spacing="xs">
|
||||
<WeatherIcon code={weather!.current_weather.weathercode} />
|
||||
<WeatherIcon code={weather.current_weather.weathercode} />
|
||||
<Title>
|
||||
{getPerferedUnit(
|
||||
weather!.current_weather.temperature,
|
||||
weather.current_weather.temperature,
|
||||
widget.properties.displayInFahrenheit
|
||||
)}
|
||||
</Title>
|
||||
@@ -89,12 +93,12 @@ function WeatherTile({ widget }: WeatherTileProps) {
|
||||
<Group noWrap spacing="xs">
|
||||
<IconArrowUpRight />
|
||||
{getPerferedUnit(
|
||||
weather!.daily.temperature_2m_max[0],
|
||||
weather.daily.temperature_2m_max[0],
|
||||
widget.properties.displayInFahrenheit
|
||||
)}
|
||||
<IconArrowDownRight />
|
||||
{getPerferedUnit(
|
||||
weather!.daily.temperature_2m_min[0],
|
||||
weather.daily.temperature_2m_min[0],
|
||||
widget.properties.displayInFahrenheit
|
||||
)}
|
||||
</Group>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// To parse this data:
|
||||
//
|
||||
// import { Convert, WeatherResponse } from "./file";
|
||||
//
|
||||
// const weatherResponse = Convert.toWeatherResponse(json);
|
||||
//
|
||||
// These functions will throw an error if the JSON doesn't
|
||||
// match the expected interface, even if the JSON is valid.
|
||||
|
||||
export interface WeatherResponse {
|
||||
current_weather: CurrentWeather;
|
||||
utc_offset_seconds: number;
|
||||
latitude: number;
|
||||
elevation: number;
|
||||
longitude: number;
|
||||
generationtime_ms: number;
|
||||
daily_units: DailyUnits;
|
||||
daily: Daily;
|
||||
}
|
||||
|
||||
export interface CurrentWeather {
|
||||
winddirection: number;
|
||||
windspeed: number;
|
||||
time: string;
|
||||
weathercode: number;
|
||||
temperature: number;
|
||||
}
|
||||
|
||||
export interface Daily {
|
||||
temperature_2m_max: number[];
|
||||
time: Date[];
|
||||
temperature_2m_min: number[];
|
||||
weathercode: number[];
|
||||
}
|
||||
|
||||
export interface DailyUnits {
|
||||
temperature_2m_max: string;
|
||||
temperature_2m_min: string;
|
||||
time: string;
|
||||
weathercode: string;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { WeatherResponse } from './types';
|
||||
|
||||
/**
|
||||
* Requests the weather of the specified city
|
||||
* @param cityName name of the city where the weather should be requested
|
||||
* @returns weather of specified city
|
||||
*/
|
||||
export const useWeatherForCity = (cityName: string) => {
|
||||
const {
|
||||
data: city,
|
||||
isLoading,
|
||||
isError,
|
||||
} = useQuery({
|
||||
queryKey: ['weatherCity', { cityName }],
|
||||
queryFn: () => fetchCity(cityName),
|
||||
cacheTime: 1000 * 60 * 60 * 24, // the city is cached for 24 hours
|
||||
staleTime: Infinity, // the city is never considered stale
|
||||
});
|
||||
const weatherQuery = useQuery({
|
||||
queryKey: ['weather', { cityName }],
|
||||
queryFn: () => fetchWeather(city?.results[0]),
|
||||
enabled: Boolean(city),
|
||||
cacheTime: 1000 * 60 * 60 * 6, // the weather is cached for 6 hours
|
||||
staleTime: 1000 * 60 * 5, // the weather is considered stale after 5 minutes
|
||||
});
|
||||
|
||||
return {
|
||||
...weatherQuery,
|
||||
isLoading: weatherQuery.isLoading || isLoading,
|
||||
isError: weatherQuery.isError || isError,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests the coordinates of a city
|
||||
* @param cityName name of city
|
||||
* @returns list with all coordinates for citites with specified name
|
||||
*/
|
||||
const fetchCity = async (cityName: string) => {
|
||||
const res = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${cityName}`);
|
||||
return (await res.json()) as { results: Coordinates[] };
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests the weather of specific coordinates
|
||||
* @param coordinates of the location the weather should be fetched
|
||||
* @returns weather of specified coordinates
|
||||
*/
|
||||
async function fetchWeather(coordinates?: Coordinates) {
|
||||
if (!coordinates) return null;
|
||||
const { longitude, latitude } = coordinates;
|
||||
const res = await fetch(
|
||||
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min¤t_weather=true&timezone=Europe%2FLondon`
|
||||
);
|
||||
// eslint-disable-next-line consistent-return
|
||||
return (await res.json()) as WeatherResponse;
|
||||
}
|
||||
|
||||
type Coordinates = { latitude: number; longitude: number };
|
||||
@@ -40,7 +40,8 @@ export type IWidgetOptionValue =
|
||||
| INumberInputOptionValue
|
||||
| IDraggableListInputValue
|
||||
| IDraggableEditableListInputValue<any>
|
||||
| IMultipleTextInputOptionValue;
|
||||
| IMultipleTextInputOptionValue
|
||||
| ILocationOptionValue;
|
||||
|
||||
// Interface for data type
|
||||
interface DataType {
|
||||
@@ -95,6 +96,11 @@ export type ISliderInputOptionValue = {
|
||||
inputProps?: Partial<SliderProps>;
|
||||
};
|
||||
|
||||
type ILocationOptionValue = {
|
||||
type: 'location';
|
||||
defaultValue: { latitude: number; longitude: number };
|
||||
};
|
||||
|
||||
// will show a sortable list that can have sub settings
|
||||
export type IDraggableListInputValue = {
|
||||
type: 'draggable-list';
|
||||
|
||||
Reference in New Issue
Block a user