From 53dc32f8fce8a22df75501f4f17fb9a80cd7034f Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 5 Jul 2025 09:22:47 -0500 Subject: [PATCH] use daisyui components --- assets/js/app.js | 696 +++++++++++++----- lib/aprsme_web/components/core_components.ex | 115 ++- lib/aprsme_web/components/layouts.ex | 2 +- .../components/layouts/app.html.heex | 2 +- .../components/layouts/root.html.heex | 2 +- lib/aprsme_web/live/about_live.html.heex | 102 +-- lib/aprsme_web/live/api_docs_live.ex | 527 ++++++------- lib/aprsme_web/live/info_live/show.html.heex | 118 ++- .../live/packets_live/callsign_view.html.heex | 198 ++--- .../live/packets_live/index.html.heex | 276 +++---- .../live/weather_live/callsign_view.html.heex | 113 ++- 11 files changed, 1230 insertions(+), 921 deletions(-) diff --git a/assets/js/app.js b/assets/js/app.js index 0da43b6..f1152c4 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -76,9 +76,41 @@ Hooks.APRSMap = MapAPRSMap; Hooks.ResponsiveSlideoverHook = ResponsiveSlideoverHook; Hooks.ChartJSTempChart = { - mounted() { this.renderChart(); }, + mounted() { + console.log('Temperature chart mounted'); + + // Initialize global chart instances map if it doesn't exist + if (!window.chartInstances) { + window.chartInstances = new Map(); + } + + // Register this chart instance + window.chartInstances.set(this.el.id, this); + + this.renderChart(); + // Listen for theme changes + this.themeChangeHandler = () => { + console.log('Temperature chart received themeChanged event'); + if (this.chart) { + console.log('Re-rendering temperature chart'); + this.renderChart(); + } + }; + window.addEventListener('themeChanged', this.themeChangeHandler); + }, updated() { this.renderChart(); }, + destroyed() { + // Clean up event listener + if (this.themeChangeHandler) { + window.removeEventListener('themeChanged', this.themeChangeHandler); + } + // Remove from global chart instances + if (window.chartInstances) { + window.chartInstances.delete(this.el.id); + } + }, renderChart() { + console.log('Temperature chart renderChart called'); if (this.chart) { this.chart.destroy(); } @@ -87,6 +119,8 @@ Hooks.ChartJSTempChart = { const times = data.map(d => new Date(d.timestamp)); const temps = data.map(d => d.temperature); const dews = data.map(d => d.dew_point); + const colors = getThemeColors(); + console.log('Temperature chart colors:', colors); const canvas = this.el.querySelector('canvas'); if (!canvas) { @@ -121,7 +155,13 @@ Hooks.ChartJSTempChart = { plugins: { title: { display: true, - text: 'Temperature & Dew Point (°F)' + text: 'Temperature & Dew Point (°F)', + color: colors.text + }, + legend: { + labels: { + color: colors.text + } } }, scales: { @@ -135,13 +175,27 @@ Hooks.ChartJSTempChart = { }, title: { display: true, - text: 'Time' + text: 'Time', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid } }, y: { title: { display: true, - text: '°F' + text: '°F', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid } } } @@ -151,16 +205,50 @@ Hooks.ChartJSTempChart = { }; Hooks.ChartJSHumidityChart = { - mounted() { this.renderChart(); }, + mounted() { + console.log('Humidity chart mounted'); + + // Initialize global chart instances map if it doesn't exist + if (!window.chartInstances) { + window.chartInstances = new Map(); + } + + // Register this chart instance + window.chartInstances.set(this.el.id, this); + + this.renderChart(); + // Listen for theme changes + this.themeChangeHandler = () => { + console.log('Humidity chart received themeChanged event'); + if (this.chart) { + console.log('Re-rendering humidity chart'); + this.renderChart(); + } + }; + window.addEventListener('themeChanged', this.themeChangeHandler); + }, updated() { this.renderChart(); }, + destroyed() { + // Clean up event listener + if (this.themeChangeHandler) { + window.removeEventListener('themeChanged', this.themeChangeHandler); + } + // Remove from global chart instances + if (window.chartInstances) { + window.chartInstances.delete(this.el.id); + } + }, renderChart() { + console.log('Humidity chart renderChart called'); if (this.chart) { this.chart.destroy(); } const data = JSON.parse(this.el.dataset.weatherHistory); const times = data.map(d => new Date(d.timestamp)); - const hums = data.map(d => d.humidity); + const humidity = data.map(d => d.humidity); + const colors = getThemeColors(); + console.log('Humidity chart colors:', colors); const canvas = this.el.querySelector('canvas'); if (!canvas) { @@ -174,9 +262,9 @@ Hooks.ChartJSHumidityChart = { labels: times, datasets: [{ label: 'Humidity (%)', - data: hums, - borderColor: 'blue', - backgroundColor: 'rgba(0, 0, 255, 0.1)', + data: humidity, + borderColor: 'green', + backgroundColor: 'rgba(0, 255, 0, 0.1)', tension: 0.1 }] }, @@ -186,7 +274,13 @@ Hooks.ChartJSHumidityChart = { plugins: { title: { display: true, - text: 'Humidity (%)' + text: 'Humidity (%)', + color: colors.text + }, + legend: { + labels: { + color: colors.text + } } }, scales: { @@ -200,13 +294,27 @@ Hooks.ChartJSHumidityChart = { }, title: { display: true, - text: 'Time' + text: 'Time', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid } }, y: { title: { display: true, - text: '%' + text: '%', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid } } } @@ -216,16 +324,50 @@ Hooks.ChartJSHumidityChart = { }; Hooks.ChartJSPressureChart = { - mounted() { this.renderChart(); }, + mounted() { + console.log('Pressure chart mounted'); + + // Initialize global chart instances map if it doesn't exist + if (!window.chartInstances) { + window.chartInstances = new Map(); + } + + // Register this chart instance + window.chartInstances.set(this.el.id, this); + + this.renderChart(); + // Listen for theme changes + this.themeChangeHandler = () => { + console.log('Pressure chart received themeChanged event'); + if (this.chart) { + console.log('Re-rendering pressure chart'); + this.renderChart(); + } + }; + window.addEventListener('themeChanged', this.themeChangeHandler); + }, updated() { this.renderChart(); }, + destroyed() { + // Clean up event listener + if (this.themeChangeHandler) { + window.removeEventListener('themeChanged', this.themeChangeHandler); + } + // Remove from global chart instances + if (window.chartInstances) { + window.chartInstances.delete(this.el.id); + } + }, renderChart() { + console.log('Pressure chart renderChart called'); if (this.chart) { this.chart.destroy(); } const data = JSON.parse(this.el.dataset.weatherHistory); const times = data.map(d => new Date(d.timestamp)); - const pressures = data.map(d => d.pressure); + const pressure = data.map(d => d.pressure); + const colors = getThemeColors(); + console.log('Pressure chart colors:', colors); const canvas = this.el.querySelector('canvas'); if (!canvas) { @@ -238,138 +380,8 @@ Hooks.ChartJSPressureChart = { data: { labels: times, datasets: [{ - label: 'Pressure (hPa)', - data: pressures, - borderColor: 'green', - backgroundColor: 'rgba(0, 128, 0, 0.1)', - tension: 0.1 - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - title: { - display: true, - text: 'Pressure (hPa)' - } - }, - scales: { - x: { - type: 'time', - time: { - displayFormats: { - hour: 'HH:mm', - minute: 'HH:mm' - } - }, - title: { - display: true, - text: 'Time' - } - }, - y: { - title: { - display: true, - text: 'hPa' - } - } - } - } - }); - } -}; - -Hooks.ChartJSWindDirectionChart = { - mounted() { this.renderChart(); }, - updated() { this.renderChart(); }, - renderChart() { - if (this.chart) { - this.chart.destroy(); - } - - const data = JSON.parse(this.el.dataset.weatherHistory); - const times = data.map(d => new Date(d.timestamp)); - const dirs = data.map(d => d.wind_direction); - - const canvas = this.el.querySelector('canvas'); - if (!canvas) { - console.error('Canvas element not found for wind direction chart'); - return; - } - - this.chart = new Chart(canvas, { - type: 'line', - data: { - labels: times, - datasets: [{ - label: 'Wind Direction (°)', - data: dirs, - borderColor: 'orange', - backgroundColor: 'rgba(255, 165, 0, 0.1)', - tension: 0.1 - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - title: { - display: true, - text: 'Wind Direction (°)' - } - }, - scales: { - x: { - type: 'time', - time: { - displayFormats: { - hour: 'HH:mm', - minute: 'HH:mm' - } - }, - title: { - display: true, - text: 'Time' - } - }, - y: { - title: { - display: true, - text: '°' - } - } - } - } - }); - } -}; - -Hooks.ChartJSWindSpeedChart = { - mounted() { this.renderChart(); }, - updated() { this.renderChart(); }, - renderChart() { - if (this.chart) { - this.chart.destroy(); - } - - const data = JSON.parse(this.el.dataset.weatherHistory); - const times = data.map(d => new Date(d.timestamp)); - const speeds = data.map(d => d.wind_speed); - - const canvas = this.el.querySelector('canvas'); - if (!canvas) { - console.error('Canvas element not found for wind speed chart'); - return; - } - - this.chart = new Chart(canvas, { - type: 'line', - data: { - labels: times, - datasets: [{ - label: 'Wind Speed (mph)', - data: speeds, + label: 'Pressure (mb)', + data: pressure, borderColor: 'purple', backgroundColor: 'rgba(128, 0, 128, 0.1)', tension: 0.1 @@ -381,7 +393,13 @@ Hooks.ChartJSWindSpeedChart = { plugins: { title: { display: true, - text: 'Wind Speed (mph)' + text: 'Pressure (mb)', + color: colors.text + }, + legend: { + labels: { + color: colors.text + } } }, scales: { @@ -395,13 +413,156 @@ Hooks.ChartJSWindSpeedChart = { }, title: { display: true, - text: 'Time' + text: 'Time', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid } }, y: { title: { display: true, - text: 'mph' + text: 'mb', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid + } + } + } + } + }); + } +}; + +Hooks.ChartJSWindChart = { + mounted() { + console.log('Wind chart mounted'); + + // Initialize global chart instances map if it doesn't exist + if (!window.chartInstances) { + window.chartInstances = new Map(); + } + + // Register this chart instance + window.chartInstances.set(this.el.id, this); + + this.renderChart(); + // Listen for theme changes + this.themeChangeHandler = () => { + console.log('Wind chart received themeChanged event'); + if (this.chart) { + console.log('Re-rendering wind chart'); + this.renderChart(); + } + }; + window.addEventListener('themeChanged', this.themeChangeHandler); + }, + updated() { this.renderChart(); }, + destroyed() { + // Clean up event listener + if (this.themeChangeHandler) { + window.removeEventListener('themeChanged', this.themeChangeHandler); + } + // Remove from global chart instances + if (window.chartInstances) { + window.chartInstances.delete(this.el.id); + } + }, + renderChart() { + console.log('Wind chart renderChart called'); + if (this.chart) { + this.chart.destroy(); + } + + const data = JSON.parse(this.el.dataset.weatherHistory); + const times = data.map(d => new Date(d.timestamp)); + const windSpeed = data.map(d => d.wind_speed); + const windGust = data.map(d => d.wind_gust); + const colors = getThemeColors(); + console.log('Wind chart colors:', colors); + + const canvas = this.el.querySelector('canvas'); + if (!canvas) { + console.error('Canvas element not found for wind chart'); + return; + } + + this.chart = new Chart(canvas, { + type: 'line', + data: { + labels: times, + datasets: [ + { + label: 'Wind Speed (mph)', + data: windSpeed, + borderColor: 'orange', + backgroundColor: 'rgba(255, 165, 0, 0.1)', + tension: 0.1 + }, + { + label: 'Wind Gust (mph)', + data: windGust, + borderColor: 'brown', + backgroundColor: 'rgba(165, 42, 42, 0.1)', + tension: 0.1 + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: 'Wind Speed & Gust (mph)', + color: colors.text + }, + legend: { + labels: { + color: colors.text + } + } + }, + scales: { + x: { + type: 'time', + time: { + displayFormats: { + hour: 'HH:mm', + minute: 'HH:mm' + } + }, + title: { + display: true, + text: 'Time', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid + } + }, + y: { + title: { + display: true, + text: 'mph', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid } } } @@ -411,9 +572,41 @@ Hooks.ChartJSWindSpeedChart = { }; Hooks.ChartJSRainChart = { - mounted() { this.renderChart(); }, + mounted() { + console.log('Rain chart mounted'); + + // Initialize global chart instances map if it doesn't exist + if (!window.chartInstances) { + window.chartInstances = new Map(); + } + + // Register this chart instance + window.chartInstances.set(this.el.id, this); + + this.renderChart(); + // Listen for theme changes + this.themeChangeHandler = () => { + console.log('Rain chart received themeChanged event'); + if (this.chart) { + console.log('Re-rendering rain chart'); + this.renderChart(); + } + }; + window.addEventListener('themeChanged', this.themeChangeHandler); + }, updated() { this.renderChart(); }, + destroyed() { + // Clean up event listener + if (this.themeChangeHandler) { + window.removeEventListener('themeChanged', this.themeChangeHandler); + } + // Remove from global chart instances + if (window.chartInstances) { + window.chartInstances.delete(this.el.id); + } + }, renderChart() { + console.log('Rain chart renderChart called'); if (this.chart) { this.chart.destroy(); } @@ -422,7 +615,9 @@ Hooks.ChartJSRainChart = { const times = data.map(d => new Date(d.timestamp)); const rain1h = data.map(d => d.rain_1h); const rain24h = data.map(d => d.rain_24h); - const rainMid = data.map(d => d.rain_since_midnight); + const rainSinceMidnight = data.map(d => d.rain_since_midnight); + const colors = getThemeColors(); + console.log('Rain chart colors:', colors); const canvas = this.el.querySelector('canvas'); if (!canvas) { @@ -445,15 +640,15 @@ Hooks.ChartJSRainChart = { { label: 'Rain (24h)', data: rain24h, - borderColor: 'navy', - backgroundColor: 'rgba(0, 0, 128, 0.1)', + borderColor: 'cyan', + backgroundColor: 'rgba(0, 255, 255, 0.1)', tension: 0.1 }, { label: 'Rain (since midnight)', - data: rainMid, - borderColor: 'teal', - backgroundColor: 'rgba(0, 128, 128, 0.1)', + data: rainSinceMidnight, + borderColor: 'navy', + backgroundColor: 'rgba(0, 0, 128, 0.1)', tension: 0.1 } ] @@ -464,7 +659,13 @@ Hooks.ChartJSRainChart = { plugins: { title: { display: true, - text: 'Rain (inches)' + text: 'Rainfall (inches)', + color: colors.text + }, + legend: { + labels: { + color: colors.text + } } }, scales: { @@ -478,13 +679,27 @@ Hooks.ChartJSRainChart = { }, title: { display: true, - text: 'Time' + text: 'Time', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid } }, y: { title: { display: true, - text: 'in' + text: 'inches', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid } } } @@ -494,16 +709,50 @@ Hooks.ChartJSRainChart = { }; Hooks.ChartJSLuminosityChart = { - mounted() { this.renderChart(); }, + mounted() { + console.log('Luminosity chart mounted'); + + // Initialize global chart instances map if it doesn't exist + if (!window.chartInstances) { + window.chartInstances = new Map(); + } + + // Register this chart instance + window.chartInstances.set(this.el.id, this); + + this.renderChart(); + // Listen for theme changes + this.themeChangeHandler = () => { + console.log('Luminosity chart received themeChanged event'); + if (this.chart) { + console.log('Re-rendering luminosity chart'); + this.renderChart(); + } + }; + window.addEventListener('themeChanged', this.themeChangeHandler); + }, updated() { this.renderChart(); }, + destroyed() { + // Clean up event listener + if (this.themeChangeHandler) { + window.removeEventListener('themeChanged', this.themeChangeHandler); + } + // Remove from global chart instances + if (window.chartInstances) { + window.chartInstances.delete(this.el.id); + } + }, renderChart() { + console.log('Luminosity chart renderChart called'); if (this.chart) { this.chart.destroy(); } const data = JSON.parse(this.el.dataset.weatherHistory); const times = data.map(d => new Date(d.timestamp)); - const lum = data.map(d => d.luminosity); + const luminosity = data.map(d => d.luminosity); + const colors = getThemeColors(); + console.log('Luminosity chart colors:', colors); const canvas = this.el.querySelector('canvas'); if (!canvas) { @@ -517,9 +766,9 @@ Hooks.ChartJSLuminosityChart = { labels: times, datasets: [{ label: 'Luminosity', - data: lum, - borderColor: 'gold', - backgroundColor: 'rgba(255, 215, 0, 0.1)', + data: luminosity, + borderColor: 'yellow', + backgroundColor: 'rgba(255, 255, 0, 0.1)', tension: 0.1 }] }, @@ -529,7 +778,13 @@ Hooks.ChartJSLuminosityChart = { plugins: { title: { display: true, - text: 'Luminosity' + text: 'Luminosity', + color: colors.text + }, + legend: { + labels: { + color: colors.text + } } }, scales: { @@ -543,13 +798,27 @@ Hooks.ChartJSLuminosityChart = { }, title: { display: true, - text: 'Time' + text: 'Time', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid } }, y: { title: { display: true, - text: '' + text: 'Luminosity', + color: colors.text + }, + ticks: { + color: colors.text + }, + grid: { + color: colors.grid } } } @@ -558,6 +827,99 @@ Hooks.ChartJSLuminosityChart = { } }; +// Helper function to get theme-aware colors +const getThemeColors = () => { + const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; + return { + text: isDark ? '#e5e7eb' : '#111827', + grid: isDark ? '#374151' : '#9ca3af', + background: isDark ? 'rgba(0, 0, 0, 0.1)' : 'rgba(255, 255, 255, 0.1)' + }; +}; + +// Theme switching functionality +const theme = (() => { + if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) { + return localStorage.getItem('theme'); + } + return 'auto'; +})(); + +const applyTheme = (theme) => { + const element = document.documentElement; + + if (theme === 'auto') { + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + element.setAttribute('data-theme', 'dark'); + } else { + element.setAttribute('data-theme', 'light'); + } + } else { + element.setAttribute('data-theme', theme); + } +}; + +// Apply initial theme +applyTheme(theme); +window.localStorage.setItem('theme', theme); + +// Global function to re-render all charts +window.reRenderAllCharts = () => { + console.log('Re-rendering all charts...'); + + // Store all chart instances globally so we can access them + if (!window.chartInstances) { + window.chartInstances = new Map(); + } + + // Re-render all stored chart instances + window.chartInstances.forEach((chartInstance, elementId) => { + console.log('Re-rendering chart:', elementId); + if (chartInstance && typeof chartInstance.renderChart === 'function') { + chartInstance.renderChart(); + } + }); + + // Also dispatch a custom event that charts can listen to + console.log('Dispatching themeChanged event'); + window.dispatchEvent(new CustomEvent('themeChanged')); +}; + +const handleThemeClick = (selectedTheme) => { + console.log('Theme changed to:', selectedTheme); + applyTheme(selectedTheme); + localStorage.setItem('theme', selectedTheme); + + // Re-render all charts with new theme colors + setTimeout(() => { + console.log('Calling reRenderAllCharts after theme change'); + window.reRenderAllCharts(); + }, 100); +}; + +// Listen for system theme changes when auto is selected +window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { + if (localStorage.getItem('theme') === 'auto') { + applyTheme('auto'); + + // Re-render all charts with new theme colors + setTimeout(() => { + window.reRenderAllCharts(); + }, 100); + } +}); + +// Add event listeners for theme switching +document.addEventListener('DOMContentLoaded', () => { + const themeButtons = document.querySelectorAll('[data-set-theme]'); + themeButtons.forEach(button => { + button.addEventListener('click', () => { + const theme = button.getAttribute('data-set-theme'); + handleThemeClick(theme); + }); + }); +}); + let liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, params: { _csrf_token: csrfToken }, diff --git a/lib/aprsme_web/components/core_components.ex b/lib/aprsme_web/components/core_components.ex index f05b348..fa93051 100644 --- a/lib/aprsme_web/components/core_components.ex +++ b/lib/aprsme_web/components/core_components.ex @@ -444,19 +444,86 @@ defmodule AprsmeWeb.CoreComponents do def header(assigns) do ~H""" -
- -
+ + + + """ + end + + @doc """ + Renders a theme selector dropdown. + """ + def theme_selector(assigns) do + ~H""" + """ end @@ -682,19 +749,17 @@ defmodule AprsmeWeb.CoreComponents do def navigation(assigns) do ~H""" - + <%= if @variant == :horizontal do %> + + <% else %> +
  • Home
  • +
  • API
  • +
  • About
  • + <% end %> """ end end diff --git a/lib/aprsme_web/components/layouts.ex b/lib/aprsme_web/components/layouts.ex index 0ba89ab..ba8dd5f 100644 --- a/lib/aprsme_web/components/layouts.ex +++ b/lib/aprsme_web/components/layouts.ex @@ -5,6 +5,6 @@ defmodule AprsmeWeb.Layouts do embed_templates "layouts/*" def body_class(_assigns) do - ["bg-white antialiased"] + ["bg-base-100 antialiased"] end end diff --git a/lib/aprsme_web/components/layouts/app.html.heex b/lib/aprsme_web/components/layouts/app.html.heex index 7993974..908b4ec 100644 --- a/lib/aprsme_web/components/layouts/app.html.heex +++ b/lib/aprsme_web/components/layouts/app.html.heex @@ -1,5 +1,5 @@ <.header /> -
    +
    <.flash kind={:info} title="Success!" flash={@flash} /> <.flash kind={:error} title="Error!" flash={@flash} /> diff --git a/lib/aprsme_web/components/layouts/root.html.heex b/lib/aprsme_web/components/layouts/root.html.heex index 1fc305a..c24820b 100644 --- a/lib/aprsme_web/components/layouts/root.html.heex +++ b/lib/aprsme_web/components/layouts/root.html.heex @@ -1,5 +1,5 @@ - + diff --git a/lib/aprsme_web/live/about_live.html.heex b/lib/aprsme_web/live/about_live.html.heex index 9e0f7b1..a023740 100644 --- a/lib/aprsme_web/live/about_live.html.heex +++ b/lib/aprsme_web/live/about_live.html.heex @@ -1,69 +1,45 @@ -
    -
    - - -
    -
    -

    Under heavy construction

    -

    - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. -

    - -

    - Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. -

    - -
    -

    Built with Modern Technologies

    -
    - - Elixir - - - Phoenix - - - LiveView - - - PostgreSQL - - - Tailwind CSS - -
    -
    - -

    Join Our Community

    -

    - Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. -

    - -
    - - - - - - Explore the Map - +
    +
    +
    +

    Under heavy construction

    +

    + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. +

    +

    + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. +

    +
    +

    Built with Modern Technologies

    +
    + Elixir + Phoenix + LiveView + PostgreSQL + Tailwind CSS
    -
    - - -
    -

    - © 2025 aprs.me. Lorem ipsum dolor sit amet, consectetur adipiscing elit. +

    Join Our Community

    +

    + Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus.

    +
    +
    +

    + © 2025 aprs.me. Lorem ipsum dolor sit amet, consectetur adipiscing elit. +

    +
    diff --git a/lib/aprsme_web/live/api_docs_live.ex b/lib/aprsme_web/live/api_docs_live.ex index 59c93f3..895e451 100644 --- a/lib/aprsme_web/live/api_docs_live.ex +++ b/lib/aprsme_web/live/api_docs_live.ex @@ -213,48 +213,58 @@ defmodule AprsmeWeb.ApiDocsLive do @impl true def render(assigns) do ~H""" -
    +
    -

    APRS.me API Documentation

    -

    +

    APRS.me API Documentation

    +

    RESTful JSON API for accessing APRS packet data and station information.

    -
    -
    -

    Overview

    -
    -
    -
    -

    - The APRS.me API provides programmatic access to Amateur Radio APRS (Automatic Packet Reporting System) - data. All API endpoints return JSON data and follow RESTful conventions. -

    +
    +
    +

    Overview

    +

    + The APRS.me API provides programmatic access to Amateur Radio APRS (Automatic Packet Reporting System) + data. All API endpoints return JSON data and follow RESTful conventions. +

    -
    -
    -

    Base URL

    - - https://aprs.me/api/v1 - -
    - -
    -

    Content Type

    - - application/json - -
    +
    +
    +

    Base URL

    + + https://aprs.me/api/v1 +
    -
    -

    Rate Limiting

    -

    - Currently no rate limiting is enforced, but please be respectful and avoid excessive requests. - Rate limiting may be implemented in the future. -

    +
    +

    Content Type

    + + application/json + +
    +
    + +
    + + + +
    +

    Rate Limiting

    +
    + Currently no rate limiting is enforced, but please be respectful and avoid excessive requests. Rate limiting may be implemented in the future. +
    @@ -263,66 +273,46 @@ defmodule AprsmeWeb.ApiDocsLive do
    -
    -
    -
    -

    Get Latest Packet by Callsign

    - - GET - +
    +
    +
    +

    Get Latest Packet by Callsign

    + GET
    -
    -
    -

    Endpoint

    -
    +

    Endpoint

    +
    GET /api/v1/callsign/{"{callsign}"}
    -

    Description

    -

    +

    Description

    +

    Retrieves the most recent APRS packet for the specified callsign. The callsign can include an SSID (e.g., N0CALL-9) or just the base callsign (e.g., N0CALL).

    -

    Parameters

    +

    Parameters

    - - +
    + - - - - + + + + - + - - - - + + + +
    - Parameter - - Type - - Required - - Description - ParameterTypeRequiredDescription
    - callsign - - string - - Yes - - Amateur radio callsign with optional SSID (e.g., N0CALL or N0CALL-9) - callsignstringYesAmateur radio callsign with optional SSID (e.g., N0CALL or N0CALL-9)
    @@ -330,18 +320,18 @@ defmodule AprsmeWeb.ApiDocsLive do
    -

    Example Request

    -
    +

    Example Request

    +
    curl -X GET "https://aprs.me/api/v1/callsign/N0CALL-9" \
          -H "Accept: application/json"
    -

    Response Format

    +

    Response Format

    -

    Success Response (200 OK)

    -
    +

    Success Response (200 OK)

    +
    <%= raw ~s|{
         "data": {
         "id": 12345,
    @@ -383,16 +373,16 @@ defmodule AprsmeWeb.ApiDocsLive do
         }| %>
    -

    Not Found Response (404)

    -
    +

    Not Found Response (404)

    +
    <%= raw ~s|{
         "data": null,
         "message": "No packets found for callsign N0CALL-9"
         }| %>
    -

    Error Response (400)

    -
    +

    Error Response (400)

    +
    <%= raw ~s|{
         "error": {
         "message": "Invalid callsign format",
    @@ -405,118 +395,73 @@ defmodule AprsmeWeb.ApiDocsLive do
             
    -
    -
    -

    Response Fields

    -
    - -
    +
    +
    +

    Response Fields

    - - +
    + - - - + + + - + - - - - - - - - + + + - - - - - - - - + + + - - - - - - - - + + + - - - - - - - - + + + - - - - - - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    - Field - - Type - - Description - FieldTypeDescription
    idintegerUnique packet identifier
    - callsign - string - Full callsign with SSID (e.g., "N0CALL-9") - idintegerUnique packet identifier
    - base_callsign - stringBase callsign without SSID
    ssidstring - SSID (Secondary Station Identifier), null if not present - callsignstringFull callsign with SSID (e.g., "N0CALL-9")
    - received_at - datetime - When the packet was received (ISO 8601 format) -
    - position - object - Position data (latitude, longitude, course, speed, altitude) - base_callsignstringBase callsign without SSID
    - symbol - object - APRS symbol information (code and table_id) -
    - weather - object - Weather data if present (temperature, humidity, wind, etc.) - ssidstringSSID (Secondary Station Identifier), null if not present
    - equipment - object - Equipment information (manufacturer, type) -
    - message - object - Message data if the packet is a message - received_atdatetimeWhen the packet was received (ISO 8601 format)
    - raw_packet - string - Original raw APRS packet as received - positionobjectPosition data (latitude, longitude, course, speed, altitude)
    symbolobjectAPRS symbol information (code and table_id)
    weatherobjectWeather data if present (temperature, humidity, wind, etc.)
    equipmentobjectEquipment information (manufacturer, type)
    messageobjectMessage data if the packet is a message
    raw_packetstringOriginal raw APRS packet as received
    @@ -525,62 +470,37 @@ defmodule AprsmeWeb.ApiDocsLive do
    -
    -
    -

    HTTP Status Codes

    -
    - -
    +
    +
    +

    HTTP Status Codes

    - - +
    + - - + + - + - - - - - - + + - - - - - - + + - - + + + + + + + + + +
    - Status Code - - Description - Status CodeDescription
    - 200 OK - - Request successful, packet data returned -
    - 400 Bad Request - - Invalid callsign format or malformed request - 200 OKRequest successful, packet data returned
    - 404 Not Found - - No packets found for the specified callsign -
    - 408 Request Timeout - Request took too long to process400 Bad RequestInvalid callsign format or malformed request
    - 500 Internal Server Error - - Server error occurred while processing the request - 404 Not FoundNo packets found for the specified callsign
    408 Request TimeoutRequest took too long to process
    500 Internal Server ErrorServer error occurred while processing the request
    @@ -589,68 +509,62 @@ defmodule AprsmeWeb.ApiDocsLive do
    -
    -
    -

    Planned Endpoints

    -
    - -
    -

    +

    +
    +

    Planned Endpoints

    +

    The following endpoints are planned for future releases:

    -
    +
    GET /api/v1/callsign/{"{callsign}"}/history -

    Get historical packets for a callsign

    +

    Get historical packets for a callsign

    - Planned + Planned
    -
    +
    GET /api/v1/packets/recent -

    Get recent packets with filtering options

    +

    Get recent packets with filtering options

    - Planned + Planned
    -
    +
    GET /api/v1/packets/area -

    Get packets within a geographic area

    +

    Get packets within a geographic area

    - Planned + Planned
    -
    +
    GET /api/v1/weather/{"{callsign}"} -

    Get weather data from weather stations

    +

    Get weather data from weather stations

    - Planned + Planned
    -
    -
    -

    Test the API

    -
    - -
    -

    +

    +
    +

    Test the API

    +

    Try the API directly from this page. Enter a callsign to see the most recent packet data.

    -