Solar: move the data attribution to the bottom of the page

The full NASA POWER acknowledgment, license and changes now sit in a small
"Data attribution" section at the end of the calculator, always visible,
instead of behind a link under the location.  The source line under the
location points to it.  Bumps wrolpi-help to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Roland 2026-10-04 18:22:52 -06:00
parent 3bfd198fa0
commit a51b15bf4f
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
3 changed files with 25 additions and 20 deletions

View file

@ -2,7 +2,6 @@ import React from "react";
import {useSearchParams} from "react-router";
import {
Accordion,
Anchor,
Button,
Grid,
Group,
@ -199,6 +198,17 @@ const compassLabel = azimuth => {
// Small explanatory text at the top of a collapsed section.
const Hint = ({children}) => <p style={{fontSize: '0.9em', opacity: 0.75, marginTop: 0}}>{children}</p>;
// NASA POWER's requested acknowledgment, the license, and how WROLPi changed the data (CC BY 4.0).
function DataAttribution() {
return <section aria-labelledby='solar-data-attribution'
style={{marginTop: '2em', fontSize: '0.85em', opacity: 0.75}}>
<Header as='h4' id='solar-data-attribution'>Data attribution</Header>
<p>{CLIMATOLOGY_ACKNOWLEDGMENT} {CLIMATOLOGY_DATA_REFERENCE}</p>
<p>License: {CLIMATOLOGY_LICENSE}</p>
<p>{CLIMATOLOGY_CHANGES}</p>
</section>;
}
const ESTIMATE_INFO = 'A typical-year estimate from monthly averages. Real years vary; expect roughly ±10–15% '
+ 'over a year and more in any single month. Shading from trees and buildings is not modeled beyond the '
+ 'Shading loss.';
@ -216,7 +226,6 @@ const GEOLOCATION_ERRORS = {
function LocationSection({get, set, s, climatology}) {
const [locating, setLocating] = React.useState(false);
const [showAttribution, setShowAttribution] = React.useState(false);
const [geoError, setGeoError] = React.useState(null);
const latError = get('lat') && s.latitude === null ? 'Latitude must be between -90 and 90' : null;
const lonError = get('lon') && s.longitude === null ? 'Longitude must be between -180 and 180' : null;
@ -248,17 +257,7 @@ function LocationSection({get, set, s, climatology}) {
Enter your own monthly sunlight under "Use my own sunlight data" below.
</Message>;
} else {
source = <>
Sunlight and temperature: {CLIMATOLOGY_SOURCE}.{' '}
<Anchor component='button' type='button' size='sm' onClick={() => setShowAttribution(!showAttribution)}>
Data attribution
</Anchor>
{showAttribution && <div style={{marginTop: '0.5em'}}>
<p>{CLIMATOLOGY_ACKNOWLEDGMENT} {CLIMATOLOGY_DATA_REFERENCE}</p>
<p>License: {CLIMATOLOGY_LICENSE}</p>
<p>{CLIMATOLOGY_CHANGES}</p>
</div>}
</>;
source = `Sunlight and temperature: ${CLIMATOLOGY_SOURCE}; see Data attribution at the bottom of the page.`;
}
return <>
@ -622,5 +621,7 @@ export function SolarCalculator() {
<Accordion.Panel><OwnDataSection set={set} s={s} nasa={nasa}/></Accordion.Panel>
</Accordion.Item>
</Accordion>
<DataAttribution/>
</div>;
}

View file

@ -122,14 +122,18 @@ describe('SolarCalculator', () => {
expect(global.fetch).toHaveBeenCalledWith(expect.stringMatching(/\/data\/solar-climatology\.bin$/));
});
test('the data attribution expands', async () => {
test('the data attribution is at the bottom of the page', async () => {
renderWithProviders(<SolarCalculator/>, {route: DENVER});
await findEstimate();
expect(screen.queryByText(/Prediction Of Worldwide Energy Resources/)).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', {name: 'Data attribution'}));
expect(screen.getByText(/Prediction Of Worldwide Energy Resources/)).toBeInTheDocument();
expect(screen.getByText(/WROLPi modified the data/)).toBeInTheDocument();
expect(screen.getByText(/CC BY 4\.0/, {selector: 'p'})).toBeInTheDocument();
const heading = screen.getByRole('heading', {name: 'Data attribution'});
const section = screen.getByRole('region', {name: 'Data attribution'});
expect(within(section).getByText(/Prediction Of Worldwide Energy Resources/)).toBeInTheDocument();
expect(within(section).getByText(/WROLPi modified the data/)).toBeInTheDocument();
expect(within(section).getByText(/CC BY 4\.0/)).toBeInTheDocument();
// After everything else, including the last Fine-tune section.
const lastControl = screen.getByText('Use my own sunlight data: off');
expect(lastControl.compareDocumentPosition(heading) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(screen.getByText(/see Data attribution at the bottom of the page/)).toBeInTheDocument();
});
test('typing writes the URL and leaves defaults out', async () => {

@ -1 +1 @@
Subproject commit 241ba0032e3b571f1919b2b4f1905449fbd567ed
Subproject commit e118fe46c94a0f5f371b009ccc9d7fd14ca979df