From c0c867f7abcead518d8e9047dc3df4b0b70f769e Mon Sep 17 00:00:00 2001 From: Nanda Date: Tue, 19 Aug 2025 14:02:19 +1000 Subject: [PATCH 1/5] Add link component. --- lib/ReactViewModels/ViewState.ts | 10 +++++ .../SettingsPanelLinkCustomComponent.ts | 40 +++++++++++++++++++ .../Custom/registerCustomComponentTypes.ts | 2 + lib/ReactViews/Generic/FeatureLink.tsx | 37 +++++++++++++++++ lib/ReactViews/Map/Panels/SettingPanel.tsx | 4 ++ 5 files changed, 93 insertions(+) create mode 100644 lib/ReactViews/Custom/SettingsPanelLinkCustomComponent.ts create mode 100644 lib/ReactViews/Generic/FeatureLink.tsx diff --git a/lib/ReactViewModels/ViewState.ts b/lib/ReactViewModels/ViewState.ts index 11e97f1a133..1a651b359f5 100644 --- a/lib/ReactViewModels/ViewState.ts +++ b/lib/ReactViewModels/ViewState.ts @@ -367,6 +367,8 @@ export default class ViewState { */ @observable retainSharePanel: boolean = false; // The large share panel accessed via Share/Print button + @observable settingsPanelIsVisible: boolean = false; + /** * The currently open tool */ @@ -613,6 +615,14 @@ export default class ViewState { this.searchState.searchCatalog(); } + /** + * Open settings panel + */ + @action + openSettingsPanel(): void { + this.settingsPanelIsVisible = true; + } + @action clearPreviewedItem(): void { this.userDataPreviewedItem = undefined; diff --git a/lib/ReactViews/Custom/SettingsPanelLinkCustomComponent.ts b/lib/ReactViews/Custom/SettingsPanelLinkCustomComponent.ts new file mode 100644 index 00000000000..523d97f52e7 --- /dev/null +++ b/lib/ReactViews/Custom/SettingsPanelLinkCustomComponent.ts @@ -0,0 +1,40 @@ +import { action } from "mobx"; +import { createElement, ReactElement } from "react"; +import { ViewState } from "terriajs-plugin-api"; +import FeatureLink from "../Generic/FeatureLink"; +import CustomComponent, { + DomElement, + ProcessNodeContext +} from "./CustomComponent"; + +/** + * A `` custom component, that shows a link like button which when clicked + * opens the settings panel. + * + * Props accepted + * - title - The text to use as alt link content + * - children - The link text + * + * Example: Change base map + */ +export default class SettingsPanelLinkCustomComponent extends CustomComponent { + get name(): string { + return "settingspanel"; + } + + get attributes(): string[] { + return ["title"]; + } + + processNode( + _context: ProcessNodeContext, + node: DomElement, + children: ReactElement[] + ): ReactElement { + return createElement(FeatureLink, { + title: node.attribs?.title, + children, + onClick: action((viewState: ViewState) => viewState.openSettingsPanel()) + }); + } +} diff --git a/lib/ReactViews/Custom/registerCustomComponentTypes.ts b/lib/ReactViews/Custom/registerCustomComponentTypes.ts index 42f6df306fd..56f03adbcdb 100644 --- a/lib/ReactViews/Custom/registerCustomComponentTypes.ts +++ b/lib/ReactViews/Custom/registerCustomComponentTypes.ts @@ -8,6 +8,7 @@ import CsvChartCustomComponent from "./CsvChartCustomComponent"; import CustomComponent from "./CustomComponent"; import FeedbackLinkCustomComponent from "./FeedbackLinkCustomComponent"; import SOSChartCustomComponent from "./SOSChartCustomComponent"; +import SettingsPanelLinkCustomComponent from "./SettingsPanelLinkCustomComponent"; import TerriaTooltipCustomComponent from "./TerriaTooltip"; /** @@ -23,6 +24,7 @@ export default function registerCustomComponentTypes(terria?: Terria) { CustomComponent.register(new ApiTableChartCustomComponent()); CustomComponent.register(new CollapsibleCustomComponent()); CustomComponent.register(new FeedbackLinkCustomComponent()); + CustomComponent.register(new SettingsPanelLinkCustomComponent()); CustomComponent.register(new TerriaTooltipCustomComponent()); // At the time this is called `cesiumIonOAuth2ApplicationID` won't be populated yet. diff --git a/lib/ReactViews/Generic/FeatureLink.tsx b/lib/ReactViews/Generic/FeatureLink.tsx new file mode 100644 index 00000000000..58c1356070e --- /dev/null +++ b/lib/ReactViews/Generic/FeatureLink.tsx @@ -0,0 +1,37 @@ +import { FC, ReactNode } from "react"; +import styled from "styled-components"; +import { ViewState, useViewState } from "terriajs-plugin-api"; + +interface PropsType { + title?: string; + children: ReactNode | ReactNode[]; + onClick: (viewState: ViewState) => void; +} + +/** + * A button as link that provides common styling for custom component types + * that open a feature. + */ +const FeatureLink: FC = ({ title, onClick, children }) => { + const viewState = useViewState(); + return ( + { + e.stopPropagation(); + onClick(viewState); + }} + > + {children} + + ); +}; + +const ButtonAsLink = styled.button` + background: none; + border: none; + text-decoration: underline dashed; + color: inherit; +`; + +export default FeatureLink; diff --git a/lib/ReactViews/Map/Panels/SettingPanel.tsx b/lib/ReactViews/Map/Panels/SettingPanel.tsx index c7b73dcf90c..71b119d115d 100644 --- a/lib/ReactViews/Map/Panels/SettingPanel.tsx +++ b/lib/ReactViews/Map/Panels/SettingPanel.tsx @@ -212,6 +212,10 @@ const SettingPanel: FC = observer(() => { btnText={t("settingPanel.btnText")} viewState={viewState} smallScreen={viewState.useSmallScreenInterface} + isOpen={viewState.settingsPanelIsVisible} + onOpenChanged={action((isOpen: boolean) => { + viewState.settingsPanelIsVisible = isOpen; + })} > From cfc19f1e3a9bf143692b54cc14e8d6a5311fe0d9 Mon Sep 17 00:00:00 2001 From: Nanda Date: Tue, 19 Aug 2025 14:18:47 +1000 Subject: [PATCH 2/5] Fix imports. --- .../Custom/SettingsPanelLinkCustomComponent.ts | 15 +++++++++------ lib/ReactViews/Generic/FeatureLink.tsx | 5 +++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/ReactViews/Custom/SettingsPanelLinkCustomComponent.ts b/lib/ReactViews/Custom/SettingsPanelLinkCustomComponent.ts index 523d97f52e7..989ed92c5d9 100644 --- a/lib/ReactViews/Custom/SettingsPanelLinkCustomComponent.ts +++ b/lib/ReactViews/Custom/SettingsPanelLinkCustomComponent.ts @@ -1,6 +1,6 @@ import { action } from "mobx"; import { createElement, ReactElement } from "react"; -import { ViewState } from "terriajs-plugin-api"; +import ViewState from "../../ReactViewModels/ViewState"; import FeatureLink from "../Generic/FeatureLink"; import CustomComponent, { DomElement, @@ -31,10 +31,13 @@ export default class SettingsPanelLinkCustomComponent extends CustomComponent { node: DomElement, children: ReactElement[] ): ReactElement { - return createElement(FeatureLink, { - title: node.attribs?.title, - children, - onClick: action((viewState: ViewState) => viewState.openSettingsPanel()) - }); + return createElement( + FeatureLink, + { + title: node.attribs?.title, + onClick: action((viewState: ViewState) => viewState.openSettingsPanel()) + }, + ...children + ); } } diff --git a/lib/ReactViews/Generic/FeatureLink.tsx b/lib/ReactViews/Generic/FeatureLink.tsx index 58c1356070e..229899ea30a 100644 --- a/lib/ReactViews/Generic/FeatureLink.tsx +++ b/lib/ReactViews/Generic/FeatureLink.tsx @@ -1,10 +1,11 @@ import { FC, ReactNode } from "react"; import styled from "styled-components"; -import { ViewState, useViewState } from "terriajs-plugin-api"; +import ViewState from "../../ReactViewModels/ViewState"; +import { useViewState } from "../Context/ViewStateContext"; interface PropsType { title?: string; - children: ReactNode | ReactNode[]; + children?: ReactNode | ReactNode[]; onClick: (viewState: ViewState) => void; } From ca7b9fcda901b9ef2d9651ba474e6145c85dc425 Mon Sep 17 00:00:00 2001 From: Nanda Date: Tue, 4 Nov 2025 12:18:05 +1100 Subject: [PATCH 3/5] Add specs. --- lib/ReactViews/Custom/CustomComponent.ts | 7 +++ .../SettingsPanelCustomComponentSpec.ts | 52 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 test/ReactViews/Custom/SettingsPanelCustomComponentSpec.ts diff --git a/lib/ReactViews/Custom/CustomComponent.ts b/lib/ReactViews/Custom/CustomComponent.ts index c0dad95dd95..64da74487a2 100644 --- a/lib/ReactViews/Custom/CustomComponent.ts +++ b/lib/ReactViews/Custom/CustomComponent.ts @@ -103,6 +103,13 @@ export default abstract class CustomComponent { this._types.set(component.name, component); } + /** + * Unregister all components + */ + static unregisterAll() { + this._types.clear(); + } + /** * Checks if a custom component with a given name is registered. * @param name The name of the custom component. diff --git a/test/ReactViews/Custom/SettingsPanelCustomComponentSpec.ts b/test/ReactViews/Custom/SettingsPanelCustomComponentSpec.ts new file mode 100644 index 00000000000..2b182c41fcf --- /dev/null +++ b/test/ReactViews/Custom/SettingsPanelCustomComponentSpec.ts @@ -0,0 +1,52 @@ +import { screen } from "@testing-library/react"; +import { userEvent } from "@testing-library/user-event"; +import Terria from "../../../lib/Models/Terria"; +import ViewState from "../../../lib/ReactViewModels/ViewState"; +import parseCustomMarkdownToReact from "../../../lib/ReactViews/Custom/parseCustomMarkdownToReact"; +import registerCustomComponentTypes from "../../../lib/ReactViews/Custom/registerCustomComponentTypes"; +import { renderWithContexts } from "../withContext"; +import CustomComponent from "../../../lib/ReactViews/Custom/CustomComponent"; + +describe("SettingsPanelCustomComponent", function () { + let viewState: ViewState; + + beforeEach(function () { + viewState = new ViewState({ + terria: new Terria(), + catalogSearchProvider: undefined + }); + + registerCustomComponentTypes(viewState.terria); + }); + + afterEach(function () { + CustomComponent.unregisterAll(); + }); + + it("renders", function () { + renderWithContexts( + parseCustomMarkdownToReact( + "Click to open " + ), + viewState + ); + + const settingsLink = screen.queryByTitle("Settings"); + expect(settingsLink).toBeVisible(); + }); + + it("opens the settings panel when clicked", async function () { + renderWithContexts( + parseCustomMarkdownToReact( + "Click to open " + ), + viewState + ); + + const settingsLink = screen.queryByTitle("Settings"); + expect(settingsLink).toBeDefined(); + expect(viewState.settingsPanelIsVisible).toBe(false); + await userEvent.click(settingsLink!); + expect(viewState.settingsPanelIsVisible).toBe(true); + }); +}); From 1df749b3750ea63b543ef3ace9d43eed943255f9 Mon Sep 17 00:00:00 2001 From: Nanda Date: Wed, 5 Nov 2025 10:08:41 +1100 Subject: [PATCH 4/5] Update CHANGES.md. --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index a04de80ca75..d2606635854 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ - Update docs for Client-side config: change `searchBar` parameter to `searchBarConfig` - Fix to show preview map when used outside the explorer panel. - Update `csv-geo-au` support to include the latest Australian Government regions. +- Add `` custom component to open Map settings panel from template code (like short report, feature info etc). - [The next improvement] #### 8.11.0 - 2025-10-09 From 5424842b29fb8c6fe9efb9f6a9cf1d4be5ae7fc5 Mon Sep 17 00:00:00 2001 From: Nanda Date: Tue, 9 Dec 2025 21:27:22 +1100 Subject: [PATCH 5/5] Fix CHANGES.md. --- CHANGES.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9428cf03d11..d6590c799ae 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ - Add `token` to `ArcGisMapServerCatalogItem`, `ArcGisMapServerCatalogGroup`, `ArcGisFeatureServerCatalogItem`, `ArcGisFeatureServerCatalogGroup`, `ArcGisImageServerCatalogItem`, `I3SCatalogItem` and `ArcGisCatalogGroup` - if defined, it will be added to the `token` parameter for all ArcGIS Rest API requests. - Added `tokenUrl` to `ArcGisImageServerCatalogItem`, and tweaked behaviour in `ArcGisMapServerCatalogItem` and `ArcGisImageServerCatalogItem` so that if both `token` and `tokenUrl` are defined, then `tokenUrl` will be used. This allows the token to be refreshed if needed. - WMTS read URL from operations metadata #7371 +- Add `` custom component to open Map settings panel from template code (like short report, feature info etc). - [The next improvement] #### 8.11.1 - 2025-12-04 @@ -15,8 +16,6 @@ - Update docs for Client-side config: change `searchBar` parameter to `searchBarConfig` - Fix to show preview map when used outside the explorer panel. - Update `csv-geo-au` support to include the latest Australian Government regions. -- Add `` custom component to open Map settings panel from template code (like short report, feature info etc). -- [The next improvement] - Add `backgroundColor` trait to base maps for changing the map container background in 2D/Leaflet mode ([7718](https://github.com/TerriaJS/terriajs/pull/7718)) - Keep camera steady when switching between viewer modes. - Fix a bug where some georeferenced tiles where incorrectly positioned in Terria.