From 9e2c0a201df171e16e2b600aed641a9f294dd453 Mon Sep 17 00:00:00 2001 From: Tim Guite Date: Wed, 25 Mar 2020 16:27:40 +0000 Subject: [PATCH 1/3] Some initial interfaces, functions and tests Intended to provide better support for manipulating information in the URL than we have currently --- src/ui/widgets/urlControl.test.ts | 120 ++++++++++++++++++++++++++++++ src/ui/widgets/urlControl.ts | 47 ++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 src/ui/widgets/urlControl.test.ts create mode 100644 src/ui/widgets/urlControl.ts diff --git a/src/ui/widgets/urlControl.test.ts b/src/ui/widgets/urlControl.test.ts new file mode 100644 index 00000000..fd0260df --- /dev/null +++ b/src/ui/widgets/urlControl.test.ts @@ -0,0 +1,120 @@ +import { + getUrlInfoFromHistory, + putUrlInfoToHistory, + UrlInfo, + UrlPageDescription, + updatePageDesciption, + removePageDescription +} from "./urlControl"; +import { History } from "history"; + +const mockHistory = { + location: { + pathname: "mypath/" + }, + push(path: string): void { + this.location.pathname = path; + } +} as History; + +const mockInfo: UrlInfo = { + page1: { + filename: "page1", + filetype: "json", + macroMap: {} + }, + page2: { + filename: "page2", + filetype: "bob", + macroMap: {} + }, + page3: { + filename: "page3", + filetype: "opi", + macroMap: { + device: "example device" + } + } +}; + +beforeEach((): void => { + mockHistory.location.pathname = "mypath/"; +}); + +describe("interaction with history", (): void => { + it("puts one description in history", (): void => { + const desc: UrlPageDescription = { + filename: "file", + filetype: "json", + macroMap: { + device: "example device" + } + }; + const info: UrlInfo = { + desc: desc + }; + putUrlInfoToHistory(mockHistory, info); + expect(mockHistory.location.pathname).toBe( + "/" + encodeURIComponent(JSON.stringify(info)) + ); + }); + + it("returns nothing when path is blank", (): void => { + expect(getUrlInfoFromHistory(mockHistory)).toEqual({}); + }); + it("gets one page description properly", (): void => { + const desc: UrlPageDescription = { + filename: "file", + filetype: "json", + macroMap: { + device: "example device" + } + }; + const info: UrlInfo = { + desc: desc + }; + putUrlInfoToHistory(mockHistory, info); + expect(getUrlInfoFromHistory(mockHistory)).toEqual(info); + }); + it("gets multiple page descriptions properly", (): void => { + const desc: UrlPageDescription = { + filename: "file", + filetype: "json", + macroMap: { + device: "example device" + } + }; + const info: UrlInfo = { + desc: desc, + desc2: desc, + desc3: desc + }; + putUrlInfoToHistory(mockHistory, info); + expect(getUrlInfoFromHistory(mockHistory)).toEqual(info); + }); +}); + +describe("modifying UrlInfo object", (): void => { + it("adds a new page description", (): void => { + const desc: UrlPageDescription = { + filename: "page4", + filetype: "json", + macroMap: {} + }; + const info = updatePageDesciption(mockInfo, "page4", desc); + expect(info.page4).toStrictEqual(desc); + }); + it("modifies and existing page description", (): void => { + const desc: UrlPageDescription = { + filename: "page4", + filetype: "json", + macroMap: {} + }; + const info = updatePageDesciption(mockInfo, "page1", desc); + expect(info.page1).toStrictEqual(desc); + }); + it("removes an existing page description", (): void => { + const info = removePageDescription(mockInfo, "page2"); + expect(info.page2).toBeUndefined(); + }); +}); diff --git a/src/ui/widgets/urlControl.ts b/src/ui/widgets/urlControl.ts new file mode 100644 index 00000000..ae5338b5 --- /dev/null +++ b/src/ui/widgets/urlControl.ts @@ -0,0 +1,47 @@ +// File with useful functions for manipulating the path + +import { MacroMap } from "../../redux/csState"; +import { History } from "history"; + +export interface UrlPageDescription { + filename: string; + filetype?: "json" | "opi" | "bob"; + macroMap: MacroMap; +} + +export interface UrlInfo { + [key: string]: UrlPageDescription; +} + +// Functions for getting/putting url from/to history object +export function getUrlInfoFromHistory(history: History): UrlInfo { + const info = history.location.pathname + .split("/") + .slice(1) + .join("/"); + + if (info === "") { + return {}; + } else { + return JSON.parse(decodeURIComponent(info)) as UrlInfo; + } +} + +export function putUrlInfoToHistory(history: History, info: UrlInfo): void { + history.push("/" + encodeURIComponent(JSON.stringify(info))); +} + +// Add or update a page +export function updatePageDesciption( + info: UrlInfo, + page: string, + desc: UrlPageDescription +): UrlInfo { + return { ...info, [page]: desc }; +} + +// Remove a page +export function removePageDescription(info: UrlInfo, page: string): UrlInfo { + const { [page]: value, ...remainingInfo } = info; + return remainingInfo; +} From 5be6803f46c9108f69b30c9f36e383bec1ef09ab Mon Sep 17 00:00:00 2001 From: Tim Guite Date: Thu, 26 Mar 2020 12:15:58 +0000 Subject: [PATCH 2/3] Implemented these changes in actions, propTypes and dynamicPage Also changed app.tsx to prevent errors, have already made changes to some JSON files but more changes are to follow --- src/app.tsx | 9 ++-- src/ui/widgets/DynamicPage/dynamicPage.tsx | 39 ++++++++------ src/ui/widgets/propTypes.ts | 9 ++-- src/ui/widgets/urlControl.test.ts | 16 +++--- src/ui/widgets/urlControl.ts | 4 +- src/ui/widgets/widgetActions.ts | 60 +++++++++------------- 6 files changed, 67 insertions(+), 70 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index 2b0fcc26..50976f44 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -73,9 +73,12 @@ const App: React.FC = (): JSX.Element => { { type: OPEN_PAGE, openPageInfo: { - location: "app", - page: "menu", - macros: "{}" + page: "app", + pageDescription: { + filename: "menu", + filetype: "json", + macros: {} + } } } ] diff --git a/src/ui/widgets/DynamicPage/dynamicPage.tsx b/src/ui/widgets/DynamicPage/dynamicPage.tsx index 43632b5f..df08eb2f 100644 --- a/src/ui/widgets/DynamicPage/dynamicPage.tsx +++ b/src/ui/widgets/DynamicPage/dynamicPage.tsx @@ -1,6 +1,7 @@ import React, { useContext } from "react"; import log from "loglevel"; -import { Route, RouteComponentProps } from "react-router-dom"; +import { Route } from "react-router-dom"; +import { History } from "history"; import { Widget } from "../widget"; import { WidgetPropType } from "../widgetProps"; @@ -12,29 +13,34 @@ import { BaseUrlContext } from "../../../baseUrl"; import { EmbeddedDisplay } from "../EmbeddedDisplay/embeddedDisplay"; import { Color } from "../../../types/color"; import { RelativePosition } from "../../../types/position"; +import { getUrlInfoFromHistory, UrlPageDescription } from "../urlControl"; -export interface DynamicParams { - json: string; - macros?: string; -} - -export function DynamicPageFetch({ - match -}: RouteComponentProps): JSX.Element { +export function DynamicPageFetch(props: { + history: History; + routePath: string; +}): JSX.Element { const baseUrl = useContext(BaseUrlContext); - const file = `${baseUrl}/json/${match.params.json}.json`; - let map = {}; + const currentUrlInfo = getUrlInfoFromHistory(props.history); + let pageDesc: UrlPageDescription; + let file = ""; + let macros = {}; try { - map = match.params.macros && JSON.parse(match.params.macros); + pageDesc = currentUrlInfo[props.routePath]; + file = `${baseUrl}/json/${pageDesc.filename}.json`; + macros = pageDesc.macros ?? {}; + // const file = `${baseUrl}/json/${match.params.json}.json`; + // let map = {}; } catch (error) { - log.warn(match.params.json); + log.warn(currentUrlInfo); log.warn(error); + return
; } + return ( ); @@ -50,7 +56,6 @@ const DynamicPageComponent = ( ): JSX.Element => (
(
- +
)} /> diff --git a/src/ui/widgets/propTypes.ts b/src/ui/widgets/propTypes.ts index 727a7247..135bb82f 100644 --- a/src/ui/widgets/propTypes.ts +++ b/src/ui/widgets/propTypes.ts @@ -84,8 +84,11 @@ const OpenPagePropType = PropTypes.shape({ type: StringProp, openPageInfo: PropTypes.shape({ page: StringProp, - location: StringProp, - macros: StringProp, + pageDescription: PropTypes.shape({ + filename: PropTypes.string, + filetype: PropTypes.oneOf(["bob", "opi", "json"]), + macros: MacrosProp + }), description: StringPropOpt }).isRequired }); @@ -93,7 +96,7 @@ const OpenPagePropType = PropTypes.shape({ const ClosePagePropType = PropTypes.shape({ type: StringProp, closePageInfo: PropTypes.shape({ - location: StringProp, + page: StringProp, description: StringPropOpt }).isRequired }); diff --git a/src/ui/widgets/urlControl.test.ts b/src/ui/widgets/urlControl.test.ts index fd0260df..920f3ae3 100644 --- a/src/ui/widgets/urlControl.test.ts +++ b/src/ui/widgets/urlControl.test.ts @@ -21,17 +21,17 @@ const mockInfo: UrlInfo = { page1: { filename: "page1", filetype: "json", - macroMap: {} + macros: {} }, page2: { filename: "page2", filetype: "bob", - macroMap: {} + macros: {} }, page3: { filename: "page3", filetype: "opi", - macroMap: { + macros: { device: "example device" } } @@ -46,7 +46,7 @@ describe("interaction with history", (): void => { const desc: UrlPageDescription = { filename: "file", filetype: "json", - macroMap: { + macros: { device: "example device" } }; @@ -66,7 +66,7 @@ describe("interaction with history", (): void => { const desc: UrlPageDescription = { filename: "file", filetype: "json", - macroMap: { + macros: { device: "example device" } }; @@ -80,7 +80,7 @@ describe("interaction with history", (): void => { const desc: UrlPageDescription = { filename: "file", filetype: "json", - macroMap: { + macros: { device: "example device" } }; @@ -99,7 +99,7 @@ describe("modifying UrlInfo object", (): void => { const desc: UrlPageDescription = { filename: "page4", filetype: "json", - macroMap: {} + macros: {} }; const info = updatePageDesciption(mockInfo, "page4", desc); expect(info.page4).toStrictEqual(desc); @@ -108,7 +108,7 @@ describe("modifying UrlInfo object", (): void => { const desc: UrlPageDescription = { filename: "page4", filetype: "json", - macroMap: {} + macros: {} }; const info = updatePageDesciption(mockInfo, "page1", desc); expect(info.page1).toStrictEqual(desc); diff --git a/src/ui/widgets/urlControl.ts b/src/ui/widgets/urlControl.ts index ae5338b5..3cd73aca 100644 --- a/src/ui/widgets/urlControl.ts +++ b/src/ui/widgets/urlControl.ts @@ -5,8 +5,8 @@ import { History } from "history"; export interface UrlPageDescription { filename: string; - filetype?: "json" | "opi" | "bob"; - macroMap: MacroMap; + filetype: "json" | "opi" | "bob"; + macros: MacroMap; } export interface UrlInfo { diff --git a/src/ui/widgets/widgetActions.ts b/src/ui/widgets/widgetActions.ts index 0009e278..a930f8e1 100644 --- a/src/ui/widgets/widgetActions.ts +++ b/src/ui/widgets/widgetActions.ts @@ -3,6 +3,14 @@ import { valueToVtype } from "../../types/vtypes/utils"; import { History } from "history"; import log from "loglevel"; +import { + UrlPageDescription, + putUrlInfoToHistory, + updatePageDesciption, + removePageDescription, + getUrlInfoFromHistory +} from "./urlControl"; + export const OPEN_PAGE = "OPEN_PAGE"; export const CLOSE_PAGE = "CLOSE_PAGE"; export const OPEN_WEBPAGE = "OPEN_WEBPAGE"; @@ -18,8 +26,7 @@ export interface OpenPage { type: typeof OPEN_PAGE; openPageInfo: { page: string; - location: string; - macros: string; + pageDescription: UrlPageDescription; description: string; }; } @@ -27,7 +34,7 @@ export interface OpenPage { export interface ClosePage { type: typeof CLOSE_PAGE; closePageInfo: { - location: string; + page: string; description: string; }; } @@ -87,7 +94,7 @@ export const getActionDescription = (action: WidgetAction): string => { if (action.closePageInfo.description) { return action.closePageInfo.description; } else { - return `Open ${action.closePageInfo.location}`; + return `Open ${action.closePageInfo.page}`; } default: throw new InvalidAction(action); @@ -96,42 +103,21 @@ export const getActionDescription = (action: WidgetAction): string => { export const openPage = (action: OpenPage, history: History): void => { //Find current browser path: currentPath - let currentPath = ""; - if (history.location.pathname !== undefined) - currentPath = history.location.pathname; - const { location, page, macros } = action.openPageInfo; - - //New page component in action.location - const newPathComponent = location + "/" + page + "/" + macros + "/"; - - //Find existing component in same location - const matcher = new RegExp(location + "/[^/]*/[^/]*/"); - const groups = matcher.exec(currentPath); - if (groups !== null && groups[0] !== undefined) { - //Swap component in location - currentPath = currentPath.replace(groups[0], newPathComponent); - } else { - //Append component in location - currentPath = currentPath + newPathComponent; - } - history.push(currentPath); + const currentUrlInfo = getUrlInfoFromHistory(history); + const { page, pageDescription } = action.openPageInfo; + const newUrlInfo = updatePageDesciption( + currentUrlInfo, + page, + pageDescription + ); + putUrlInfoToHistory(history, newUrlInfo); }; export const closePage = (action: ClosePage, history: History): void => { - const { location } = action.closePageInfo; - //Find current browser path: currentPath - let currentPath = ""; - if (history.location.pathname !== undefined) - currentPath = history.location.pathname; - - //Find any existing component in action location - const matcher = new RegExp(location + "/[^/]*/[^/]*/"); - const groups = matcher.exec(currentPath); - if (groups !== null && groups[0] !== undefined) { - //Remove component in location - currentPath = currentPath.replace(groups[0], ""); - } - history.push(currentPath); + const currentUrlInfo = getUrlInfoFromHistory(history); + const { page } = action.closePageInfo; + const newUrlInfo = removePageDescription(currentUrlInfo, page); + putUrlInfoToHistory(history, newUrlInfo); }; export const executeAction = ( From dc1a6c3c423755303ddc51416f21e71038c8284e Mon Sep 17 00:00:00 2001 From: Tim Guite Date: Thu, 26 Mar 2020 12:27:37 +0000 Subject: [PATCH 3/3] Applied changes to JSON files --- public/json/menu.json | 27 ++++++++++------ public/json/motor.json | 11 +++++-- public/json/synoptic.json | 67 ++++++++++++++++++++++++--------------- 3 files changed, 68 insertions(+), 37 deletions(-) diff --git a/public/json/menu.json b/public/json/menu.json index 61e72190..46c88658 100644 --- a/public/json/menu.json +++ b/public/json/menu.json @@ -19,9 +19,12 @@ { "type": "OPEN_PAGE", "openPageInfo": { - "location": "app", - "page": "beamline", - "macros": "{}" + "page": "app", + "pageDescription": { + "filename": "beamline", + "filetype": "json", + "macros": {} + } } } ] @@ -44,9 +47,12 @@ { "type": "OPEN_PAGE", "openPageInfo": { - "location": "app", - "page": "frontPage", - "macros": "{}" + "page": "app", + "pageDescription": { + "filename": "frontPage", + "filetype": "json", + "macros": {} + } } } ] @@ -69,9 +75,12 @@ { "type": "OPEN_PAGE", "openPageInfo": { - "location": "app", - "page": "performance", - "macros": "{}" + "page": "app", + "pageDescription": { + "filename": "performance", + "filetype": "json", + "macros": {} + } } } ] diff --git a/public/json/motor.json b/public/json/motor.json index b44c63ac..54110d5c 100644 --- a/public/json/motor.json +++ b/public/json/motor.json @@ -197,9 +197,14 @@ { "type": "OPEN_PAGE", "openPageInfo": { - "location": "detail", - "page": "motorDetail", - "macros": "{\"motor\":\"${motor}\"}" + "page": "detail", + "pageDescription": { + "filename": "motorDetail", + "filetype": "json", + "macros": { + "motor": "${motor}" + } + } } } ] diff --git a/public/json/synoptic.json b/public/json/synoptic.json index e79c226f..84200c04 100644 --- a/public/json/synoptic.json +++ b/public/json/synoptic.json @@ -26,10 +26,13 @@ { "type": "OPEN_PAGE", "openPageInfo": { - "location": "embed", - "page": "motor", - "description": "Motor 1 Info", - "macros": "{\"motor\":\"1\"}" + "page": "embed", + "pageDescription": { + "filename": "motor", + "filetype": "json", + "description": "Motor 1 Info", + "macros": { "motor": "1" } + } } } ] @@ -51,10 +54,13 @@ { "type": "OPEN_PAGE", "openPageInfo": { - "location": "embed", - "page": "motor", - "description": "Motor 2 Info", - "macros": "{\"motor\":\"2\"}" + "page": "embed", + "pageDescription": { + "filename": "motor", + "filetype": "json", + "description": "Motor 2 Info", + "macros": { "motor": "2" } + } } } ] @@ -100,10 +106,13 @@ { "type": "OPEN_PAGE", "openPageInfo": { - "location": "embed", - "page": "motor", - "description": "Motor 3 Info", - "macros": "{\"motor\":\"3\"}" + "page": "embed", + "pageDescription": { + "filename": "motor", + "filetype": "json", + "description": "Motor 3 Info", + "macros": { "motor": "3" } + } } } ] @@ -125,10 +134,13 @@ { "type": "OPEN_PAGE", "openPageInfo": { - "location": "embed", - "page": "digitelmpc", - "description": "Digitel MPC Sim", - "macros": "{}" + "page": "embed", + "pageDescription": { + "filename": "digitelmpc", + "filetype": "json", + "description": "Digitel MPC Sim", + "macros": {} + } } } ] @@ -149,12 +161,14 @@ "actions": [ { "type": "OPEN_PAGE", - "openPageInfo": { - "location": "embed", - "page": "digitelmpcionp", - "description": "Digitel MPC Ionp Sim", - "macros": "{}" + "page": "embed", + "pageDescription": { + "filename": "digitelmpcionp", + "filetype": "json", + "description": "Digitel MPC Ionp Sim", + "macros": {} + } } } ] @@ -175,10 +189,13 @@ { "type": "OPEN_PAGE", "openPageInfo": { - "location": "embed", - "page": "motorSummary", - "macros": "{}", - "description": "Motor Summary Screen" + "page": "embed", + "pageDescription": { + "filename": "motorSummary", + "filetype": "json", + "macros": {}, + "description": "Motor Summary Screen" + } } } ]