diff --git a/assets/fish-background.svg b/assets/fish-background.svg index 68c3701..ea94376 100644 --- a/assets/fish-background.svg +++ b/assets/fish-background.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/scale-section/style.scss b/src/scale-section/style.scss index 509e5ab..a0272f9 100644 --- a/src/scale-section/style.scss +++ b/src/scale-section/style.scss @@ -9,7 +9,7 @@ border: none; .wp-scale-section-content { - background-color: white; + background-color: #fff; text-align: center; h2 { diff --git a/src/weather/block.json b/src/weather/block.json index 9603d2a..e923c27 100644 --- a/src/weather/block.json +++ b/src/weather/block.json @@ -12,13 +12,9 @@ }, "textdomain": "spearfishing-stuff", "attributes": { - "content": { + "time": { "type": "string", - "default": "Heading" - }, - "height": { - "type": "number", - "default": 300 + "default": "current" } }, "editorScript": "file:./index.js", diff --git a/src/weather/edit.tsx b/src/weather/edit.tsx index 2044e99..afeff1c 100644 --- a/src/weather/edit.tsx +++ b/src/weather/edit.tsx @@ -1,20 +1,30 @@ import { useBlockProps, InspectorControls } from "@wordpress/block-editor"; import ServerSideRender from "@wordpress/server-side-render"; -import { PanelBody } from "@wordpress/components"; +import { PanelBody, RadioControl } from "@wordpress/components"; import "./editor.scss"; import React from "react"; export default function Edit({ attributes, setAttributes }) { const blockProps = useBlockProps(); + const { time } = attributes; return ( <>
- +
- - {/* Add settings in the future here */} + + setAttributes({ time: value })} + /> + ); diff --git a/src/weather/style.scss b/src/weather/style.scss index 1687471..b18d590 100644 --- a/src/weather/style.scss +++ b/src/weather/style.scss @@ -1,11 +1,23 @@ .wp-block-spearfishing-stuff-weather { margin: 20px auto; - max-width: 1440px; + max-width: 1440px !important; + width: 100%; - .wp-weather-content { + // Mobile responsive for screens smaller than 500px + @media (max-width: 500px) { + .wp-spearfishing-weather-content { + flex-direction: column; + } + } + + .wp-spearfishing-weather-content { display: flex; gap: 20px; + span.unit { + font-size: 12px; + } + .weather-data { color: #fff; background-color: rgb(0 3 55 / 50%); @@ -16,41 +28,84 @@ border-radius: 5px; padding: 20px; - &.heading h2 { - font-weight: 100; + svg { + fill: #fff; } - .direction { - svg { - fill: #fff; - margin: 0; - } + p, + h3 { + margin: 0; + } + } - p { - margin: 0; - font-size: 12px; - } + .forecast-day { + width: 100%; - h3 { - font-weight: 100; - margin: 0; - } - } + .forecast-data { + display: grid; + grid-template-rows: repeat(14, auto); + width: 100%; + overflow: hidden; + border: 1px solid #000; + border-radius: 5px; + + .forecast-hour { + display: flex; + justify-content: space-between; + align-items: center; + height: 0; + overflow: hidden; + opacity: 0; + transition: 500ms ease-in-out all, opacity 500ms 100ms ease-in-out; + + &.best-hour { + opacity: 1; + height: 40px; + padding: 5px; + background-color: #ffcc00; + border: 1px solid black; + margin: -1px; + } - .data { - display: flex; - flex-direction: column; + .wind, + .wave { + &.good { + color: green; + } + &.bad { + color: var(--wp--preset--color--secondary); + } + } - span { - font-size: 26px; - margin-bottom: -10px; + & > div { + display: flex; + width: 25%; + justify-content: space-between; + } + + h4, + p { + margin: 0; + } } + } - p { - margin: 0; - font-size: 12px; + &.open { + .forecast-hour { + opacity: 1; + height: 40px; + padding: 5px; + border-bottom: 1px solid #000; + + &:last-of-type { + border-bottom: none; + } } } } } -} \ No newline at end of file +} + +.wp-block-cover { + border-radius: 5px; +} diff --git a/src/weather/view.js b/src/weather/view.js index 4a02b92..00f434d 100644 --- a/src/weather/view.js +++ b/src/weather/view.js @@ -1,3 +1,42 @@ /** - * Empty for now. + * On DOM Ready + * @param {Object} options */ + +document.addEventListener( 'DOMContentLoaded', () => { + const forecastDays = document.querySelectorAll( '.forecast-day' ); + + forecastDays.forEach( ( day ) => { + // Add the toggle class to the forecast-day element + day.addEventListener( 'click', () => day.classList.toggle( 'open' ) ); + + // Sort the hours by data-rating + const hours = day.querySelectorAll( '.forecast-hour' ); + const hoursArray = Array.from( hours ); + const sortedHours = hoursArray.sort( ( a, b ) => a.dataset.rating - b.dataset.rating ); + sortedHours[0].classList.add( 'best-hour' ); + sortedHours[1].classList.add( 'best-hour' ); + + // Tag the best and worst wind and wave forecasts + const wind = day.querySelectorAll( '.wind' ); + const windArray = Array.from( wind ); + windArray.map( ( wind ) => { + if ( 3.9 >= wind.dataset.speed ) { + wind.classList.add( 'good' ); + } else if ( 10 <= wind.dataset.speed ) { + wind.classList.add( 'bad' ); + } + } ); + + const wave = day.querySelectorAll( '.wave' ); + const waveArray = Array.from( wave ); + waveArray.map( ( wave ) => { + if ( .3 >= wave.dataset.height ) { + wave.classList.add( 'good' ); + } else if ( 1 <= wave.dataset.height ) { + wave.classList.add( 'bad' ); + } + } ); + } ); + +}); diff --git a/weather-api.php b/weather-api.php new file mode 100644 index 0000000..524e56d --- /dev/null +++ b/weather-api.php @@ -0,0 +1,228 @@ + $station_url . '1731¶ms=WaterTemp,Hm0,Hmax,MeanDir180,MeanDir', + 'barcelona_port' => $station_url . '4752¶ms=AirTemp,WindSpeed,WindSpeedMax,WindDir180,WindDir', + ]; + + $mh = curl_multi_init(); + $requests = []; + + foreach ( $urls as $location => $url ) { + $ch = curl_init(); + curl_setopt( $ch, CURLOPT_URL, $url ); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); + $requests[$location] = $ch; + curl_multi_add_handle( $mh, $ch ); + } + + $active = null; + do { + $status = curl_multi_exec( $mh, $active ); + if ( $active ) { + curl_multi_select( $mh ); + } + } while ( $active && $status == CURLM_OK ); + + $responses = []; + foreach ( $requests as $location => $ch ) { + if ( curl_errno( $ch ) ) { + error_log( 'Spearfishing Weather cUrl error: ' . $location . ' - ' . curl_error( $ch ) ); + } else { + $responses[$location] = json_decode( curl_multi_getcontent( $ch ), true ); + } + + curl_multi_remove_handle( $mh, $ch ); + curl_close( $ch ); + } + + curl_multi_close( $mh ); + + $current_weather_data = []; + foreach ( $responses as $location => $response ) { + $latest_data = end( $response['content'][1] ); + + switch ( $location ) { + case 'barcelona_buoy' : + $current_weather_data['wave_temperature'] = round( $latest_data[1][0], 1 ); + $current_weather_data['wave_height'] = round( $latest_data[2][0], 1 ); + $current_weather_data['wave_height_max'] = round( $latest_data[3][0], 1 ); + $current_weather_data['wave_direction_to_degree'] = $latest_data[4][0] ?? null; + $current_weather_data['wave_direction_from_degree'] = $latest_data[5][0] ?? null; + $current_weather_data['wave_direction'] = degree_to_direction( $latest_data[5][0] ?? null ); + break; + + case 'barcelona_port' : + $current_weather_data['wind_temperature'] = round( $latest_data[1][0], 1 ); + $current_weather_data['wind_speed'] = round( $latest_data[2][0] * 2.237, 1 ); // M/S converted to MPH + $current_weather_data['wind_speed_max'] = round( $latest_data[3][0] * 2.237, 1 ); // M/S converted to MPH + $current_weather_data['wind_direction_to_degree'] = $latest_data[4][0] ?? null; + $current_weather_data['wind_direction_from_degree'] = $latest_data[5][0] ?? null; + $current_weather_data['wind_direction'] = degree_to_direction( $latest_data[5][0] ?? null ); + break; + } + } + + return $current_weather_data; +} + +/** + * Gets the current weather data and caches it for 10 minutes. + * @return array The current weather data. + */ +function get_current_weather_data() { + $cache_key = 'spearfishing_current_weather_data'; + $cached_response = wp_cache_get( $cache_key ); + + if ( true ) { + $current_weather_data = fetch_current_weather_data(); + wp_cache_set( $cache_key, $current_weather_data, '', 600 ); + + return $current_weather_data; + } + + return $cached_response; +} + +function fetch_forecast_weather_data() { + $forecast_url = 'https://movil.puertos.es/simo/item/'; + $urls = [ + 'wave' => $forecast_url . 'Siwana/712018014/forecast_view?fields=Datetime,Hm0,MeanDir180,MeanDir', + 'wind' => $forecast_url . 'Atmosfera/712018014/forecast_view?fields=Datetime,WindSpeed,WindDir180,WindDir', + ]; + + $mh = curl_multi_init(); + $requests = []; + + foreach ( $urls as $type => $url ) { + $ch = curl_init(); + curl_setopt( $ch, CURLOPT_URL, $url ); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); + $requests[$type] = $ch; + curl_multi_add_handle( $mh, $ch ); + } + + $active = null; + do { + $status = curl_multi_exec( $mh, $active ); + if ( $active ) { + curl_multi_select( $mh ); + } + } while ( $active && $status == CURLM_OK ); + + $responses = []; + foreach ( $requests as $type => $ch ) { + if ( curl_errno( $ch ) ) { + error_log( 'Spearfishing Weather cUrl error: ' . $type . ' - ' . curl_error( $ch ) ); + } else { + $responses[$type] = json_decode( curl_multi_getcontent( $ch ), true ); + } + + curl_multi_remove_handle( $mh, $ch ); + curl_close( $ch ); + } + + curl_multi_close( $mh ); + + $forecast_weather_data = []; + $barcelona_timezone = new DateTimeZone( 'Europe/Madrid' ); + foreach ( $responses as $type => $response ) { + foreach ( $response['content'] as $data ) { + $timestamp = $data[0]; + $epoch_time = new DateTime( "@$timestamp", $barcelona_timezone ); + $hour = $epoch_time->format('H'); + $day = $epoch_time->format('j-n'); + + // Only get data for 6am to 9pm + if ( $hour < 7 || $hour > 20 ) { + continue; + } + + if ( ! isset( $forecast_weather_data[$day][$hour] ) ) { + $forecast_weather_data[$day][$hour] = []; + } + + switch ( $type ) { + case 'wind': + $forecast_weather_data[$day][$hour]['wind_speed'] = number_format( round( $data[1] * 2.237, 1), 1, '.', ''); // M/S converted to MPH + $forecast_weather_data[$day][$hour]['wind_direction_to_degree'] = $data[2]; + $forecast_weather_data[$day][$hour]['wind_direction_from_degree'] = $data[3]; + break; + + case 'wave': + $forecast_weather_data[$day][$hour]['wave_height'] = number_format( round( $data[1], 1 ), 1, '.', ''); + $forecast_weather_data[$day][$hour]['wave_direction_to_degree'] = $data[2]; + $forecast_weather_data[$day][$hour]['wave_direction_from_degree'] = $data[3]; + break; + } + } + } + + $last_day = end( $forecast_weather_data ); + if ( ! isset( $last_day[07]['wind_speed'] ) ) { + $forecast_weather_data = array_slice( $forecast_weather_data, 0, -1 ); + } + + $filtered_forecast_weather_data = calculate_hour_rating($forecast_weather_data); + + return $filtered_forecast_weather_data; +} + +function calculate_hour_rating( $data ) { + return array_map( + fn( $hours ) => array_map( + fn( $hour_data ) => array_merge( $hour_data, ['rating' => $hour_data['wind_speed'] + $hour_data['wave_height'] * 10]), + $hours + ), + $data + ); +} + +/** + * Gets the forecast weather data and caches it for one hour. + * @return array The forecast weather data. + */ +function get_forecast_weather_data() { + $cache_key = 'spearfishing_forecast_weather_data'; + $cached_response = wp_cache_get( $cache_key ); + + if ( true ) { + $forecast_weather_data = fetch_forecast_weather_data(); + wp_cache_set( $cache_key, $forecast_weather_data, '', HOUR_IN_SECONDS ); + + return $forecast_weather_data; + } + + return $cached_response; +} \ No newline at end of file diff --git a/weather-block-render.php b/weather-block-render.php index 409e7d4..980ffd7 100644 --- a/weather-block-render.php +++ b/weather-block-render.php @@ -171,47 +171,82 @@ function convert_from_degree($degree) { * Renders the weather block. * @return string The rendered block. */ -function render_weather_block() { - $wind_data = get_wind_data(); - $water_data = get_water_data(); - - return << -
-
-

Current Weather Conditions

+function render_weather_block( $block_attributes, $content ) { + require_once __DIR__ . '/weather-api.php'; + + $weather_data = 'forecast' === $block_attributes['time'] ? get_forecast_weather_data() : get_current_weather_data(); + + if ( $block_attributes['time'] === 'forecast' ) { + $output = ''; + + if ( count( $weather_data ) === 4 ) { + $first_day = array_shift( $weather_data ); + } + + foreach ( $weather_data as $day => $data ) { + $output .= '

' . htmlspecialchars($day) . '

'; + + $day = array_reduce(array_keys($data), function($carry, $key) use ($data) { + $time = $data[$key]; + + $carry .= << + {$key} +
+

{$time['wind_speed']}mph

+ + + +
+
+

{$time['wave_height']}m

+ + + +
+
+ HTML; + return $carry; + }, ''); + + $output .= $day . '
'; + } + + return << +
+ $output
-
-
-
+ HTML; + } + + if ( $block_attributes['time'] === 'current' ) { + return << +
+
+ -

{$wind_data['direction']} | {$wind_data['direction_from_degree']}°

+

{$weather_data['wind_direction']}

Wind

+

{$weather_data['wind_speed']} | {$weather_data['wind_speed_max']}mph

+

{$weather_data['wind_temperature']}°celsius

+
-
- {$wind_data['speed']} | {$wind_data['speed_max']} -

knots

- {$wind_data['temperature']}° -

celsius

-
-
-
-
-
+ -

{$water_data['direction']} | {$water_data['direction_from_degree']}°

-

Water

-
-
- {$water_data['height']} | {$water_data['height_max']} -

meters

- {$water_data['temperature']}° -

celsius

+

{$weather_data['wave_direction']}

+

Waves

+

{$weather_data['wave_height']} | {$weather_data['wave_height_max']}meters

+

{$weather_data['wave_temperature']}°celsius

-
- HTML; -} \ No newline at end of file + HTML; + } + +}