Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<settingspanel>` custom component to open Map settings panel from template code (like short report, feature info etc).
- Add UI to show toast messages.
- [The next improvement]

Expand Down
10 changes: 10 additions & 0 deletions lib/ReactViewModels/ViewState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions lib/ReactViews/Custom/CustomComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
43 changes: 43 additions & 0 deletions lib/ReactViews/Custom/SettingsPanelLinkCustomComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { action } from "mobx";
import { createElement, ReactElement } from "react";
import ViewState from "../../ReactViewModels/ViewState";
import FeatureLink from "../Generic/FeatureLink";
import CustomComponent, {
DomElement,
ProcessNodeContext
} from "./CustomComponent";

/**
* A `<settingspanel>` 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: <settingspanel title="Open settings panel">Change base map</settingspanel>
*/
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,
onClick: action((viewState: ViewState) => viewState.openSettingsPanel())
},
...children
);
}
}
2 changes: 2 additions & 0 deletions lib/ReactViews/Custom/registerCustomComponentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand All @@ -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.
Expand Down
38 changes: 38 additions & 0 deletions lib/ReactViews/Generic/FeatureLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { FC, ReactNode } from "react";
import styled from "styled-components";
import ViewState from "../../ReactViewModels/ViewState";
import { useViewState } from "../Context/ViewStateContext";

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<PropsType> = ({ title, onClick, children }) => {
const viewState = useViewState();
return (
<ButtonAsLink
title={title}
onClick={(e) => {
e.stopPropagation();
onClick(viewState);
}}
>
{children}
</ButtonAsLink>
);
};

const ButtonAsLink = styled.button`
background: none;
border: none;
text-decoration: underline dashed;
color: inherit;
`;

export default FeatureLink;
4 changes: 4 additions & 0 deletions lib/ReactViews/Map/Panels/SettingPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
})}
>
<Box padded column>
<Box paddedVertically={1}>
Expand Down
52 changes: 52 additions & 0 deletions test/ReactViews/Custom/SettingsPanelCustomComponentSpec.ts
Original file line number Diff line number Diff line change
@@ -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 <settingspanel title='Settings' />"
),
viewState
);

const settingsLink = screen.queryByTitle("Settings");
expect(settingsLink).toBeVisible();
});

it("opens the settings panel when clicked", async function () {
renderWithContexts(
parseCustomMarkdownToReact(
"Click to open <settingspanel title='Settings' />"
),
viewState
);

const settingsLink = screen.queryByTitle("Settings");
expect(settingsLink).toBeDefined();
expect(viewState.settingsPanelIsVisible).toBe(false);
await userEvent.click(settingsLink!);
expect(viewState.settingsPanelIsVisible).toBe(true);
});
});
Loading