From e85ea317daf887050d010f2ca1b63ab22bd5b0c6 Mon Sep 17 00:00:00 2001 From: j08lue Date: Fri, 15 Mar 2024 14:05:44 +0100 Subject: [PATCH 01/89] Add ADR about design system change --- docs/adr/003-design-system-change.md | 61 ++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 docs/adr/003-design-system-change.md diff --git a/docs/adr/003-design-system-change.md b/docs/adr/003-design-system-change.md new file mode 100644 index 000000000..23d9ca22c --- /dev/null +++ b/docs/adr/003-design-system-change.md @@ -0,0 +1,61 @@ +# Change of Design system + +* Status: In Review, **In Progress**, Done +* Authors: @j08lue, @sandrahoang686, @faustoperez +* Deciders: @sandrahoang686, @hanbyul-here, @faustoperez, @j08lue +* As of: 2024/03 + +## Context and Problem Statement + +When we started work on VEDA UI as an evolution of the [COVID-19 Dashboard](https://github.com/NASA-IMPACT/covid-dashboard), we chose a design system that the team was familiar with and that worked well for building responsive, mobile-first data-centric applications, the [DevSeed UI Library](https://github.com/developmentseed/ui-library-seed), developed and used at Development Seed. + +Since then, the project has grown and especially in the adaptation of VEDA UI for the U.S. Greenhouse Gas Center and Earth Information Center, more needs for custom pages and feature additions have come up. + +For the development of VEDA UI and adaptation for projects, the current UI library has become an obstacle: +1. The library does not include some standard components we have been needing, which means we have to re-invent wheels +1. The components are not optimized for accessibility (e.g. selected state), which is a requirement for NASA sites +1. The library is no longer actively maintained or used in new projects, such that implementing new components or features means a large overhead for a single project +1. Documentation is rudimentary - fine if you are familiar but lacking for external teams (VEDA's open-source commitment) +1. Community-maintained libraries have a great maturity, completeness with standard components, and good documentation + + +## Decision Drivers + +- Cost-efficient +- Developer and designer velocity +- Ease of adaptation for external teams +- Ease of support for existing and new instances + + +## Considered Options + +- [1] Keep current design system +- [2] Introduce new design system + + +## Decision Outcome + +✔️ [2] Introduce new design system + +We decided to Replace the existing custom design system with a well-maintained, widely adopted community-supported design system. This ensures built-in accessibility standards and eliminates the need for designers and developers to invest time in ongoing maintenance. + +The preliminary systems we are considering are: + * [Chakra UI](https://chakra-ui.com/). Built on React, it has a large component library, built-in accessibility and theming options. It is designed with responsive and mobile-first principles and has a strong community and documentation. + * The [U.S. Web Design System (USWDS)](https://github.com/uswds/uswds) is tailored for government projects. It's designed to meet the specific needs of U.S. federal websites, offering components that are accessible, secure, and in line with U.S. digital standards. + +Any option we choose will have to be extended for data visualization components (charts, maps, widgets, etc.) and themed for the 3 current VEDA instances: VEDA Dashboard, U.S. GHG Center, Earth.gov. + + +## Pros and Cons of the Options + +### [1] Keep current design system +- 💚 No refactor costs +- 🚩 Maintenance costs carried by single project +- 🚩 Slower design and development for new components +- 🚩 Obstacle to external implementation and adaptation of VEDA UI + +### [2] Introduce new design system +- 💚 Access to communty-developed features - more components we need, docs +- 💚 Improved accessibility out of the box +- 💚 Easier onboarding / external adaptation because of better documentation and community support +- 🚩 Larger investment into refactor and onboarding \ No newline at end of file From 4cfe48861f3c479db0726a08886db663733aee15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20R=C3=BChl?= Date: Thu, 24 Oct 2024 13:06:10 +0200 Subject: [PATCH 02/89] Add first test to the aoi specification --- .../map/controls/aoi/preset-selector.tsx | 4 +- .../map/analysis-message-control.tsx | 4 +- test/playwright/tests/exploreAoi.spec.ts | 55 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 test/playwright/tests/exploreAoi.spec.ts diff --git a/app/scripts/components/common/map/controls/aoi/preset-selector.tsx b/app/scripts/components/common/map/controls/aoi/preset-selector.tsx index fe0f1a508..92b8901e2 100644 --- a/app/scripts/components/common/map/controls/aoi/preset-selector.tsx +++ b/app/scripts/components/common/map/controls/aoi/preset-selector.tsx @@ -88,7 +88,9 @@ const SelectorWrapper = styled.div` position: relative; `; -const PresetSelect = styled.select` +const PresetSelect = styled.select.attrs({ + 'data-testid': 'preset-selector' + })` max-width: 200px; height: ${selectorHeight}; color: transparent; diff --git a/app/scripts/components/exploration/components/map/analysis-message-control.tsx b/app/scripts/components/exploration/components/map/analysis-message-control.tsx index 25517b22e..973ae0dce 100644 --- a/app/scripts/components/exploration/components/map/analysis-message-control.tsx +++ b/app/scripts/components/exploration/components/map/analysis-message-control.tsx @@ -78,7 +78,9 @@ const MessageStatusIndicator = styled.div` } }} `; -const MessageContent = styled.div` +const MessageContent = styled.div.attrs({ + 'data-testid': 'analysis-message' + })` line-height: 1.5rem; max-height: 1.5rem; diff --git a/test/playwright/tests/exploreAoi.spec.ts b/test/playwright/tests/exploreAoi.spec.ts new file mode 100644 index 000000000..9c7096f37 --- /dev/null +++ b/test/playwright/tests/exploreAoi.spec.ts @@ -0,0 +1,55 @@ +import { test, expect } from '../pages/basePage'; + +test.describe('Area of Interest (AOI) Analysis', () => { + test.beforeEach( + async ({ page, explorePage, consentBanner, datasetSelectorComponent }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal to be visible in trace + page.on('pageerror', (exception) => { + console.log(`Uncaught exception: "${JSON.stringify(exception)}"`); + pageErrorCalled = true; + }); + + const mapboxResponsePromise = page.waitForResponse( + /api\.mapbox.com\/v4\/mapbox\.mapbox-streets-v8/i + ); + + // Given that I am on the map view (explore page) + await page.goto('/exploration'); + await consentBanner.acceptButton.click(); + await datasetSelectorComponent.addFirstDataset(); + await explorePage.closeFeatureTour(); + } + ); + + test('User selects a pre-defined AOI', async ({ page }) => { + // When I select the "Analyze an area" tool (Dropdown menu) + const toolbar = page.getByTestId('preset-selector'); + // And choose one of the listed regions + await toolbar.selectOption('Hawaii'); + + // Then map should display the selected area as the AOI + // const aoi = await page.$('selector-for-aoi'); // Adjust the selector as needed + // expect(aoi).not.toBeNull(); + + // // And the AOI should not be editable when clicking on it + // await page.click('selector-for-aoi'); // Adjust the selector as needed + // const isEditable = await page.$eval('selector-for-aoi', el => console.log(el)); // Adjust the selector as needed + // expect(isEditable).toBe(false); + + // And the analysis message should display the size of the area + const analysisMessage = await page + .getByTestId('analysis-message') + .textContent() + + expect(analysisMessage).toContain('An area of 17 K km2 is selected.'); + + // And the 'Delete all areas' button should be shown + const deleteButton = await page.$('text=Delete all areas'); + expect(deleteButton).not.toBeNull(); + + // And the 'Run analysis' button should be shown + const runButton = await page.$('text=Run analysis'); + expect(runButton).not.toBeNull(); + }); +}); From 3b41dad93e2cf760560a03f01b84f0cfe6a0ff1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20R=C3=BChl?= Date: Thu, 24 Oct 2024 17:56:33 +0200 Subject: [PATCH 03/89] Add instructions to the setup documentation on how to run the tests --- docs/development/SETUP.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/development/SETUP.md b/docs/development/SETUP.md index 0a6aaea5c..ee91da3d2 100644 --- a/docs/development/SETUP.md +++ b/docs/development/SETUP.md @@ -62,6 +62,20 @@ VEDA-UI includes a GitHub action for checking TypeScript and lint errors. If you #### Pre-commit Hook Additionally, there's a pre-commit hook that performs the same error checks. This helps developers identify and address issues at an earlier stage. If you need to bypass the check (to make a local temporary commit etc.), include the `--no-verify`` flag in your commit command. +### Testing + +## Unit tests + +`yarn test` + +## End-to-end tests +Make sure the development server is running (`yarn serve`) + +`yarn test:e2e --ui` + +Alternatively, you can install the playwright extension for vs code (ms-playwright.playwright) and run the tests directly from there. It allows to run the tests in watch mode, open the browser or trace viewer, set breakpoints,... +Again, make sure that the development server is running (`yarn serve`). + ## Deployment To prepare the app for deployment run: From 86e83fa50e185d134e79d629d76710dfd23a5f2e Mon Sep 17 00:00:00 2001 From: Hanbyul Jo Date: Fri, 25 Oct 2024 13:53:32 -0400 Subject: [PATCH 04/89] Handle empty source parameters --- .../components/exploration/data-utils-no-faux-module.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/scripts/components/exploration/data-utils-no-faux-module.ts b/app/scripts/components/exploration/data-utils-no-faux-module.ts index c49804d1a..bfe0927d4 100644 --- a/app/scripts/components/exploration/data-utils-no-faux-module.ts +++ b/app/scripts/components/exploration/data-utils-no-faux-module.ts @@ -17,7 +17,6 @@ import { DATA_METRICS, DEFAULT_DATA_METRICS } from './components/datasets/analysis-metrics'; -import { DEFAULT_COLORMAP } from './components/datasets/colormap-options'; import { utcString2userTzDate } from '$utils/date'; import { DatasetLayer, @@ -64,8 +63,8 @@ function getInitialMetrics(data: DatasetLayer): DataMetric[] { return foundMetrics; } -function getInitialColorMap(dataset: DatasetLayer): string { - return dataset.sourceParams?.colormap_name ?? DEFAULT_COLORMAP; +function getInitialColorMap(dataset: DatasetLayer): string|undefined { + return dataset.sourceParams?.colormap_name; } export function reconcileDatasets( @@ -194,6 +193,10 @@ export function resolveRenderParams( datasetSourceParams: Record | undefined, queryDataRenders: Record | undefined ): Record | undefined { + // Return null if there are no user-configured sourcparams nor render parameter + // so it doesn't get subbed with default values + if (!datasetSourceParams && !queryDataRenders) return undefined; + // Start with sourceParams from the dataset. // Return the source param as it is if exists if (hasValidSourceParams(datasetSourceParams)) { From 1ee6025c404c6e6d947cf4e06455d0be9248a7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20R=C3=BChl?= Date: Mon, 28 Oct 2024 11:20:02 +0100 Subject: [PATCH 05/89] Use more specific locator, using test id --- test/playwright/pages/explorePage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/playwright/pages/explorePage.ts b/test/playwright/pages/explorePage.ts index 5ba74c44c..f9720568d 100644 --- a/test/playwright/pages/explorePage.ts +++ b/test/playwright/pages/explorePage.ts @@ -14,7 +14,7 @@ export default class ExplorePage { this.mapboxCanvas = this.page.getByLabel('Map', { exact: true }); this.firstDatasetItem = this.page.getByRole('article'); this.closeFeatureTourButton = this.page.getByRole('button', { name: 'Close feature tour' }); - this.presetSelector = this.page.locator('#preset-selector'); + this.presetSelector = this.page.getByTestId('preset-selector'); } async closeFeatureTour() { From 15636b84440c898c843d66596a57b56143c0742f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20R=C3=BChl?= Date: Mon, 28 Oct 2024 11:31:04 +0100 Subject: [PATCH 06/89] Improve aoi tests make steps appear in report, use better locators and validators, add expectations verbose, ... --- test/playwright/tests/exploreAoi.spec.ts | 61 +++++++++++++----------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/test/playwright/tests/exploreAoi.spec.ts b/test/playwright/tests/exploreAoi.spec.ts index 9c7096f37..3a89d9a3d 100644 --- a/test/playwright/tests/exploreAoi.spec.ts +++ b/test/playwright/tests/exploreAoi.spec.ts @@ -23,33 +23,38 @@ test.describe('Area of Interest (AOI) Analysis', () => { ); test('User selects a pre-defined AOI', async ({ page }) => { - // When I select the "Analyze an area" tool (Dropdown menu) - const toolbar = page.getByTestId('preset-selector'); - // And choose one of the listed regions - await toolbar.selectOption('Hawaii'); - - // Then map should display the selected area as the AOI - // const aoi = await page.$('selector-for-aoi'); // Adjust the selector as needed - // expect(aoi).not.toBeNull(); - - // // And the AOI should not be editable when clicking on it - // await page.click('selector-for-aoi'); // Adjust the selector as needed - // const isEditable = await page.$eval('selector-for-aoi', el => console.log(el)); // Adjust the selector as needed - // expect(isEditable).toBe(false); - - // And the analysis message should display the size of the area - const analysisMessage = await page - .getByTestId('analysis-message') - .textContent() - - expect(analysisMessage).toContain('An area of 17 K km2 is selected.'); - - // And the 'Delete all areas' button should be shown - const deleteButton = await page.$('text=Delete all areas'); - expect(deleteButton).not.toBeNull(); - - // And the 'Run analysis' button should be shown - const runButton = await page.$('text=Run analysis'); - expect(runButton).not.toBeNull(); + await test.step('When I select the "Analyze an area" tool (Dropdown menu)', async () => { + const toolbar = page.getByTestId('preset-selector'); + + await test.step('And choose one of the listed regions', async () => { + toolbar.selectOption('Hawaii'); + }); + }); + + await test.step('Then the map should display the selected area as the AOI', async () => { + // const aoi = await page.$('selector-for-aoi'); // Adjust the selector as needed + // expect(aoi).not.toBeNull(); + + await test.step('And the AOI should not be editable when clicking on it', async () => { + // await page.click('selector-for-aoi'); // Adjust the selector as needed + // const isEditable = await page.$eval('selector-for-aoi', el => console.log(el)); // Adjust the selector as needed + // expect(isEditable).toBe(false); + }); + }); + + await expect( + page.getByTestId('analysis-message'), + 'And the analysis message should display the size of the area' + ).toHaveText(/An area of 17 K km2/i); + + await expect( + page.getByRole('button', { name: 'Delete all areas' }), + 'And the "Delete all areas" button should be shown' + ).toBeVisible(); + + await expect( + page.getByRole('button', { name: 'Run analysis' }), + 'And the "Run analysis" button should be shown' + ).toBeVisible(); }); }); From c83bce5420f6990795f783705ca5271c46061f9e Mon Sep 17 00:00:00 2001 From: Hanbyul Jo Date: Tue, 29 Oct 2024 12:40:38 -0400 Subject: [PATCH 07/89] Release v5.9.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a0d719ab3..c1af59eb4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@developmentseed/veda-ui", "description": "Dashboard", - "version": "5.8.0", + "version": "5.9.0", "author": { "name": "Development Seed", "url": "https://developmentseed.org/" From ebcaff6ae919c34974c42bc9c15660ee74264210 Mon Sep 17 00:00:00 2001 From: snmln Date: Tue, 29 Oct 2024 15:54:19 -0400 Subject: [PATCH 08/89] Revert "Color maps range slider implementation (#1190)" This reverts commit 233a31c2ba96261894012fa2008ace6716010adb, reversing changes made to 46e1c5df246aafec25b760e231938607cd1cd41e. --- .../style-generators/raster-paint-layer.tsx | 25 +- .../style-generators/raster-timeseries.tsx | 20 +- app/scripts/components/common/map/types.d.ts | 1 - app/scripts/components/common/uswds/index.tsx | 2 - app/scripts/components/common/uswds/input.tsx | 10 - .../components/exploration/atoms/hooks.ts | 11 - .../datasets/color-range-slider.scss | 50 ---- .../components/datasets/colorRangeSlider.tsx | 272 ------------------ .../components/datasets/colormap-options.tsx | 127 ++------ .../components/datasets/data-layer-card.tsx | 123 +++----- .../components/datasets/dataset-list-item.tsx | 6 +- .../exploration/components/map/layer.tsx | 6 +- .../components/exploration/types.d.ts.ts | 7 +- 13 files changed, 85 insertions(+), 575 deletions(-) delete mode 100644 app/scripts/components/common/uswds/input.tsx delete mode 100644 app/scripts/components/exploration/components/datasets/color-range-slider.scss delete mode 100644 app/scripts/components/exploration/components/datasets/colorRangeSlider.tsx diff --git a/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx b/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx index e5aa8479d..bf5f170bf 100644 --- a/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx +++ b/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx @@ -13,7 +13,6 @@ interface RasterPaintLayerProps extends BaseGeneratorParams { colorMap?: string | undefined; tileParams: Record; generatorPrefix?: string; - reScale?: { min: number; max: number }; } export function RasterPaintLayer(props: RasterPaintLayerProps) { @@ -25,8 +24,7 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) { hidden, opacity, colorMap, - reScale, - generatorPrefix = 'raster' + generatorPrefix = 'raster', } = props; const { updateStyle } = useMapStyle(); @@ -34,14 +32,8 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) { const generatorId = `${generatorPrefix}-${id}`; const updatedTileParams = useMemo(() => { - return { - ...tileParams, - ...(colorMap && { - colormap_name: colorMap - }), - ...(reScale && { rescale: Object.values(reScale) }) - }; - }, [tileParams, colorMap, reScale]); + return { ...tileParams, ...colorMap && {colormap_name: colorMap}}; + }, [tileParams, colorMap]); // // Generate Mapbox GL layers and sources for raster timeseries @@ -55,9 +47,7 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) { useEffect( () => { - const tileParamsAsString = qs.stringify(updatedTileParams, { - arrayFormat: 'comma' - }); + const tileParamsAsString = qs.stringify(updatedTileParams, { arrayFormat: 'comma' }); const zarrSource: RasterSource = { type: 'raster', @@ -73,8 +63,8 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) { paint: { 'raster-opacity': hidden ? 0 : rasterOpacity, 'raster-opacity-transition': { - duration: 320 - } + duration: 320, + }, }, minzoom: minZoom, metadata: { @@ -103,8 +93,7 @@ export function RasterPaintLayer(props: RasterPaintLayerProps) { tileApiEndpoint, haveTileParamsChanged, generatorParams, - colorMap, - reScale + colorMap // generatorParams includes hidden and opacity // hidden, // opacity, diff --git a/app/scripts/components/common/map/style-generators/raster-timeseries.tsx b/app/scripts/components/common/map/style-generators/raster-timeseries.tsx index d0bd548c8..271bf74ac 100644 --- a/app/scripts/components/common/map/style-generators/raster-timeseries.tsx +++ b/app/scripts/components/common/map/style-generators/raster-timeseries.tsx @@ -63,10 +63,10 @@ export function RasterTimeseries(props: RasterTimeseriesProps) { stacApiEndpoint, tileApiEndpoint, colorMap, - reScale } = props; const { current: mapInstance } = useMaps(); + const theme = useTheme(); const { updateStyle } = useMapStyle(); @@ -270,9 +270,7 @@ export function RasterTimeseries(props: RasterTimeseriesProps) { controller }); const mosaicUrl = responseData.links[1].href; - setMosaicUrl( - mosaicUrl.replace('/{tileMatrixSetId}', '/WebMercatorQuad') - ); + setMosaicUrl(mosaicUrl.replace('/{tileMatrixSetId}', '/WebMercatorQuad')); } catch (error) { // @NOTE: conditional logic TO BE REMOVED once new BE endpoints have moved to prod... Fallback on old request url if new endpoints error with nonexistance... if (error.request) { @@ -286,14 +284,10 @@ export function RasterTimeseries(props: RasterTimeseriesProps) { const mosaicUrl = responseData.links[1].href; setMosaicUrl(mosaicUrl); } else { - LOG && + LOG && /* eslint-disable-next-line no-console */ - console.log( - 'Titiler /register %cEndpoint error', - 'color: red;', - error - ); - throw error; + console.log('Titiler /register %cEndpoint error', 'color: red;', error); + throw error; } } @@ -367,8 +361,7 @@ export function RasterTimeseries(props: RasterTimeseriesProps) { { assets: 'cog_default', ...(sourceParams ?? {}), - ...(colorMap && { colormap_name: colorMap }), - ...(reScale && {rescale: Object.values(reScale)}) + ...colorMap && {colormap_name: colorMap} }, // Temporary solution to pass different tile parameters for hls data { @@ -496,7 +489,6 @@ export function RasterTimeseries(props: RasterTimeseriesProps) { }, [ mosaicUrl, colorMap, - reScale, points, minZoom, haveSourceParamsChanged, diff --git a/app/scripts/components/common/map/types.d.ts b/app/scripts/components/common/map/types.d.ts index 47ad68c4f..899931d39 100644 --- a/app/scripts/components/common/map/types.d.ts +++ b/app/scripts/components/common/map/types.d.ts @@ -54,7 +54,6 @@ export interface BaseTimeseriesProps extends BaseGeneratorParams { zoomExtent?: number[]; onStatusChange?: (result: { status: ActionStatus; id: string }) => void; colorMap?: string; - reScale?: { min: number; max: number }; } // export interface ZarrTimeseriesProps extends BaseTimeseriesProps { diff --git a/app/scripts/components/common/uswds/index.tsx b/app/scripts/components/common/uswds/index.tsx index a8f151d6b..e5410b060 100644 --- a/app/scripts/components/common/uswds/index.tsx +++ b/app/scripts/components/common/uswds/index.tsx @@ -2,5 +2,3 @@ export { USWDSAlert } from './alert'; export { USWDSButtonGroup, USWDSButton } from './button'; export { USWDSLink } from './link'; export { USWDSBanner, USWDSBannerContent } from './banner'; - -export { USWDSTextInput, USWDSTextInputMask } from './input'; diff --git a/app/scripts/components/common/uswds/input.tsx b/app/scripts/components/common/uswds/input.tsx deleted file mode 100644 index f239b6bc5..000000000 --- a/app/scripts/components/common/uswds/input.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import { TextInput, TextInputMask } from "@trussworks/react-uswds"; - -export function USWDSTextInput(props) { - return ; -} - -export function USWDSTextInputMask(props) { - return ; - } \ No newline at end of file diff --git a/app/scripts/components/exploration/atoms/hooks.ts b/app/scripts/components/exploration/atoms/hooks.ts index 115f374f3..93ea7f5d1 100644 --- a/app/scripts/components/exploration/atoms/hooks.ts +++ b/app/scripts/components/exploration/atoms/hooks.ts @@ -157,17 +157,6 @@ export function useTimelineDatasetColormap( return useAtom(colorMapAtom); } -export function useTimelineDatasetColormapScale( - datasetAtom: PrimitiveAtom -) { - const colorMapScaleAtom = useMemo(() => { - return focusAtom(datasetAtom, (optic) => - optic.prop('settings').prop('scale') - ); - }, [datasetAtom]); - - return useAtom(colorMapScaleAtom); -} export const useTimelineDatasetAnalysis = ( datasetAtom: PrimitiveAtom ) => { diff --git a/app/scripts/components/exploration/components/datasets/color-range-slider.scss b/app/scripts/components/exploration/components/datasets/color-range-slider.scss deleted file mode 100644 index d66d2e815..000000000 --- a/app/scripts/components/exploration/components/datasets/color-range-slider.scss +++ /dev/null @@ -1,50 +0,0 @@ -/* Removing the default appearance */ -.thumb, -.thumb::-webkit-slider-thumb { - touch-action: 'none'; - - -webkit-appearance: none; - -webkit-tap-highlight-color: transparent; -} - -.thumb { - pointer-events: none; -} -/* For Chrome browsers */ -.thumb::-webkit-slider-thumb { - -webkit-appearance: none; - pointer-events: all; - width: 20px; - height: 20px; - background-color: #fff; - border-radius: 50%; - border: 2px solid #1565ef; - border-width: 1px; - box-shadow: 0 0 0 1px #c6c6c6; - cursor: pointer; -} - -/* For Firefox browsers */ -.thumb::-moz-range-thumb { - -webkit-appearance: none; - pointer-events: all; - width: 20px; - height: 20px; - background-color: #fff; - border-radius: 50%; - border: 2px solid #1565ef; - border-width: 1px; - box-shadow: 0 0 0 1px #c6c6c6; - cursor: pointer; -} - -input::-webkit-outer-spin-button, -input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -/* Firefox */ -input[type='number'] { - -moz-appearance: textfield; -} diff --git a/app/scripts/components/exploration/components/datasets/colorRangeSlider.tsx b/app/scripts/components/exploration/components/datasets/colorRangeSlider.tsx deleted file mode 100644 index 835e5b42a..000000000 --- a/app/scripts/components/exploration/components/datasets/colorRangeSlider.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import React, { useCallback, useEffect, useState, useRef } from 'react'; - -import './color-range-slider.scss'; -import { colorMapScale } from '$components/exploration/types.d.ts'; - -import { USWDSTextInput } from '$components/common/uswds'; - -interface ColorrangeRangeSlideProps { - // Absolute minimum of color range - min: number; - - // Absolute maximum of color range - max: number; - - // Previously selected minimum and maximum of colorRangeScale - colorMapScale: colorMapScale | undefined; - - // Update colorRangeScale - setColorMapScale: (colorMapScale: colorMapScale) => void; -} - -export function ColorRangeSlider({ - min, - max, - colorMapScale, - setColorMapScale -}: ColorrangeRangeSlideProps) { - const setDefaultMin = colorMapScale?.min ? colorMapScale.min : min; - const setDefaultMax = colorMapScale?.max ? colorMapScale.max : max; - - const [minVal, setMinVal] = useState(setDefaultMin); - const [maxVal, setMaxVal] = useState(setDefaultMax); - const minValRef = useRef(setDefaultMin); - const maxValRef = useRef(setDefaultMax); - const [inputError, setInputError] = useState({ - min: false, - max: false, - largerThanMax: false, - lessThanMin: false - }); - - const range = useRef(null); - - // Convert to percentage - const getPercent = useCallback( - (value) => ((value - min) * 100) / (max - min), - [min, max] - ); - //Calculate the range - const rangeCalculation = (maxPercent, minPercent) => { - const thumbWidth = 20; - if (range.current) { - range.current.style.width = `calc(${ - maxPercent - minPercent >= 100 ? 100 : maxPercent - minPercent - }% - ${(thumbWidth - minPercent * 0.2) * (maxPercent / 100)}px )`; - } - return; - }; - const resetErrorOnSlide = (value, slider) => { - if (value > min || value < max) { - slider === 'max' - ? setInputError({ ...inputError, max: false, lessThanMin: false }) - : setInputError({ ...inputError, min: false, largerThanMax: false }); - } - }; - - const minMaxBuffer = 0.001; - const textInputClasses = - 'flex-1 radius-md height-3 font-size-3xs width-5 border-2px '; - const thumbPosition = `position-absolute pointer-events width-card height-0 outline-0`; - - useEffect(() => { - let maxValPrevious; - let minValPrevious; - //checking that there are no current errors with inputs - if (Object.values(inputError).every((error) => !error)) { - //set the filled range bar on initial load - if ( - colorMapScale && - maxVal != maxValPrevious && - minVal != minValPrevious - ) { - const minPercent = getPercent(minValRef.current); - const maxPercent = getPercent(maxValRef.current); - - rangeCalculation(maxPercent, minPercent); - - if (range.current) - range.current.style.left = `calc(${minPercent}% + ${ - 10 - minPercent * 0.2 - }px)`; - } else { - //set the filled range bar if change to max slider - if (maxVal != maxValPrevious) { - maxValPrevious = maxVal; - const minPercent = getPercent(minValRef.current); - const maxPercent = getPercent(maxVal); - rangeCalculation(maxPercent, minPercent); - } - //set the filled range bar if change to min slider - if (minVal != minValPrevious) { - minValPrevious = minVal; - const minPercent = getPercent(minVal); - const maxPercent = getPercent(maxValRef.current); - rangeCalculation(maxPercent, minPercent); - if (range.current) - range.current.style.left = `calc(${minPercent}% + ${ - 10 - minPercent * 0.2 - }px)`; - } - } - } - // determining if there is an initial colorMapeScale or if it is the default min and max - if ( - !colorMapScale || - (colorMapScale.max == max && colorMapScale.min == min) - ) { - setColorMapScale({ min: minVal, max: maxVal }); - } else - setColorMapScale({ - min: Number(minValRef.current), - max: Number(maxValRef.current) - }); /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [maxVal, minVal, getPercent, setColorMapScale, inputError, max, min]); - - return ( -
-
- - - { - if (event.target.value == '') return (minValRef.current = minVal); - }} - onChange={(event) => { - minValRef.current = event.target.value; - const value = Number(event.target.value); - - if (value > maxVal - minMaxBuffer) - return setInputError({ ...inputError, largerThanMax: true }); - if (value < min || value > max) { - return setInputError({ ...inputError, min: true }); - } else { - setInputError({ - ...inputError, - min: false, - largerThanMax: false - }); - setMinVal(Math.min(value, maxVal - minMaxBuffer)); - } - }} - /> - -
- { - const value = Math.min( - Number(event.target.value), - maxVal - minMaxBuffer - ); - resetErrorOnSlide(value, 'min'); - setMinVal(value); - minValRef.current = value; - }} - className={`thumb ${thumbPosition} z-index-30`} - style={{ - zIndex: minVal >= max - 10 * minMaxBuffer ? '500' : '300' - }} - /> - { - if (event.target.value == '') return (maxValRef.current = maxVal); - }} - onChange={(event) => { - const value = Math.max( - Number(event.target.value), - minVal + minMaxBuffer - ); - resetErrorOnSlide(value, 'max'); - setMaxVal(value); - maxValRef.current = value; - }} - className={`thumb ${thumbPosition} z-400`} - style={{ - zIndex: minVal <= max - 10 * minMaxBuffer ? '500' : '400' - }} - /> -
-
-
- {/* Show divergent map middle point */} - {min < 0 ? ( -
- ) : null} -
-
-
- { - const value = Number(event.target.value); - maxValRef.current = event.target.value; - - if (value < minVal + minMaxBuffer) - return setInputError({ ...inputError, lessThanMin: true }); - - if (value < min || value > max) { - return setInputError({ ...inputError, max: true }); - } else { - //unsetting error - setInputError({ ...inputError, max: false, lessThanMin: false }); - setMaxVal(Math.max(value, minVal + minMaxBuffer)); - } - }} - /> - - {/* error message for min input that is outside min max of color map */} - {inputError.max || inputError.min ? ( -

- Please enter a value between {min} and {max} -

- ) : null}{' '} - {/* error message for min input that is larger than current max */} - {inputError.largerThanMax ? ( -

- Please enter a value less than {maxValRef.current} -

- ) : null} - {/* error message for min input that is less than current min */} - {inputError.lessThanMin ? ( -

- Please enter a value larger than {minValRef.current} -

- ) : null} -
- ); -} diff --git a/app/scripts/components/exploration/components/datasets/colormap-options.tsx b/app/scripts/components/exploration/components/datasets/colormap-options.tsx index a57451778..a6ce07e21 100644 --- a/app/scripts/components/exploration/components/datasets/colormap-options.tsx +++ b/app/scripts/components/exploration/components/datasets/colormap-options.tsx @@ -1,40 +1,23 @@ import React, { useEffect, useState } from 'react'; -import { Icon } from '@trussworks/react-uswds'; +import { Icon } from "@trussworks/react-uswds"; import { CollecticonDrop } from '@devseed-ui/collecticons'; - -import { - sequentialColorMaps, - divergingColorMaps, - restColorMaps -} from './colorMaps'; +import { sequentialColorMaps, divergingColorMaps, restColorMaps } from './colorMaps'; import './colormap-options.scss'; -import { ColorRangeSlider } from './colorRangeSlider'; -import { colorMapScale } from '$components/exploration/types.d.ts'; export const DEFAULT_COLORMAP = 'viridis'; const CURATED_SEQUENTIAL_COLORMAPS = [ - 'viridis', - 'plasma', - 'inferno', - 'magma', - 'cividis', - 'purples', - 'blues', - 'reds', - 'greens', - 'oranges', - 'ylgnbu', - 'ylgn', - 'gnbu' + 'viridis', 'plasma', 'inferno', 'magma', 'cividis', + 'purples', 'blues', 'reds', 'greens', 'oranges', + 'ylgnbu', 'ylgn', 'gnbu' ]; -const CURATED_DIVERGING_COLORMAPS = ['rdbu', 'rdylbu', 'bwr', 'coolwarm']; +const CURATED_DIVERGING_COLORMAPS = [ + 'rdbu', 'rdylbu', 'bwr', 'coolwarm' +]; -export const classifyColormap = ( - colormapName: string -): 'sequential' | 'diverging' | 'rest' | 'unknown' => { +export const classifyColormap = (colormapName: string): 'sequential' | 'diverging' | 'rest' | 'unknown' => { const baseName = normalizeColorMap(colormapName); if (sequentialColorMaps[baseName]) { @@ -50,16 +33,9 @@ export const classifyColormap = ( interface ColormapOptionsProps { colorMap: string | undefined; setColorMap: (colorMap: string) => void; - min: number; - max: number; - setColorMapScale: (colorMapScale: colorMapScale) => void; - colorMapScale: colorMapScale | undefined; } -export const getColormapColors = ( - colormapName: string, - isReversed: boolean -): string[] => { +export const getColormapColors = (colormapName: string, isReversed: boolean): string[] => { const baseName = normalizeColorMap(colormapName); const colormapData = sequentialColorMaps[baseName] || @@ -73,19 +49,10 @@ export const getColormapColors = ( return `rgba(${r}, ${g}, ${b}, ${a})`; }); - return isReversed - ? colors.reduceRight((acc, color) => [...acc, color], []) - : colors; + return isReversed ? colors.reduceRight((acc, color) => [...acc, color], []) : colors; }; -export function ColormapOptions({ - colorMap = DEFAULT_COLORMAP, - min, - max, - setColorMap, - setColorMapScale, - colorMapScale -}: ColormapOptionsProps) { +export function ColormapOptions({ colorMap = DEFAULT_COLORMAP, setColorMap}: ColormapOptionsProps) { const initialIsReversed = colorMap.endsWith('_r'); const initialColorMap = normalizeColorMap(colorMap); @@ -96,15 +63,9 @@ export function ColormapOptions({ const [customColorMap, setCustomColorMap] = useState(null); useEffect(() => { - if ( - colormapType === 'sequential' && - !CURATED_SEQUENTIAL_COLORMAPS.includes(selectedColorMap) - ) { + if (colormapType === 'sequential' && !CURATED_SEQUENTIAL_COLORMAPS.includes(selectedColorMap)) { setCustomColorMap(selectedColorMap); - } else if ( - colormapType === 'diverging' && - !CURATED_DIVERGING_COLORMAPS.includes(selectedColorMap) - ) { + } else if (colormapType === 'diverging' && !CURATED_DIVERGING_COLORMAPS.includes(selectedColorMap)) { setCustomColorMap(selectedColorMap); } }, [selectedColorMap, colormapType]); @@ -113,25 +74,15 @@ export function ColormapOptions({ if (colormapType === 'sequential') { if (customColorMap) { - availableColormaps = [ - { name: customColorMap }, - ...CURATED_SEQUENTIAL_COLORMAPS.map((name) => ({ name })) - ]; + availableColormaps = [{ name: customColorMap }, ...CURATED_SEQUENTIAL_COLORMAPS.map(name => ({ name }))]; } else { - availableColormaps = CURATED_SEQUENTIAL_COLORMAPS.map((name) => ({ - name - })); + availableColormaps = CURATED_SEQUENTIAL_COLORMAPS.map(name => ({ name })); } } else if (colormapType === 'diverging') { if (customColorMap) { - availableColormaps = [ - { name: customColorMap }, - ...CURATED_DIVERGING_COLORMAPS.map((name) => ({ name })) - ]; + availableColormaps = [{ name: customColorMap }, ...CURATED_DIVERGING_COLORMAPS.map(name => ({ name }))]; } else { - availableColormaps = CURATED_DIVERGING_COLORMAPS.map((name) => ({ - name - })); + availableColormaps = CURATED_DIVERGING_COLORMAPS.map(name => ({ name })); } } else if (colormapType === 'rest') { availableColormaps = [{ name: selectedColorMap }]; @@ -154,36 +105,17 @@ export function ColormapOptions({ return (
-
- Colormap options -
- -
- -
- +
Colormap options
+
+
+ {isReversed ? ( ) : ( )} - +
@@ -193,21 +125,13 @@ export function ColormapOptions({ return (
handleColorMapSelect(name.toLowerCase())} >
+ )} + {showNonConfigurableCmap && - )} + />} + ); diff --git a/app/scripts/components/exploration/components/datasets/dataset-list-item.tsx b/app/scripts/components/exploration/components/datasets/dataset-list-item.tsx index 8c1d08441..a80792c32 100644 --- a/app/scripts/components/exploration/components/datasets/dataset-list-item.tsx +++ b/app/scripts/components/exploration/components/datasets/dataset-list-item.tsx @@ -33,8 +33,7 @@ import { useTimelineDatasetAtom, useTimelineDatasetColormap, useTimelineDatasetSettings, - useTimelineDatasetVisibility, - useTimelineDatasetColormapScale + useTimelineDatasetVisibility } from '$components/exploration/atoms/hooks'; import { useAnalysisController, @@ -104,7 +103,6 @@ export function DatasetListItem(props: DatasetListItemProps) { const [isVisible, setVisible] = useTimelineDatasetVisibility(datasetAtom); const [colorMap, setColorMap] = useTimelineDatasetColormap(datasetAtom); - const [colorMapScale, setColorMapScale]=useTimelineDatasetColormapScale(datasetAtom); const [modalLayerInfo, setModalLayerInfo] = React.useState(); const [, setSetting] = useTimelineDatasetSettings(datasetAtom); @@ -211,7 +209,7 @@ export function DatasetListItem(props: DatasetListItemProps) {
- +
{modalLayerInfo && ( ); case 'cmr': @@ -88,7 +88,6 @@ export function Layer(props: LayerProps) { opacity={opacity} onStatusChange={onStatusChange} colorMap={colorMap} - reScale={scale} /> ); case 'raster': @@ -106,7 +105,6 @@ export function Layer(props: LayerProps) { opacity={opacity} onStatusChange={onStatusChange} colorMap={colorMap} - reScale={scale} /> ); default: diff --git a/app/scripts/components/exploration/types.d.ts.ts b/app/scripts/components/exploration/types.d.ts.ts index 2ada067a1..b6daad9f4 100644 --- a/app/scripts/components/exploration/types.d.ts.ts +++ b/app/scripts/components/exploration/types.d.ts.ts @@ -95,10 +95,7 @@ export interface DatasetData extends EnhancedDatasetLayer { timeDensity: TimeDensity; domain: Date[]; } -export interface colorMapScale { - min: number; - max: number; -} + export interface DatasetSettings { // Whether or not the layer should be shown on the map. isVisible?: boolean; @@ -108,8 +105,6 @@ export interface DatasetSettings { analysisMetrics?: DataMetric[]; // Active colormap of the layer. colorMap?: string; - // Active colormap scale - scale?: colorMapScale; } // Any sort of meta information the dataset like: From 7301b3caacaa0c42653bb680a182baa17f15b7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20R=C3=BChl?= Date: Wed, 30 Oct 2024 14:27:34 +0100 Subject: [PATCH 09/89] Update STYLE_GUIDE.md --- docs/development/STYLE_GUIDE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/development/STYLE_GUIDE.md b/docs/development/STYLE_GUIDE.md index 5ccbeaf39..2083e2846 100644 --- a/docs/development/STYLE_GUIDE.md +++ b/docs/development/STYLE_GUIDE.md @@ -13,6 +13,7 @@ This style guide is an evolving document and will be updated as the VEDA UI proj ### Naming for Branches - Use the related GitHub issue number; if not available, use `hotfix` or `update`. +- The ideal pattern would be {issue#}-{some}-{meaningful}-{title} ## Tagging in PRs @@ -47,4 +48,4 @@ To maintain consistent code formatting and linting across all contributors, foll 1. Navigate to the `.vscode` directory at the root of the project 2. Copy the `settings.json.sample` file from the veda-ui repository 3. Rename the copied file to `settings.json` -4. Customize the `settings.json` file with your personal preferences as needed \ No newline at end of file +4. Customize the `settings.json` file with your personal preferences as needed From 4d53f7387ea333b5f790c94585c9f87469738920 Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Wed, 30 Oct 2024 14:57:41 +0100 Subject: [PATCH 10/89] Update condition to run playwright tests on release branches --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index de8ac13d1..9ae2af5a4 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -127,7 +127,7 @@ jobs: timeout-minutes: 60 needs: prep runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/heads/release') + if: startsWith(github.head_ref, 'release') steps: - name: Checkout From d7cc8747ff0fef1eb72eda073e8cff29f17b3046 Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 30 Oct 2024 21:28:16 +0100 Subject: [PATCH 11/89] Add paragraph on design system TBD --- docs/adr/003-design-system-change.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/adr/003-design-system-change.md b/docs/adr/003-design-system-change.md index 23d9ca22c..80267033a 100644 --- a/docs/adr/003-design-system-change.md +++ b/docs/adr/003-design-system-change.md @@ -45,6 +45,7 @@ The preliminary systems we are considering are: Any option we choose will have to be extended for data visualization components (charts, maps, widgets, etc.) and themed for the 3 current VEDA instances: VEDA Dashboard, U.S. GHG Center, Earth.gov. +The final choice for a web design system is not subject of this ADR and will be documented separately. ## Pros and Cons of the Options From da6c562c34c223b0c4ba84ad73a5a9b2ead373b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20R=C3=BChl?= Date: Mon, 28 Oct 2024 11:48:09 +0100 Subject: [PATCH 12/89] Enable prettier for .ts/.tsx files, but only warn --- .eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 54e09bbf1..92c358655 100644 --- a/.eslintrc +++ b/.eslintrc @@ -99,7 +99,7 @@ "files": ["**/*.ts", "**/*.tsx"], "plugins": ["@typescript-eslint"], "rules": { - "prettier/prettier": 0, + "prettier/prettier": 2, "@typescript-eslint/no-unused-vars": 2, "@typescript-eslint/no-non-null-assertion": 0 }, From dcb5fdf45ac1003bddd53eb210800345ce3ed1b5 Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Thu, 31 Oct 2024 10:30:32 +0100 Subject: [PATCH 13/89] Hotfix to hide the external link badge from cards --- app/scripts/components/common/card/index.tsx | 16 +++++++--------- .../common/featured-slider-section.tsx | 3 +-- .../components/common/related-content.tsx | 4 +--- app/scripts/components/common/smart-link.tsx | 16 +++++++--------- app/scripts/components/home/featured-stories.tsx | 3 +-- app/scripts/components/sandbox/cards/index.js | 2 -- .../components/stories/hub/hub-content.tsx | 4 ++-- app/scripts/types/veda.ts | 2 +- app/scripts/utils/url.ts | 3 +-- mock/stories/external-link-example.stories.mdx | 2 +- parcel-resolver-veda/index.d.ts | 2 +- 11 files changed, 23 insertions(+), 34 deletions(-) diff --git a/app/scripts/components/common/card/index.tsx b/app/scripts/components/common/card/index.tsx index c3dc7e221..08e32a3bb 100644 --- a/app/scripts/components/common/card/index.tsx +++ b/app/scripts/components/common/card/index.tsx @@ -227,7 +227,6 @@ export interface LinkProperties { export interface LinkWithPathProperties extends LinkProperties { linkTo: string; - isLinkExternal?: boolean; } export interface CardComponentBaseProps { @@ -243,6 +242,7 @@ export interface CardComponentBaseProps { parentTo?: string; tagLabels?: string[]; footerContent?: JSX.Element; + hideExternalLinkBadge?: boolean; onCardClickCapture?: MouseEventHandler; } @@ -251,7 +251,6 @@ export interface CardComponentBaseProps { export interface CardComponentPropsDeprecated extends CardComponentBaseProps { linkTo: string; onLinkClick?: MouseEventHandler; - isLinkExternal?: boolean; } export interface CardComponentProps extends CardComponentBaseProps { linkProperties: LinkWithPathProperties; @@ -278,6 +277,7 @@ function CardComponent(props: CardComponentPropsType) { tagLabels, parentTo, footerContent, + hideExternalLinkBadge, onCardClickCapture } = props; // @TODO: This process is not necessary once all the instances adapt the linkProperties syntax @@ -289,25 +289,23 @@ function CardComponent(props: CardComponentPropsType) { const { linkProperties: linkPropertiesProps } = props; linkProperties = linkPropertiesProps; } else { - const { linkTo, onLinkClick, isLinkExternal } = props; + const { linkTo, onLinkClick } = props; linkProperties = { linkTo, onLinkClick, pathAttributeKeyName: 'to', - LinkElement: SmartLink, - isLinkExternal + LinkElement: SmartLink }; } - const isExternalLink = linkProperties.isLinkExternal ?? /^https?:\/\//.test(linkProperties.linkTo); + const isExternalLink = /^https?:\/\//.test(linkProperties.linkTo); return ( {title} - {isExternalLink && } + {(hideExternalLinkBadge !== true && isExternalLink) && } {!isExternalLink && tagLabels && parentTo && ( tagLabels.map((label) => ( diff --git a/app/scripts/components/common/featured-slider-section.tsx b/app/scripts/components/common/featured-slider-section.tsx index e70a00b15..824d1e2ce 100644 --- a/app/scripts/components/common/featured-slider-section.tsx +++ b/app/scripts/components/common/featured-slider-section.tsx @@ -110,8 +110,7 @@ function FeaturedSliderSection(props: FeaturedSliderSectionProps) { linkProperties={{ linkTo: `${d.asLink?.url ?? getItemPath(d)}`, pathAttributeKeyName: 'to', - LinkElement: SmartLink, - isLinkExternal: d.isLinkExternal + LinkElement: SmartLink }} title={d.name} overline={ diff --git a/app/scripts/components/common/related-content.tsx b/app/scripts/components/common/related-content.tsx index b78e0a9ca..79fcfbc99 100644 --- a/app/scripts/components/common/related-content.tsx +++ b/app/scripts/components/common/related-content.tsx @@ -52,7 +52,6 @@ interface FormatBlock { date: string; link: string; asLink?: LinkContentData; - isLinkExternal?: boolean; parentLink: string; media: Media; parent: RelatedContentData['type']; @@ -152,8 +151,7 @@ export default function RelatedContent(props: RelatedContentProps) { linkProperties={{ linkTo: `${t.asLink?.url ?? t.link}`, LinkElement: SmartLink, - pathAttributeKeyName: 'to', - isLinkExternal: t.isLinkExternal + pathAttributeKeyName: 'to' }} title={t.name} date={ diff --git a/app/scripts/components/common/smart-link.tsx b/app/scripts/components/common/smart-link.tsx index 09a5a1f88..62c2d61f8 100644 --- a/app/scripts/components/common/smart-link.tsx +++ b/app/scripts/components/common/smart-link.tsx @@ -5,18 +5,17 @@ import { getLinkProps } from '$utils/url'; interface SmartLinkProps { to: string; - isLinkExternal?: boolean; onLinkClick?: ()=> void; children?: ReactNode; } /** - * Switches between a `a` and a `Link` depending on the optional `isLinkExternal` prop or the url. + * Switches between a `a` and a `Link` depending on the url. */ export default function SmartLink(props: SmartLinkProps) { - const { to, isLinkExternal, onLinkClick, children, ...rest } = props; - const isExternalLink = isLinkExternal ?? /^https?:\/\//.test(to); - const linkProps = getLinkProps(to, isLinkExternal, undefined, onLinkClick); + const { to, onLinkClick, children, ...rest } = props; + const isExternalLink = /^https?:\/\//.test(to); + const linkProps = getLinkProps(to, undefined, onLinkClick); return isExternalLink ? ( {children} @@ -28,15 +27,14 @@ export default function SmartLink(props: SmartLinkProps) { interface CustomLinkProps { href: string; - isLinkExternal?: boolean; } /** - * For links defined as markdown, this component will open the external link in a new tab depending on the optional `isLinkExternal` prop or the url. + * For links defined as markdown, this component will open the external link in a new tab. */ export function CustomLink(props: CustomLinkProps) { - const { href, isLinkExternal, ...rest } = props; - const isExternalLink = isLinkExternal ?? /^https?:\/\//.test(href); + const { href, ...rest } = props; + const isExternalLink = /^https?:\/\//.test(href); const linkProps = getLinkProps(href); return isExternalLink ? ( diff --git a/app/scripts/components/home/featured-stories.tsx b/app/scripts/components/home/featured-stories.tsx index 23d1c1f23..a8bb557a7 100644 --- a/app/scripts/components/home/featured-stories.tsx +++ b/app/scripts/components/home/featured-stories.tsx @@ -82,8 +82,7 @@ function FeaturedStories() { linkProperties={{ linkTo: `${d.asLink?.url ?? getStoryPath(d)}`, LinkElement: SmartLink, - pathAttributeKeyName: 'to', - isLinkExternal: d.isLinkExternal + pathAttributeKeyName: 'to' }} title={d.name} tagLabels={[getString('stories').one]} diff --git a/app/scripts/components/sandbox/cards/index.js b/app/scripts/components/sandbox/cards/index.js index dbd59add9..834473e9f 100644 --- a/app/scripts/components/sandbox/cards/index.js +++ b/app/scripts/components/sandbox/cards/index.js @@ -13,7 +13,6 @@ function SandboxCards() { } + hideExternalLinkBadge={d.hideExternalLinkBadge} imgSrc={d.media?.src} imgAlt={d.media?.alt} footerContent={ diff --git a/app/scripts/types/veda.ts b/app/scripts/types/veda.ts index b11bee858..ee2873d31 100644 --- a/app/scripts/types/veda.ts +++ b/app/scripts/types/veda.ts @@ -206,7 +206,7 @@ export interface StoryData { taxonomy: Taxonomy[]; related?: RelatedContentData[]; asLink?: LinkContentData; - isLinkExternal?: boolean; + hideExternalLinkBadge?: boolean; isHidden?: boolean; } diff --git a/app/scripts/utils/url.ts b/app/scripts/utils/url.ts index a1cda8d63..926bf23ff 100644 --- a/app/scripts/utils/url.ts +++ b/app/scripts/utils/url.ts @@ -7,14 +7,13 @@ export function isExternalLink(link: string): boolean { export const getLinkProps = ( linkTo: string, - isLinkExternal?: boolean, as?: React.ForwardRefExoticComponent< LinkProps & React.RefAttributes >, onClick?: (() => void) | MouseEventHandler ) => { // Open the link in a new tab when link is external - const isExternalLink = isLinkExternal ?? /^https?:\/\//.test(linkTo); + const isExternalLink = /^https?:\/\//.test(linkTo); return isExternalLink ? { href: linkTo, diff --git a/mock/stories/external-link-example.stories.mdx b/mock/stories/external-link-example.stories.mdx index 51a5b85d9..c6225d964 100644 --- a/mock/stories/external-link-example.stories.mdx +++ b/mock/stories/external-link-example.stories.mdx @@ -10,6 +10,7 @@ media: name: Unsplash url: https://unsplash.com/ pubDate: 2023-02-09 +hideExternalLinkBadge: false taxonomy: - name: Topics values: @@ -24,5 +25,4 @@ related: id: air-quality-and-covid-19 asLink: url: 'https://developmentseed.org' -isLinkExternal: true --- diff --git a/parcel-resolver-veda/index.d.ts b/parcel-resolver-veda/index.d.ts index 52083c4dd..5b558b4ca 100644 --- a/parcel-resolver-veda/index.d.ts +++ b/parcel-resolver-veda/index.d.ts @@ -207,7 +207,7 @@ declare module 'veda' { taxonomy: Taxonomy[]; related?: RelatedContentData[]; asLink?: LinkContentData; - isLinkExternal?: boolean; + hideExternalLinkBadge?: boolean; isHidden?: boolean; } From 668ab664a2b7576676d8e5718b5778d28d6037c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20R=C3=BChl?= Date: Thu, 31 Oct 2024 09:59:19 +0100 Subject: [PATCH 14/89] Keep prettier rules in a single location in .prettierrc. Also, added the typescript parser as override for .ts/.tsx files, which finally enabled me to correctly lint the typescript files. --- .eslintrc | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.eslintrc b/.eslintrc index 92c358655..c8f05bd85 100644 --- a/.eslintrc +++ b/.eslintrc @@ -40,15 +40,7 @@ "sourceType": "module" // Allows for the use of imports }, "rules": { - "prettier/prettier": [ - 2, - { - "semi": true, - "singleQuote": true, - "jsxSingleQuote": true, - "parser": "flow" - } - ], + "prettier/prettier": 2, "inclusive-language/use-inclusive-words": 2, "semi": [2, "always"], "jsx-quotes": [2, "prefer-single"], @@ -99,7 +91,12 @@ "files": ["**/*.ts", "**/*.tsx"], "plugins": ["@typescript-eslint"], "rules": { - "prettier/prettier": 2, + "prettier/prettier": [ + 1, + { + "parser": "typescript" + } + ], "@typescript-eslint/no-unused-vars": 2, "@typescript-eslint/no-non-null-assertion": 0 }, From 80ea196d5f53f61b17772e3f34f7712edcc8b450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20R=C3=BChl?= Date: Thu, 31 Oct 2024 12:04:07 +0100 Subject: [PATCH 15/89] Add another scenario to the AOI test --- test/playwright/tests/exploreAoi.spec.ts | 28 +++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/test/playwright/tests/exploreAoi.spec.ts b/test/playwright/tests/exploreAoi.spec.ts index 3a89d9a3d..936f3acaa 100644 --- a/test/playwright/tests/exploreAoi.spec.ts +++ b/test/playwright/tests/exploreAoi.spec.ts @@ -32,13 +32,11 @@ test.describe('Area of Interest (AOI) Analysis', () => { }); await test.step('Then the map should display the selected area as the AOI', async () => { - // const aoi = await page.$('selector-for-aoi'); // Adjust the selector as needed - // expect(aoi).not.toBeNull(); + // How to check if the pre-defined AOI is created? Can we access the canvas, or methods of mbDraw? await test.step('And the AOI should not be editable when clicking on it', async () => { - // await page.click('selector-for-aoi'); // Adjust the selector as needed - // const isEditable = await page.$eval('selector-for-aoi', el => console.log(el)); // Adjust the selector as needed - // expect(isEditable).toBe(false); + // How to check that the drawing mode did not change for the AOI? + // Can we access the canvas, or methods of mbDraw? }); }); @@ -57,4 +55,24 @@ test.describe('Area of Interest (AOI) Analysis', () => { 'And the "Run analysis" button should be shown' ).toBeVisible(); }); + + test('User draws AOI when pre-defined AOI exists', async ({ page }) => { + await test.step('Given that there is a pre-defined AOI on the map', async () => { + const toolbar = page.getByTestId('preset-selector'); + await toolbar.selectOption('Hawaii'); + }); + + await test.step('When I click on a pen tool to draw custom AOI', async () => { + await page.getByRole('button', { name: 'Draw AOI' }).click(); + }); + + await test.step('Then the AOI from pre-defined AOIs should be deleted', async () => { + // How to check if the pre-defined AOI is deleted? Can we access the canvas, or methods of mbDraw? + }); + + await test.step('And the pre-defined selector should be reset and display the placeholder text', async () => { + const toolbar = page.getByTestId('preset-selector'); + expect(toolbar).toHaveValue('Analyze an area'); + }); + }); }); From 2e42222136c9f294eef6e8cdc840eec603976148 Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Thu, 31 Oct 2024 17:07:02 +0100 Subject: [PATCH 16/89] Bump package json version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a0d719ab3..c3766e4c6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@developmentseed/veda-ui", "description": "Dashboard", - "version": "5.8.0", + "version": "5.9.1", "author": { "name": "Development Seed", "url": "https://developmentseed.org/" From 10a0aa379358c38f11ccd0ff5f07c8330aeefa43 Mon Sep 17 00:00:00 2001 From: snmln Date: Thu, 17 Oct 2024 08:59:11 -0400 Subject: [PATCH 17/89] removing fucntionality from layout --- .../common/cookie-consent/index.tsx | 186 ++++++++++++------ .../components/common/cookie-consent/utils.ts | 2 +- .../components/common/layout-root/index.tsx | 35 +--- 3 files changed, 125 insertions(+), 98 deletions(-) diff --git a/app/scripts/components/common/cookie-consent/index.tsx b/app/scripts/components/common/cookie-consent/index.tsx index 3190584dd..103228314 100644 --- a/app/scripts/components/common/cookie-consent/index.tsx +++ b/app/scripts/components/common/cookie-consent/index.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Icon } from '@trussworks/react-uswds'; -import { COOKIE_CONSENT_KEY } from './utils'; +import { COOKIE_CONSENT_KEY, SESSION_KEY } from './utils'; import { USWDSAlert, USWDSButton, @@ -12,22 +12,49 @@ import './index.scss'; interface CookieConsentProps { title?: string | undefined; copy?: string | undefined; - onFormInteraction: () => void; + setGoogleTagManager: () => void; +} +interface CookieConsentShape { + responded: boolean; + answer: boolean; } -function addAttribute (copy) { +function addAttribute(copy) { return copy.replaceAll(' { - const [cookieConsentResponded, SetCookieConsentResponded] = - useState(false); - const [cookieConsentAnswer, SetCookieConsentAnswer] = - useState(false); + const readCookie = (name) => { + const nameEQ = name + '='; + const attribute = document.cookie.split(';'); + for (let i = 0; i < attribute.length; i++) { + let c = attribute[i]; + while (c.charAt(0) == ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); + } + return null; + }; + + const getCookie = () => { + const cookie = readCookie(COOKIE_CONSENT_KEY); + if (cookie) { + const cookieContents: CookieConsentShape = JSON.parse(cookie); + if (cookieContents.answer) setGoogleTagManager(); + + return cookieContents; + } + }; + + const [cookieConsentResponded, SetCookieConsentResponded] = useState( + getCookie()?.responded ?? false + ); + const [cookieConsentAnswer, SetCookieConsentAnswer] = useState( + getCookie()?.answer ?? false + ); const [closeConsent, setCloseConsent] = useState(false); //Setting expiration date for cookie to expire and re-ask user for consent. const setCookieExpiration = () => { @@ -42,67 +69,96 @@ export const CookieConsent = ({ )}; path=/; expires=${closeConsent ? '0' : setCookieExpiration()}`; }; + const setSessionData = () => { + const checkForSessionDate = window.sessionStorage.getItem(SESSION_KEY); + if (!checkForSessionDate) { + window.sessionStorage.setItem(SESSION_KEY, 'true'); + return true; + } else { + return false; + } + }; + useEffect(() => { - const cookieValue = { - responded: cookieConsentResponded, - answer: cookieConsentAnswer - }; - setCookie(cookieValue, closeConsent); - onFormInteraction(); - // Ignoring setcookie for now sine it will make infinite rendering - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cookieConsentResponded, cookieConsentAnswer, closeConsent, onFormInteraction]); + setSessionData(); + if (setSessionData()) { + const cookieValue = { + responded: cookieConsentResponded, + answer: cookieConsentAnswer + }; + setCookie(cookieValue, closeConsent); + } + // Ignoring setcookie for now since it will make infinite rendering + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + cookieConsentResponded, + cookieConsentAnswer, + closeConsent, + getCookie, + setSessionData + ]); return ( -