From 370769cf1b8d8feefc12c9ad388ea0712e906ccf Mon Sep 17 00:00:00 2001 From: Nick Forbes-Smith Date: Thu, 29 Aug 2024 18:35:54 +1000 Subject: [PATCH 1/2] Add shareClientBaseUrl --- lib/Models/InitSource.ts | 5 +++-- lib/Models/Terria.ts | 14 ++++++++++++-- .../Map/Panels/SharePanel/BuildShareLink.ts | 12 +++++++++--- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/Models/InitSource.ts b/lib/Models/InitSource.ts index dba9138ca28..d723ae398d3 100644 --- a/lib/Models/InitSource.ts +++ b/lib/Models/InitSource.ts @@ -38,9 +38,10 @@ export interface StoryData { title: string; text: string; id: string; - shareData: ShareInitSourceData; + shareData: StartData; } -export interface ShareInitSourceData { + +export interface StartData { version: string; /** Share data initSources can be a mix of initUrls (string) and initData (InitDataSource/JsonObject) */ initSources: (InitSourceData | string)[]; diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index ea1e1b2fdb5..fa7776c0feb 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -106,7 +106,7 @@ import IElementConfig from "./IElementConfig"; import InitSource, { InitSourceData, InitSourceFromData, - ShareInitSourceData, + StartData, StoryData, isInitFromData, isInitFromDataPromise, @@ -168,8 +168,17 @@ export interface ConfigParameters { * @deprecated */ proxyableDomainsUrl?: string; + /** URL to TerriaJS-server config. Defaults to `serverconfig/`. */ serverConfigUrl?: string; + /** + * URL of the service used to generate share links. This defaults to `share` if not specified, which maps to TerriaJS Server `share` endpoint. + */ shareUrl?: string; + /** + * Base URL of the client application used to generate share links. If not specified, the current page base URI will be used. + * For example, if `shareClientBaseUrl` is `http://example.com/`, then a share link will be generated as `http://example.com/#share=...`. + */ + shareClientBaseUrl?: string; /** * URL of the service used to send feedback. If not specified, the "Give Feedback" button will not appear. */ @@ -506,6 +515,7 @@ export default class Terria { proxyableDomainsUrl: "proxyabledomains/", // deprecated, will be determined from serverconfig serverConfigUrl: "serverconfig/", shareUrl: "share", + shareClientBaseUrl: undefined, feedbackUrl: undefined, initFragmentPaths: ["init/"], storyEnabled: true, @@ -2259,7 +2269,7 @@ async function interpretStartData( ) { if (isJsonObject(startData, false)) { // Convert startData to v8 if necessary - let startDataV8: ShareInitSourceData | null; + let startDataV8: StartData | null; try { if ( // If startData.version has version 0.x.x - user catalog-converter to convert startData diff --git a/lib/ReactViews/Map/Panels/SharePanel/BuildShareLink.ts b/lib/ReactViews/Map/Panels/SharePanel/BuildShareLink.ts index 308bafc2c89..3c3a736c50e 100644 --- a/lib/ReactViews/Map/Panels/SharePanel/BuildShareLink.ts +++ b/lib/ReactViews/Map/Panels/SharePanel/BuildShareLink.ts @@ -17,7 +17,7 @@ import HasLocalData from "../../../../Models/HasLocalData"; import { InitSourceData, InitSourcePickedFeatures, - ShareInitSourceData, + StartData, ViewModeJson } from "../../../../Models/InitSource"; import Terria from "../../../../Models/Terria"; @@ -38,7 +38,13 @@ function buildBaseShareUrl( terria: Terria, hashParams: { [key: string]: string } ) { - const uri = new URI(document.baseURI).fragment("").search(""); + let baseUrl = document.baseURI; + + if (terria.configParameters.shareClientBaseUrl) { + baseUrl = terria.configParameters.shareClientBaseUrl; + } + + const uri = new URI(baseUrl).fragment("").search(""); if (terria.developmentEnv) { uri.addSearch(toJS(terria.userProperties)); @@ -109,7 +115,7 @@ export function getShareData( terria: Terria, viewState?: ViewState, options = { includeStories: true } -): ShareInitSourceData { +): StartData { return runInAction(() => { const { includeStories } = options; const initSource: InitSourceData = {}; From 28ead9cec1fd6faa31f9692a082f21752836cf77 Mon Sep 17 00:00:00 2001 From: Nick Forbes-Smith Date: Thu, 29 Aug 2024 18:38:14 +1000 Subject: [PATCH 2/2] TSify MenuBar and Groups + add elements config to MenuBar --- CHANGES.md | 3 +- .../Map/MenuBar/HelpButton/HelpButton.tsx | 3 +- lib/ReactViews/Map/MenuBar/MenuBar.d.ts | 11 ----- .../Map/MenuBar/{MenuBar.jsx => MenuBar.tsx} | 47 ++++++++++--------- .../Map/MenuBar/StoryButton/StoryButton.tsx | 7 +-- .../Map/Panels/LangPanel/LangPanel.tsx | 3 +- lib/ReactViews/Map/Panels/SettingPanel.tsx | 3 +- .../Map/Panels/SharePanel/SharePanel.tsx | 11 +++-- .../Map/Panels/ToolsPanel/ToolsPanel.jsx | 3 +- lib/ReactViews/Mobile/MobileMenu.jsx | 3 ++ .../customizable/{Groups.jsx => Groups.tsx} | 22 +++++++-- .../processCustomElements.jsx | 38 +++++++++------ 12 files changed, 89 insertions(+), 65 deletions(-) delete mode 100644 lib/ReactViews/Map/MenuBar/MenuBar.d.ts rename lib/ReactViews/Map/MenuBar/{MenuBar.jsx => MenuBar.tsx} (76%) rename lib/ReactViews/StandardUserInterface/customizable/{Groups.jsx => Groups.tsx} (60%) diff --git a/CHANGES.md b/CHANGES.md index 0f7d555feb7..c4e92504ea1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,8 @@ #### next release (8.7.6) - Set default value for date and datetime WPS fields only when the field is marked as required. +- TSify MenuBar and Groups +- Add elements config for MenuBar - [The next improvement] #### 8.7.5 - 2024-06-26 @@ -12,7 +14,6 @@ - Show rectangle selector for WPS bounding box parameter - Fix `store` and `status` values send in WPS Execute request. - Add docs for `modelDimensions` -- [The next improvement] #### 8.7.4 - 2024-06-07 diff --git a/lib/ReactViews/Map/MenuBar/HelpButton/HelpButton.tsx b/lib/ReactViews/Map/MenuBar/HelpButton/HelpButton.tsx index 2bfb674249a..2d40d262980 100644 --- a/lib/ReactViews/Map/MenuBar/HelpButton/HelpButton.tsx +++ b/lib/ReactViews/Map/MenuBar/HelpButton/HelpButton.tsx @@ -7,6 +7,7 @@ import Prompt from "../../../Generic/Prompt"; import { useViewState } from "../../../Context"; import Styles from "./help-button.scss"; +import withControlledVisibility from "../../../HOCs/withControlledVisibility"; const HelpButton = observer(() => { const { t } = useTranslation(); @@ -48,4 +49,4 @@ const HelpButton = observer(() => { ); }); -export default HelpButton; +export default withControlledVisibility(HelpButton); diff --git a/lib/ReactViews/Map/MenuBar/MenuBar.d.ts b/lib/ReactViews/Map/MenuBar/MenuBar.d.ts deleted file mode 100644 index a1d8f37a149..00000000000 --- a/lib/ReactViews/Map/MenuBar/MenuBar.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import IElementConfig from "../../../Models/IElementConfig"; - -interface PropsType { - animationDuration?: number; - menuItems: React.ReactElement[]; - menuLeftItems: React.ReactElement[]; - elementConfig?: IElementConfig; -} - -declare const MenuBar: React.FC; -export default MenuBar; diff --git a/lib/ReactViews/Map/MenuBar/MenuBar.jsx b/lib/ReactViews/Map/MenuBar/MenuBar.tsx similarity index 76% rename from lib/ReactViews/Map/MenuBar/MenuBar.jsx rename to lib/ReactViews/Map/MenuBar/MenuBar.tsx index 52d6ab42eeb..7b4b57e91eb 100644 --- a/lib/ReactViews/Map/MenuBar/MenuBar.jsx +++ b/lib/ReactViews/Map/MenuBar/MenuBar.tsx @@ -1,21 +1,21 @@ import classNames from "classnames"; import { runInAction } from "mobx"; import { observer } from "mobx-react"; -import PropTypes from "prop-types"; import React from "react"; -import styled from "styled-components"; -import withControlledVisibility from "../../HOCs/withControlledVisibility"; +import styled, { useTheme } from "styled-components"; import { useViewState } from "../../Context"; +import withControlledVisibility from "../../HOCs/withControlledVisibility"; import LangPanel from "../Panels/LangPanel/LangPanel"; import SettingPanel from "../Panels/SettingPanel"; import SharePanel from "../Panels/SharePanel/SharePanel"; import ToolsPanel from "../Panels/ToolsPanel/ToolsPanel"; -import StoryButton from "./StoryButton/StoryButton"; import HelpButton from "./HelpButton/HelpButton"; +import StoryButton from "./StoryButton/StoryButton"; +import IElementConfig from "../../../Models/IElementConfig"; import Styles from "./menu-bar.scss"; -const StyledMenuBar = styled.div` +const StyledMenuBar = styled.div<{ trainerBarVisible: boolean }>` pointer-events: none; ${(p) => p.trainerBarVisible && @@ -23,8 +23,17 @@ const StyledMenuBar = styled.div` top: ${Number(p.theme.trainerHeight) + Number(p.theme.mapButtonTop)}px; `} `; + +interface PropsType { + animationDuration?: number; + menuItems: React.ReactElement[]; + menuLeftItems: React.ReactElement[]; + elementConfig?: IElementConfig; +} + // The map navigation region -const MenuBar = observer((props) => { +const MenuBar = observer((props: PropsType) => { + const theme = useTheme(); const viewState = useViewState(); const terria = viewState.terria; const menuItems = props.menuItems || []; @@ -53,7 +62,7 @@ const MenuBar = observer((props) => {
    {enableTools && (
  • - +
  • )} {!viewState.useSmallScreenInterface && @@ -67,10 +76,14 @@ const MenuBar = observer((props) => {
    • - +
    • - +
    • {terria.configParameters?.languageConfiguration?.enabled ? ( @@ -78,6 +91,7 @@ const MenuBar = observer((props) => { ) : null} @@ -88,18 +102,15 @@ const MenuBar = observer((props) => {
    )}
    • - +
    {!viewState.useSmallScreenInterface && @@ -112,11 +123,5 @@ const MenuBar = observer((props) => { ); }); -MenuBar.displayName = "MenuBar"; -MenuBar.propTypes = { - animationDuration: PropTypes.number, - menuItems: PropTypes.arrayOf(PropTypes.element), - menuLeftItems: PropTypes.arrayOf(PropTypes.element) -}; export default withControlledVisibility(MenuBar); diff --git a/lib/ReactViews/Map/MenuBar/StoryButton/StoryButton.tsx b/lib/ReactViews/Map/MenuBar/StoryButton/StoryButton.tsx index f2906d7135c..c189988d43f 100644 --- a/lib/ReactViews/Map/MenuBar/StoryButton/StoryButton.tsx +++ b/lib/ReactViews/Map/MenuBar/StoryButton/StoryButton.tsx @@ -11,12 +11,13 @@ import Prompt from "../../../Generic/Prompt"; import { useRefForTerria } from "../../../Hooks/useRefForTerria"; import Styles from "./story-button.scss"; +import withControlledVisibility from "../../../HOCs/withControlledVisibility"; -interface Props { +interface Props { terria: Terria; theme: DefaultTheme; viewState: ViewState; - animationDuration: number; + animationDuration?: number; } interface ButtonProps extends Props { @@ -110,4 +111,4 @@ const StoryButton = (props: Props) => { ); }; -export default StoryButton; +export default withControlledVisibility(StoryButton); diff --git a/lib/ReactViews/Map/Panels/LangPanel/LangPanel.tsx b/lib/ReactViews/Map/Panels/LangPanel/LangPanel.tsx index 421243fe6df..f1050fb28c0 100644 --- a/lib/ReactViews/Map/Panels/LangPanel/LangPanel.tsx +++ b/lib/ReactViews/Map/Panels/LangPanel/LangPanel.tsx @@ -7,6 +7,7 @@ import Icon from "../../../../Styled/Icon"; import Ul, { Li } from "../../../../Styled/List"; import MenuPanel from "../../../StandardUserInterface/customizable/MenuPanel"; import Styles from "../../MenuBar/menu-bar.scss"; +import withControlledVisibility from "../../../HOCs/withControlledVisibility"; const stripLangLocale = (lang: string = ""): string => lang.split("-")[0]; @@ -61,4 +62,4 @@ const LangPanel = (props: Props) => { ); }; -export default LangPanel; +export default withControlledVisibility (LangPanel); diff --git a/lib/ReactViews/Map/Panels/SettingPanel.tsx b/lib/ReactViews/Map/Panels/SettingPanel.tsx index 48c9cd436a1..511cf8e303b 100644 --- a/lib/ReactViews/Map/Panels/SettingPanel.tsx +++ b/lib/ReactViews/Map/Panels/SettingPanel.tsx @@ -30,6 +30,7 @@ import Text, { TextSpan } from "../../../Styled/Text"; import withTerriaRef from "../../HOCs/withTerriaRef"; import MenuPanel from "../../StandardUserInterface/customizable/MenuPanel"; import Styles from "./setting-panel.scss"; +import withControlledVisibility from "../../HOCs/withControlledVisibility"; const sides = { left: "settingPanel.terrain.left", @@ -413,7 +414,7 @@ class SettingPanel extends React.Component { export const SETTING_PANEL_NAME = "MenuBarMapSettingsButton"; export default withTranslation()( - withTheme(withTerriaRef(SettingPanel, SETTING_PANEL_NAME)) + withTheme(withTerriaRef(withControlledVisibility(SettingPanel), SETTING_PANEL_NAME)) ); type IFlexGrid = { diff --git a/lib/ReactViews/Map/Panels/SharePanel/SharePanel.tsx b/lib/ReactViews/Map/Panels/SharePanel/SharePanel.tsx index a70f16cf43f..cb802c2395b 100644 --- a/lib/ReactViews/Map/Panels/SharePanel/SharePanel.tsx +++ b/lib/ReactViews/Map/Panels/SharePanel/SharePanel.tsx @@ -12,6 +12,7 @@ import { canShorten } from "./BuildShareLink"; import Styles from "./share-panel.scss"; import { SharePanelContent } from "./SharePanelContent"; import { ShareUrl } from "./ShareUrl"; +import withControlledVisibility from "../../../HOCs/withControlledVisibility"; const MenuPanel = require("../../../StandardUserInterface/customizable/MenuPanel").default; @@ -19,13 +20,13 @@ const StorySharePanel = require("./StorySharePanel").default; interface PropTypes extends WithTranslation { terria: Terria; - storyShare: boolean; + storyShare?: boolean; catalogShare?: boolean; catalogShareWithoutText?: boolean; - modalWidth: number; + modalWidth?: number; viewState: ViewState; - onUserClick: () => void; - btnDisabled: boolean; + onUserClick?: () => void; + btnDisabled?: boolean; t: TFunction; } @@ -184,7 +185,7 @@ class SharePanel extends React.Component { } } -export default withTranslation()(SharePanel); +export default withControlledVisibility(withTranslation()(SharePanel)) export function shouldShorten(terria: Terria) { return ( diff --git a/lib/ReactViews/Map/Panels/ToolsPanel/ToolsPanel.jsx b/lib/ReactViews/Map/Panels/ToolsPanel/ToolsPanel.jsx index af3f33d340a..b9acf57434c 100644 --- a/lib/ReactViews/Map/Panels/ToolsPanel/ToolsPanel.jsx +++ b/lib/ReactViews/Map/Panels/ToolsPanel/ToolsPanel.jsx @@ -8,6 +8,7 @@ import { useViewState } from "../../../Context"; import DropdownStyles from "../panel.scss"; import CountDatasets from "./CountDatasets"; import Styles from "./tools-panel.scss"; +import withControlledVisibility from "../../../HOCs/withControlledVisibility"; const ToolsPanel = observer(() => { const [isOpen, setIsOpen] = useState(false); @@ -45,4 +46,4 @@ const ToolsPanel = observer(() => { ); }); -export default ToolsPanel; +export default withControlledVisibility(ToolsPanel); diff --git a/lib/ReactViews/Mobile/MobileMenu.jsx b/lib/ReactViews/Mobile/MobileMenu.jsx index 319aabeace5..d134866b498 100644 --- a/lib/ReactViews/Mobile/MobileMenu.jsx +++ b/lib/ReactViews/Mobile/MobileMenu.jsx @@ -128,12 +128,14 @@ class MobileMenu extends React.Component {
    this.hideMenu()}>
    {this.props.menuItems.map((menuItem) => ( @@ -165,6 +167,7 @@ class MobileMenu extends React.Component { )} diff --git a/lib/ReactViews/StandardUserInterface/customizable/Groups.jsx b/lib/ReactViews/StandardUserInterface/customizable/Groups.tsx similarity index 60% rename from lib/ReactViews/StandardUserInterface/customizable/Groups.jsx rename to lib/ReactViews/StandardUserInterface/customizable/Groups.tsx index 20257c1f588..740e9210552 100644 --- a/lib/ReactViews/StandardUserInterface/customizable/Groups.jsx +++ b/lib/ReactViews/StandardUserInterface/customizable/Groups.tsx @@ -5,13 +5,25 @@ * and puts them in the correct place. */ +import { ReactElement } from "react"; + /** No-op grouping element for elements that should be added to the menu */ -export function Menu() {} +export function Menu({ children }: { children: ReactElement | null }) { + return children; +} /** No-op grouping element for elements that should be added to the left menu */ -export function MenuLeft() {} +export function MenuLeft({ children }: { children: ReactElement | null }) { + return children; +} /** No-op grouping element for elements that should be added to the nav*/ -export function Nav() {} +export function Nav({ children }: { children: ReactElement | null }) { + return children; +} /** No-op grouping element for elements that should be added to the experimental features*/ -export function ExperimentalMenu() {} +export function ExperimentalMenu({ children }: { children: ReactElement | null }) { + return children; +} /** No-op grouping element for elements that should be added to the Feedback*/ -export function Feedback() {} +export function Feedback({ children }: { children: ReactElement | null }) { + return children; +} diff --git a/lib/ReactViews/StandardUserInterface/processCustomElements.jsx b/lib/ReactViews/StandardUserInterface/processCustomElements.jsx index caaa1b23a85..8e872c0ab99 100644 --- a/lib/ReactViews/StandardUserInterface/processCustomElements.jsx +++ b/lib/ReactViews/StandardUserInterface/processCustomElements.jsx @@ -56,6 +56,28 @@ function findKeyForGroupElement(groupElement) { )[0]; } +function processChildren(child, isSmallScreen) { + if (typeof child === "string") { + return {child}; + } else if ( + child && + child.type.propTypes && + child.type.propTypes.smallScreen + ) { + return React.cloneElement(child, { + smallScreen: isSmallScreen + }); + } + // IF child is react fragment, then unpack + else if (child && child.type === React.Fragment) { + return React.Children.map(child.props.children, (child) => processChildren(child, isSmallScreen)); + } + + else { + return child; + } +} + /** * Gets the children out of a grouping element and sanitises them - e.g. plain strings are converted to s and * elements that need to know about whether we're in small screen configuration are provided with that prop. @@ -65,19 +87,5 @@ function findKeyForGroupElement(groupElement) { * @returns {Array} a collection of processed children. */ function getGroupChildren(isSmallScreen, groupElement) { - return React.Children.map(groupElement.props.children, (child) => { - if (typeof child === "string") { - return {child}; - } else if ( - child && - child.type.propTypes && - child.type.propTypes.smallScreen - ) { - return React.cloneElement(child, { - smallScreen: isSmallScreen - }); - } else { - return child; - } - }); + return React.Children.map(groupElement.props.children, (child) => processChildren(child, isSmallScreen)); }