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
9 changes: 9 additions & 0 deletions src/fileContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* It is provided to widgets using a React context.
*/
import React from "react";
import log from "loglevel";
import { useHistory, useLocation } from "react-router-dom";
import { MacroMap, macrosEqual } from "./types/macros";

Expand Down Expand Up @@ -280,3 +281,11 @@ export const FileProvider: React.FC<FileProviderProps> = (
</FileContext.Provider>
);
};

// Special context for exit buttons.
// A widget can register itself as handling exit actions and provide
// the function to do so via this context.
export type ExitContextType = () => void;
export const ExitFileContext = React.createContext(() => {
log.warn("Exit action has no consumer.");
});
10 changes: 8 additions & 2 deletions src/ui/widgets/ActionButton/actionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Color } from "../../../types/color";
import { Font } from "../../../types/font";
import { Border, BorderStyle } from "../../../types/border";
import { MacroContext } from "../../../types/macros";
import { FileContext } from "../../../fileContext";
import { ExitFileContext, FileContext } from "../../../fileContext";

export interface ActionButtonProps {
text: string;
Expand Down Expand Up @@ -89,10 +89,16 @@ export const ActionButtonWidget = (
): JSX.Element => {
// Function to send the value on to the PV
const files = useContext(FileContext);
const exitContext = useContext(ExitFileContext);
const parentMacros = useContext(MacroContext).macros;
function onClick(event: React.MouseEvent<HTMLButtonElement>): void {
if (props.actions !== undefined)
executeActions(props.actions as WidgetActions, files, parentMacros);
executeActions(
props.actions as WidgetActions,
files,
exitContext,
parentMacros
);
}
return (
<ActionButtonComponent
Expand Down
70 changes: 37 additions & 33 deletions src/ui/widgets/DynamicPage/dynamicPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { StringProp, InferWidgetProps, BorderPropOpt } from "../propTypes";
import { EmbeddedDisplay } from "../EmbeddedDisplay/embeddedDisplay";
import { Color } from "../../../types/color";
import { RelativePosition } from "../../../types/position";
import { FileContext } from "../../../fileContext";
import { ExitFileContext, FileContext } from "../../../fileContext";

const DynamicPageProps = {
location: StringProp,
Expand Down Expand Up @@ -48,40 +48,44 @@ export const DynamicPageComponent = (
);
} else {
return (
<div style={style}>
<EmbeddedDisplay file={file} position={new RelativePosition()} />
<div
style={{
position: "absolute",
right: "5px",
top: "5px",
width: "25px",
height: "25px",
backgroundColor: "green"
}}
>
<ActionButton
position={new RelativePosition("25px", "25px")}
backgroundColor={new Color("var(--light-background)")}
foregroundColor={new Color("#ffffff")}
actions={{
executeAsOne: false,
actions: [
{
type: CLOSE_PAGE,
dynamicInfo: {
name: props.location,
location: props.location,
file: file,
description: "Close"
}
}
]
<ExitFileContext.Provider
value={() => fileContext.removePage(props.location)}
>
<div style={style}>
<EmbeddedDisplay file={file} position={new RelativePosition()} />
<div
style={{
position: "absolute",
right: "5px",
top: "5px",
width: "25px",
height: "25px",
backgroundColor: "green"
}}
image="/img/x.png"
/>
>
<ActionButton
position={new RelativePosition("25px", "25px")}
backgroundColor={new Color("var(--light-background)")}
foregroundColor={new Color("#ffffff")}
actions={{
executeAsOne: false,
actions: [
{
type: CLOSE_PAGE,
dynamicInfo: {
name: props.location,
location: props.location,
file: file,
description: "Close"
}
}
]
}}
image="/img/x.png"
/>
</div>
</div>
</div>
</ExitFileContext.Provider>
);
}
};
Expand Down
34 changes: 32 additions & 2 deletions src/ui/widgets/EmbeddedDisplay/opiParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import {
WRITE_PV,
OPEN_WEBPAGE,
WidgetActions,
OPEN_TAB
OPEN_TAB,
EXIT
} from "../widgetActions";

export interface XmlDescription {
Expand Down Expand Up @@ -578,7 +579,36 @@ function opiPatchPaths(
}
}

export const OPI_PATCHERS: PatchFunction[] = [opiPatchRules, opiPatchPaths];
function opiPatchActions(widgetDescription: WidgetDescription): void {
if (
widgetDescription.type === "actionbutton" &&
widgetDescription.text &&
widgetDescription.text.toLowerCase() === "exit"
) {
if (
!widgetDescription.actions ||
widgetDescription.actions.actions.length === 0
) {
widgetDescription.actions = {
executeAsOne: false,
actions: [
{
type: EXIT,
exitInfo: {
description: "Exit"
}
}
]
};
}
}
}

export const OPI_PATCHERS: PatchFunction[] = [
opiPatchRules,
opiPatchPaths,
opiPatchActions
];

export function parseOpi(
xmlString: string,
Expand Down
10 changes: 8 additions & 2 deletions src/ui/widgets/SimpleSymbol/simpleSymbol.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { registerWidget } from "../register";
import { executeActions, WidgetActions } from "../widgetActions";
import { MacroContext } from "../../../types/macros";
import { FileContext } from "../../../fileContext";
import { ExitFileContext, FileContext } from "../../../fileContext";

const SimpleSymbolProps = {
imageFile: StringProp,
Expand Down Expand Up @@ -45,10 +45,16 @@ export const SimpleSymbolComponent = (
props: SimpleSymbolComponentProps
): JSX.Element => {
const files = useContext(FileContext);
const exitContext = useContext(ExitFileContext);
const parentMacros = useContext(MacroContext).macros;
function onClick(event: React.MouseEvent<HTMLDivElement>): void {
if (props.actions !== undefined) {
executeActions(props.actions as WidgetActions, files, parentMacros);
executeActions(
props.actions as WidgetActions,
files,
exitContext,
parentMacros
);
}
}
// Render the imageIndex-th part of the larger png.
Expand Down
10 changes: 8 additions & 2 deletions src/ui/widgets/Symbol/symbol.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { LabelComponent } from "../Label/label";
import { Color } from "../../../types/color";
import { executeActions, WidgetActions } from "../widgetActions";
import { MacroContext } from "../../../types/macros";
import { FileContext } from "../../../fileContext";
import { ExitFileContext, FileContext } from "../../../fileContext";
import { DType } from "../../../types/dtypes";

const SymbolProps = {
Expand Down Expand Up @@ -101,10 +101,16 @@ export const SymbolComponent = (props: SymbolComponentProps): JSX.Element => {
}

const files = useContext(FileContext);
const exitContext = useContext(ExitFileContext);
const parentMacros = useContext(MacroContext).macros;
function onClick(event: React.MouseEvent<HTMLDivElement>): void {
if (props.actions !== undefined) {
executeActions(props.actions as WidgetActions, files, parentMacros);
executeActions(
props.actions as WidgetActions,
files,
exitContext,
parentMacros
);
}
}

Expand Down
26 changes: 16 additions & 10 deletions src/ui/widgets/Tabs/dynamicTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import { EmbeddedDisplay } from "../EmbeddedDisplay/embeddedDisplay";
import { RelativePosition } from "../../../types/position";

import { FileContext } from "../../../fileContext";
import { ExitFileContext, FileContext } from "../../../fileContext";
import { TabBar } from "./tabs";

export const DynamicTabsProps = {
Expand Down Expand Up @@ -76,17 +76,23 @@ export const DynamicTabsComponent = (
const [tabName, fileDesc] = openTabs[index];
fileContext.removeTab(props.location, tabName, fileDesc);
};
const closeCurrentTab = (): void => {
const [tabName, fileDesc] = openTabs[selectedTab];
fileContext.removeTab(props.location, tabName, fileDesc);
};

return (
<div style={containerStyle}>
<TabBar
tabNames={tabNames}
selectedTab={selectedTab}
onTabSelected={onTabSelected}
onTabClosed={onTabClosed}
></TabBar>
{children[selectedTab]}
</div>
<ExitFileContext.Provider value={() => closeCurrentTab()}>
<div style={containerStyle}>
<TabBar
tabNames={tabNames}
selectedTab={selectedTab}
onTabSelected={onTabSelected}
onTabClosed={onTabClosed}
></TabBar>
{children[selectedTab]}
</div>
</ExitFileContext.Provider>
);
}
};
Expand Down
5 changes: 3 additions & 2 deletions src/ui/widgets/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Color } from "../../types/color";
import { AlarmQuality } from "../../types/dtypes";
import { Font } from "../../types/font";
import { OutlineContext } from "../../outlineContext";
import { FileContext } from "../../fileContext";
import { ExitFileContext, FileContext } from "../../fileContext";
import { executeAction, WidgetAction, WidgetActions } from "./widgetActions";
import { Popover } from "react-tiny-popover";
import { resolveTooltip } from "./tooltip";
Expand Down Expand Up @@ -185,6 +185,7 @@ export const Widget = (
): JSX.Element => {
const [id] = useId();
const files = useContext(FileContext);
const exitContext = useContext(ExitFileContext);

// Logic for context menu.
const [contextOpen, setContextOpen] = useState(false);
Expand Down Expand Up @@ -218,7 +219,7 @@ export const Widget = (
}

function triggerCallback(action: WidgetAction): void {
executeAction(action, files);
executeAction(action, files, exitContext);
setContextOpen(false);
}
let tooltip = props.tooltip;
Expand Down
29 changes: 26 additions & 3 deletions src/ui/widgets/widgetActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import log from "loglevel";
import { MacroMap } from "../../types/macros";
import { DType } from "../../types/dtypes";
import { DynamicContent } from "./propTypes";
import { FileContextType } from "../../fileContext";
import { ExitContextType, FileContextType } from "../../fileContext";

export const OPEN_PAGE = "OPEN_PAGE";
export const CLOSE_PAGE = "CLOSE_PAGE";
export const OPEN_TAB = "OPEN_TAB";
export const CLOSE_TAB = "CLOSE_TAB";
export const OPEN_WEBPAGE = "OPEN_WEBPAGE";
export const WRITE_PV = "WRITE_PV";
export const EXIT = "EXIT";

/* Giving info properties to each of the following works around a
difficulty with TypeScript and PropTypes, where there's a problem
Expand Down Expand Up @@ -46,7 +47,14 @@ export interface WritePv {
};
}

export type WidgetAction = OpenWebpage | WritePv | DynamicAction;
export interface Exit {
type: typeof EXIT;
exitInfo: {
description?: string;
};
}

export type WidgetAction = OpenWebpage | WritePv | DynamicAction | Exit;

export interface WidgetActions {
actions: WidgetAction[];
Expand Down Expand Up @@ -98,6 +106,12 @@ export const getActionDescription = (action: WidgetAction): string => {
} else {
return `Close tab ${action.dynamicInfo.name}`;
}
case EXIT:
if (action.exitInfo.description) {
return action.exitInfo.description;
} else {
return `Close current screen`;
}
default:
throw new InvalidAction(action);
}
Expand Down Expand Up @@ -148,6 +162,7 @@ export const closeTab = (
export const executeAction = (
action: WidgetAction,
files?: FileContextType,
exitContext?: ExitContextType,
parentMacros?: MacroMap
): void => {
switch (action.type) {
Expand Down Expand Up @@ -191,6 +206,13 @@ export const executeAction = (
}
writePv(action.writePvInfo.pvName, dtypeVal);
break;
case EXIT:
if (exitContext) {
exitContext();
} else {
log.error("Tried to exit but no exit context passed");
}
break;
default:
throw new InvalidAction(action);
}
Expand All @@ -199,6 +221,7 @@ export const executeAction = (
export const executeActions = (
actions: WidgetActions,
files?: FileContextType,
exitContext?: ExitContextType,
parentMacros?: MacroMap
): void => {
if (actions.actions.length > 0) {
Expand All @@ -210,7 +233,7 @@ export const executeActions = (
}
for (const action of toExecute) {
log.info(`Executing an action: ${getActionDescription(action)}`);
executeAction(action, files, parentMacros);
executeAction(action, files, exitContext, parentMacros);
}
}
};