CE-1727 Updates to processes rendering block-widgets, to get up to compatible with the android app

This commit is contained in:
2024-11-04 08:48:46 -06:00
parent 4fd50936ea
commit 81efb7e18d
10 changed files with 444 additions and 98 deletions

View File

@ -40,6 +40,8 @@ interface Props
value: any; value: any;
type: string; type: string;
isEditable?: boolean; isEditable?: boolean;
placeholder?: string;
backgroundColor?: string;
[key: string]: any; [key: string]: any;
@ -49,7 +51,7 @@ interface Props
} }
function QDynamicFormField({ function QDynamicFormField({
label, name, displayFormat, value, bulkEditMode, bulkEditSwitchChangeHandler, type, isEditable, formFieldObject, ...rest label, name, displayFormat, value, bulkEditMode, bulkEditSwitchChangeHandler, type, isEditable, placeholder, backgroundColor, formFieldObject, ...rest
}: Props): JSX.Element }: Props): JSX.Element
{ {
const [switchChecked, setSwitchChecked] = useState(false); const [switchChecked, setSwitchChecked] = useState(false);
@ -65,18 +67,30 @@ function QDynamicFormField({
inputLabelProps.shrink = true; inputLabelProps.shrink = true;
} }
const inputProps = {}; const inputProps: any = {};
if (displayFormat && displayFormat.startsWith("$")) if (displayFormat && displayFormat.startsWith("$"))
{ {
// @ts-ignore
inputProps.startAdornment = <InputAdornment position="start">$</InputAdornment>; inputProps.startAdornment = <InputAdornment position="start">$</InputAdornment>;
} }
if (displayFormat && displayFormat.endsWith("%%")) if (displayFormat && displayFormat.endsWith("%%"))
{ {
// @ts-ignore
inputProps.endAdornment = <InputAdornment position="end">%</InputAdornment>; inputProps.endAdornment = <InputAdornment position="end">%</InputAdornment>;
} }
if (placeholder)
{
inputProps.placeholder = placeholder
}
if(backgroundColor)
{
inputProps.sx = {
"&.MuiInputBase-root": {
backgroundColor: backgroundColor
}
};
}
// @ts-ignore // @ts-ignore
const handleOnWheel = (e) => const handleOnWheel = (e) =>
{ {

View File

@ -129,8 +129,6 @@ class DynamicFormUtils
const effectivelyIsRequired = field.isRequired && effectiveIsEditable; const effectivelyIsRequired = field.isRequired && effectiveIsEditable;
if (effectivelyIsRequired) if (effectivelyIsRequired)
{
if (field.possibleValueSourceName)
{ {
//////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////
// the "nullable(true)" here doesn't mean that you're allowed to set the field to null... // // the "nullable(true)" here doesn't mean that you're allowed to set the field to null... //
@ -138,11 +136,6 @@ class DynamicFormUtils
//////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////
return (Yup.string().required(`${field.label ?? "This field"} is required.`).nullable(true)); return (Yup.string().required(`${field.label ?? "This field"} is required.`).nullable(true));
} }
else
{
return (Yup.string().required(`${field.label ?? "This field"} is required.`));
}
}
return (null); return (null);
} }

View File

@ -22,19 +22,25 @@
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {Box, Skeleton} from "@mui/material"; import {Box, Skeleton} from "@mui/material";
import Card from "@mui/material/Card";
import Modal from "@mui/material/Modal";
import parse from "html-react-parser"; import parse from "html-react-parser";
import {BlockData} from "qqq/components/widgets/blocks/BlockModels"; import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
import WidgetBlock from "qqq/components/widgets/WidgetBlock"; import WidgetBlock from "qqq/components/widgets/WidgetBlock";
import React from "react"; import ProcessWidgetBlockUtils from "qqq/pages/processes/ProcessWidgetBlockUtils";
import React, {useEffect, useState} from "react";
export interface CompositeData export interface CompositeData
{ {
blockId: string;
blocks: BlockData[]; blocks: BlockData[];
styleOverrides?: any; styleOverrides?: any;
layout?: string; layout?: string;
overlayHtml?: string; overlayHtml?: string;
overlayStyleOverrides?: any; overlayStyleOverrides?: any;
modalMode: string;
styles?: any;
} }
@ -42,14 +48,15 @@ interface CompositeWidgetProps
{ {
widgetMetaData: QWidgetMetaData; widgetMetaData: QWidgetMetaData;
data: CompositeData; data: CompositeData;
actionCallback?: (blockData: BlockData) => boolean; actionCallback?: (blockData: BlockData, eventValues?: { [name: string]: any }) => boolean;
values?: { [key: string]: any };
} }
/******************************************************************************* /*******************************************************************************
** Widget which is a list of Blocks. ** Widget which is a list of Blocks.
*******************************************************************************/ *******************************************************************************/
export default function CompositeWidget({widgetMetaData, data, actionCallback}: CompositeWidgetProps): JSX.Element export default function CompositeWidget({widgetMetaData, data, actionCallback, values}: CompositeWidgetProps): JSX.Element
{ {
if (!data || !data.blocks) if (!data || !data.blocks)
{ {
@ -75,6 +82,12 @@ export default function CompositeWidget({widgetMetaData, data, actionCallback}:
boxStyle.flexWrap = "wrap"; boxStyle.flexWrap = "wrap";
boxStyle.gap = "0.5rem"; boxStyle.gap = "0.5rem";
} }
else if (layout == "FLEX_ROW")
{
boxStyle.display = "flex";
boxStyle.flexDirection = "row";
boxStyle.gap = "0.5rem";
}
else if (layout == "FLEX_ROW_SPACE_BETWEEN") else if (layout == "FLEX_ROW_SPACE_BETWEEN")
{ {
boxStyle.display = "flex"; boxStyle.display = "flex";
@ -114,6 +127,19 @@ export default function CompositeWidget({widgetMetaData, data, actionCallback}:
boxStyle = {...boxStyle, ...data.styleOverrides}; boxStyle = {...boxStyle, ...data.styleOverrides};
} }
if (data.styles?.backgroundColor)
{
boxStyle.backgroundColor = ProcessWidgetBlockUtils.processColorFromStyleMap(data.styles.backgroundColor);
}
if (data.styles?.padding)
{
boxStyle.paddingTop = data.styles?.padding.top + "px"
boxStyle.paddingBottom = data.styles?.padding.bottom + "px"
boxStyle.paddingLeft = data.styles?.padding.left + "px"
boxStyle.paddingRight = data.styles?.padding.right + "px"
}
let overlayStyle: any = {}; let overlayStyle: any = {};
if (data?.overlayStyleOverrides) if (data?.overlayStyleOverrides)
@ -121,7 +147,7 @@ export default function CompositeWidget({widgetMetaData, data, actionCallback}:
overlayStyle = {...overlayStyle, ...data.overlayStyleOverrides}; overlayStyle = {...overlayStyle, ...data.overlayStyleOverrides};
} }
return ( const content = (
<> <>
{ {
data?.overlayHtml && data?.overlayHtml &&
@ -131,7 +157,7 @@ export default function CompositeWidget({widgetMetaData, data, actionCallback}:
{ {
data.blocks.map((block: BlockData, index) => ( data.blocks.map((block: BlockData, index) => (
<React.Fragment key={index}> <React.Fragment key={index}>
<WidgetBlock widgetMetaData={widgetMetaData} block={block} actionCallback={actionCallback} /> <WidgetBlock widgetMetaData={widgetMetaData} block={block} actionCallback={actionCallback} values={values} />
</React.Fragment> </React.Fragment>
)) ))
} }
@ -139,4 +165,53 @@ export default function CompositeWidget({widgetMetaData, data, actionCallback}:
</> </>
); );
if (data.modalMode)
{
const [isModalOpen, setIsModalOpen] = useState(values && (values[data.blockId] == true));
/***************************************************************************
**
***************************************************************************/
const controlCallback = (newValue: boolean) =>
{
setIsModalOpen(newValue);
};
/***************************************************************************
**
***************************************************************************/
const modalOnClose = (event: object, reason: string) =>
{
values[data.blockId] = false;
setIsModalOpen(false);
actionCallback({blockTypeName: "BUTTON", values: {}}, {controlCode: `hideModal:${data.blockId}`});
};
//////////////////////////////////////////////////////////////////////////////////////////
// register the control-callback function - so when buttons are clicked, we can be told //
//////////////////////////////////////////////////////////////////////////////////////////
useEffect(() =>
{
if (actionCallback)
{
actionCallback(null, {
registerControlCallbackName: data.blockId,
registerControlCallbackFunction: controlCallback
});
}
}, []);
return (<Modal open={isModalOpen} onClose={modalOnClose}>
<Box sx={{position: "absolute", overflowY: "auto", maxHeight: "100%", width: "100%"}}>
<Card sx={{my: 5, mx: "auto", p: "1rem", maxWidth: "1024px"}}>
{content}
</Card>
</Box>
</Modal>);
}
else
{
return content;
}
} }

View File

@ -29,6 +29,7 @@ import parse from "html-react-parser";
import QContext from "QContext"; import QContext from "QContext";
import MDTypography from "qqq/components/legacy/MDTypography"; import MDTypography from "qqq/components/legacy/MDTypography";
import TabPanel from "qqq/components/misc/TabPanel"; import TabPanel from "qqq/components/misc/TabPanel";
import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
import BarChart from "qqq/components/widgets/charts/barchart/BarChart"; import BarChart from "qqq/components/widgets/charts/barchart/BarChart";
import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart"; import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart";
import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart"; import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart";
@ -71,6 +72,9 @@ interface Props
childUrlParams?: string; childUrlParams?: string;
parentWidgetMetaData?: QWidgetMetaData; parentWidgetMetaData?: QWidgetMetaData;
wrapWidgetsInTabPanels: boolean; wrapWidgetsInTabPanels: boolean;
actionCallback?: (blockData: BlockData) => boolean;
initialWidgetDataList: any[];
values?: {[key: string]: any};
} }
DashboardWidgets.defaultProps = { DashboardWidgets.defaultProps = {
@ -82,11 +86,14 @@ DashboardWidgets.defaultProps = {
childUrlParams: "", childUrlParams: "",
parentWidgetMetaData: null, parentWidgetMetaData: null,
wrapWidgetsInTabPanels: false, wrapWidgetsInTabPanels: false,
actionCallback: null,
initialWidgetDataList: null,
values: {}
}; };
function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, record, omitWrappingGridContainer, areChildren, childUrlParams, parentWidgetMetaData, wrapWidgetsInTabPanels}: Props): JSX.Element function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, record, omitWrappingGridContainer, areChildren, childUrlParams, parentWidgetMetaData, wrapWidgetsInTabPanels, actionCallback, initialWidgetDataList, values}: Props): JSX.Element
{ {
const [widgetData, setWidgetData] = useState([] as any[]); const [widgetData, setWidgetData] = useState(initialWidgetDataList == null ? [] as any[] : initialWidgetDataList);
const [widgetCounter, setWidgetCounter] = useState(0); const [widgetCounter, setWidgetCounter] = useState(0);
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
@ -114,7 +121,15 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
useEffect(() => useEffect(() =>
{ {
if(initialWidgetDataList && initialWidgetDataList.length > 0)
{
// todo actually, should this check each element of the array, down in the loop? yeah, when we need to, do it that way.
console.log("We already have initial widget data, so not fetching from backend.");
return
}
setWidgetData([]); setWidgetData([]);
for (let i = 0; i < widgetMetaDataList.length; i++) for (let i = 0; i < widgetMetaDataList.length; i++)
{ {
const widgetMetaData = widgetMetaDataList[i]; const widgetMetaData = widgetMetaDataList[i];
@ -563,7 +578,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsRight={labelAdditionalComponentsRight}
labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft}
> >
<CompositeWidget widgetMetaData={widgetMetaData} data={widgetData[i]} /> <CompositeWidget widgetMetaData={widgetMetaData} data={widgetData[i]} actionCallback={actionCallback} values={values} />
</Widget> </Widget>
) )
} }

View File

@ -22,9 +22,10 @@
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {Alert, Skeleton} from "@mui/material"; import {Alert, Skeleton} from "@mui/material";
import ActionButtonBlock from "qqq/components/widgets/blocks/ActionButtonBlock"; import ButtonBlock from "qqq/components/widgets/blocks/ButtonBlock";
import AudioBlock from "qqq/components/widgets/blocks/AudioBlock"; import AudioBlock from "qqq/components/widgets/blocks/AudioBlock";
import InputFieldBlock from "qqq/components/widgets/blocks/InputFieldBlock"; import InputFieldBlock from "qqq/components/widgets/blocks/InputFieldBlock";
import RevealBlock from "qqq/components/widgets/blocks/RevealBlock";
import React from "react"; import React from "react";
import BigNumberBlock from "qqq/components/widgets/blocks/BigNumberBlock"; import BigNumberBlock from "qqq/components/widgets/blocks/BigNumberBlock";
import {BlockData} from "qqq/components/widgets/blocks/BlockModels"; import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
@ -42,14 +43,15 @@ interface WidgetBlockProps
{ {
widgetMetaData: QWidgetMetaData; widgetMetaData: QWidgetMetaData;
block: BlockData; block: BlockData;
actionCallback?: (blockData: BlockData) => boolean; actionCallback?: (blockData: BlockData, eventValues?: {[name: string]: any}) => boolean;
values?: { [key: string]: any };
} }
/******************************************************************************* /*******************************************************************************
** Component to render a single Block in the widget framework! ** Component to render a single Block in the widget framework!
*******************************************************************************/ *******************************************************************************/
export default function WidgetBlock({widgetMetaData, block, actionCallback}: WidgetBlockProps): JSX.Element export default function WidgetBlock({widgetMetaData, block, actionCallback, values}: WidgetBlockProps): JSX.Element
{ {
if(!block) if(!block)
{ {
@ -69,7 +71,7 @@ export default function WidgetBlock({widgetMetaData, block, actionCallback}: Wid
if(block.blockTypeName == "COMPOSITE") if(block.blockTypeName == "COMPOSITE")
{ {
// @ts-ignore - special case for composite type block... // @ts-ignore - special case for composite type block...
return (<CompositeWidget widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />); return (<CompositeWidget widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} values={values} />);
} }
switch(block.blockTypeName) switch(block.blockTypeName)
@ -90,12 +92,14 @@ export default function WidgetBlock({widgetMetaData, block, actionCallback}: Wid
return (<BigNumberBlock widgetMetaData={widgetMetaData} data={block} />); return (<BigNumberBlock widgetMetaData={widgetMetaData} data={block} />);
case "INPUT_FIELD": case "INPUT_FIELD":
return (<InputFieldBlock widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />); return (<InputFieldBlock widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />);
case "ACTION_BUTTON": case "BUTTON":
return (<ActionButtonBlock widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />); return (<ButtonBlock widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />);
case "AUDIO": case "AUDIO":
return (<AudioBlock widgetMetaData={widgetMetaData} data={block} />); return (<AudioBlock widgetMetaData={widgetMetaData} data={block} />);
case "IMAGE": case "IMAGE":
return (<ImageBlock widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />); return (<ImageBlock widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />);
case "REVEAL":
return (<RevealBlock widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />);
default: default:
return (<Alert sx={{m: "0.5rem"}} color="warning">Unsupported block type: {block.blockTypeName}</Alert>) return (<Alert sx={{m: "0.5rem"}} color="warning">Unsupported block type: {block.blockTypeName}</Alert>)
} }

View File

@ -29,30 +29,56 @@ import React from "react";
/******************************************************************************* /*******************************************************************************
** Block that renders ... an action button... ** Block that renders ... a button...
** **
*******************************************************************************/ *******************************************************************************/
export default function ActionButtonBlock({widgetMetaData, data, actionCallback}: StandardBlockComponentProps): JSX.Element export default function ButtonBlock({widgetMetaData, data, actionCallback}: StandardBlockComponentProps): JSX.Element
{ {
const icon = data.values.iconName ? <Icon>{data.values.iconName}</Icon> : null; const startIcon = data.values.startIcon?.name ? <Icon>{data.values.startIcon.name}</Icon> : null;
const endIcon = data.values.endIcon?.name ? <Icon>{data.values.endIcon.name}</Icon> : null;
function onClick() function onClick()
{ {
if (actionCallback) if (actionCallback)
{ {
actionCallback(data, {actionCode: data.values?.actionCode}) actionCallback(data, data.values);
} }
else else
{ {
console.log("ActionButtonBlock onClick with no actionCallback present, so, noop"); console.log("ButtonBlock onClick with no actionCallback present, so, noop");
} }
} }
let buttonVariant: "gradient" | "outlined" | "text" = "gradient";
if (data.styles?.format == "outlined")
{
buttonVariant = "outlined";
}
else if (data.styles?.format == "text")
{
buttonVariant = "text";
}
else if (data.styles?.format == "filled")
{
buttonVariant = "gradient";
}
// todo - button colors... but to do RGB's, might need to move away from MDButton?
return ( return (
<BlockElementWrapper metaData={widgetMetaData} data={data} slot=""> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="">
<Box mx={1} my={1} minWidth={standardWidth}> <Box mx={1} my={1} minWidth={standardWidth}>
<MDButton type="button" variant="gradient" color="dark" size="small" fullWidth startIcon={icon} onClick={onClick}> <MDButton
{data.values.label ?? "Action"} type="button"
variant={buttonVariant}
color="dark"
size="small"
fullWidth
startIcon={startIcon}
endIcon={endIcon}
onClick={onClick}
>
{data.values.label ?? "Button"}
</MDButton> </MDButton>
</Box> </Box>
</BlockElementWrapper> </BlockElementWrapper>

View File

@ -52,10 +52,10 @@ export default function InputFieldBlock({widgetMetaData, data, actionCallback}:
// so let us remove the default blur handler, for the first (auto) focus/blur // // so let us remove the default blur handler, for the first (auto) focus/blur //
// cycle, and we seem to have a better time. // // cycle, and we seem to have a better time. //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
let onBlurRest: {onBlur?: any} = {} let dynamicFormFieldRest: {onBlur?: any, sx?: any} = {}
if(autoFocus && blurCount == 0) if(autoFocus && blurCount == 0)
{ {
onBlurRest.onBlur = (event: React.SyntheticEvent) => dynamicFormFieldRest.onBlur = (event: React.SyntheticEvent) =>
{ {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
@ -120,7 +120,18 @@ export default function InputFieldBlock({widgetMetaData, data, actionCallback}:
<BlockElementWrapper metaData={widgetMetaData} data={data} slot=""> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="">
<> <>
{labelElement} {labelElement}
<QDynamicFormField name={fieldMetaData.name} displayFormat={null} label="" formFieldObject={dynamicField} type={fieldMetaData.type} value={value} autoFocus={autoFocus} onKeyUp={eventHandler} {...onBlurRest} /> <QDynamicFormField
name={fieldMetaData.name}
displayFormat={null}
label=""
placeholder={data.values?.placeholder}
backgroundColor="#FFFFFF"
formFieldObject={dynamicField}
type={fieldMetaData.type}
value={value}
autoFocus={autoFocus}
onKeyUp={eventHandler}
{...dynamicFormFieldRest} />
</> </>
</BlockElementWrapper> </BlockElementWrapper>
</Box> </Box>

View File

@ -20,9 +20,11 @@
*/ */
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Icon from "@mui/material/Icon";
import BlockElementWrapper from "qqq/components/widgets/blocks/BlockElementWrapper"; import BlockElementWrapper from "qqq/components/widgets/blocks/BlockElementWrapper";
import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockModels"; import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockModels";
import DumpJsonBox from "qqq/utils/DumpJsonBox"; import ProcessWidgetBlockUtils from "qqq/pages/processes/ProcessWidgetBlockUtils";
import React from "react";
/******************************************************************************* /*******************************************************************************
** Block that renders ... just some text. ** Block that renders ... just some text.
@ -32,30 +34,13 @@ import DumpJsonBox from "qqq/utils/DumpJsonBox";
export default function TextBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element export default function TextBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element
{ {
let color = "rgba(0, 0, 0, 0.87)"; let color = "rgba(0, 0, 0, 0.87)";
if (data.styles?.standardColor) if (data.styles?.color)
{ {
switch (data.styles?.standardColor) color = ProcessWidgetBlockUtils.processColorFromStyleMap(data.styles.color);
{
case "SUCCESS":
color = "#2BA83F";
break;
case "WARNING":
color = "#FBA132";
break;
case "ERROR":
color = "#FB4141";
break;
case "INFO":
color = "#458CFF";
break;
case "MUTED":
color = "#7b809a";
break;
}
} }
let boxStyle = {}; let boxStyle = {};
if (data.styles?.isAlert) if (data.styles?.format == "alert")
{ {
boxStyle = boxStyle =
{ {
@ -65,17 +50,112 @@ export default function TextBlock({widgetMetaData, data}: StandardBlockComponent
borderRadius: "0.5rem", borderRadius: "0.5rem",
}; };
} }
else if (data.styles?.format == "banner")
{
boxStyle =
{
background: `${color}40`,
padding: "0.5rem",
};
}
let fontSize = "1rem";
if (data.styles?.size)
{
switch (data.styles.size.toLowerCase())
{
case "largest":
fontSize = "3rem";
break;
case "headline":
fontSize = "2rem";
break;
case "title":
fontSize = "1.5rem";
break;
case "body":
fontSize = "1rem";
break;
case "smallest":
fontSize = "0.75rem";
break;
default:
{
if (data.styles.size.match(/^\d+$/))
{
fontSize = `${data.styles.size}px`;
}
else
{
fontSize = "1rem";
}
}
}
}
let fontWeight = "400";
if (data.styles?.weight)
{
switch (data.styles.weight.toLowerCase())
{
case "thin":
case "100":
fontWeight = "100";
break;
case "extralight":
case "200":
fontWeight = "200";
break;
case "light":
case "300":
fontWeight = "300";
break;
case "normal":
case "400":
fontWeight = "400";
break;
case "medium":
case "500":
fontWeight = "500";
break;
case "semibold":
case "600":
fontWeight = "600";
break;
case "bold":
case "700":
fontWeight = "700";
break;
case "extrabold":
case "800":
fontWeight = "800";
break;
case "black":
case "900":
fontWeight = "900";
break;
}
}
const text = data.values.interpolatedText ?? data.values.text; const text = data.values.interpolatedText ?? data.values.text;
const lines = text.split("\n"); const lines = text.split("\n");
const startIcon = data.values.startIcon?.name ? <Icon>{data.values.startIcon.name}</Icon> : null;
const endIcon = data.values.endIcon?.name ? <Icon>{data.values.endIcon.name}</Icon> : null;
return ( return (
<BlockElementWrapper metaData={widgetMetaData} data={data} slot=""> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="">
<Box display="inline-block" lineHeight="1.2" sx={boxStyle}> <Box display="inline-block" lineHeight="1.2" sx={boxStyle}>
<span style={{fontSize: "1rem", color: color}}> <span style={{fontSize: fontSize, color: color, fontWeight: fontWeight}}>
{lines.map((line: string, index: number) => {lines.map((line: string, index: number) =>
( (
<div key={index}>{line}</div> <div key={index}>
<>
{index == 0 && startIcon ? {startIcon} : null}
{line}
{index == lines.length - 1 && endIcon ? {endIcon} : null}
</>
</div>
)) ))
}</span> }</span>
</Box> </Box>

View File

@ -95,10 +95,10 @@ const INITIAL_RETRY_MILLIS = 1_500;
const RETRY_MAX_MILLIS = 12_000; const RETRY_MAX_MILLIS = 12_000;
const BACKOFF_AMOUNT = 1.5; const BACKOFF_AMOUNT = 1.5;
/////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// define some functions that we can make referene to, which we'll overwrite // // define some functions that we can make reference to, which we'll overwrite //
// with functions from formik, once we're inside formik. // // with functions from formik, once we're inside formik. //
/////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
let formikSetFieldValueFunction = (field: string, value: any, shouldValidate?: boolean): void => let formikSetFieldValueFunction = (field: string, value: any, shouldValidate?: boolean): void =>
{ {
}; };
@ -150,6 +150,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
const [previouslySeenUpdatedFieldMetaDataMap, setPreviouslySeenUpdatedFieldMetaDataMap] = useState(new Map<string, QFieldMetaData>); const [previouslySeenUpdatedFieldMetaDataMap, setPreviouslySeenUpdatedFieldMetaDataMap] = useState(new Map<string, QFieldMetaData>);
const [renderedWidgets, setRenderedWidgets] = useState({} as { [step: string]: { [widgetName: string]: any } }); const [renderedWidgets, setRenderedWidgets] = useState({} as { [step: string]: { [widgetName: string]: any } });
const [controlCallbacks, setControlCallbacks] = useState({} as {[name: string]: () => void})
const {pageHeader, recordAnalytics, setPageHeader, helpHelpActive} = useContext(QContext); const {pageHeader, recordAnalytics, setPageHeader, helpHelpActive} = useContext(QContext);
@ -351,14 +352,68 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
queryStringParts.push(`${name}=${encodeURIComponent(processValues[name])}`); queryStringParts.push(`${name}=${encodeURIComponent(processValues[name])}`);
} }
let initialWidgetDataList = null;
if(processValues[widgetName])
{
processValues[widgetName].hasPermission = true
initialWidgetDataList = [processValues[widgetName]]
}
const renderedWidget = (<Box m={-2}> const renderedWidget = (<Box m={-2}>
<DashboardWidgets widgetMetaDataList={[widgetMetaData]} omitWrappingGridContainer={true} childUrlParams={queryStringParts.join("&")} /> <DashboardWidgets widgetMetaDataList={[widgetMetaData]} omitWrappingGridContainer={true} childUrlParams={queryStringParts.join("&")} initialWidgetDataList={initialWidgetDataList} values={processValues} actionCallback={blockWidgetActionCallback} />
</Box>); </Box>);
renderedWidgets[activeStep.name][widgetName] = renderedWidget; renderedWidgets[activeStep.name][widgetName] = renderedWidget;
return renderedWidget; return renderedWidget;
} }
/***************************************************************************
**
***************************************************************************/
function handleControlCode(controlCode: string)
{
const split = controlCode.split(":", 2);
let controlCallbackName: string;
let controlCallbackValue: any
if(split.length == 2)
{
if(split[0] == "showModal")
{
processValues[split[1]] = true
controlCallbackName = split[1]
controlCallbackValue = true
}
else if(split[0] == "hideModal")
{
processValues[split[1]] = false
controlCallbackName = split[1]
controlCallbackValue = false
}
else if(split[0] == "toggleModal")
{
const currentValue = processValues[split[1]]
processValues[split[1]] = !!!currentValue;
controlCallbackName = split[1]
controlCallbackValue = processValues[split[1]]
}
else
{
console.log(`Unexpected part[0] (before colon) in controlCode: [${controlCode}]`)
}
}
else
{
console.log(`Expected controlCode to have 2 colon-delimited parts, but was: [${controlCode}]`)
}
if(controlCallbackName && controlCallbacks[controlCallbackName])
{
// @ts-ignore ... args are hard
controlCallbacks[controlCallbackName](controlCallbackValue)
}
}
/*************************************************************************** /***************************************************************************
** callback used by widget blocks, e.g., for input-text-enter-on-submit, ** callback used by widget blocks, e.g., for input-text-enter-on-submit,
** and action buttons. ** and action buttons.
@ -367,30 +422,59 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
{ {
console.log(`in blockWidgetActionCallback, called by block: ${JSON.stringify(blockData)}`); console.log(`in blockWidgetActionCallback, called by block: ${JSON.stringify(blockData)}`);
/////////////////////////////////////////////////////////////////////////////// if(eventValues?.registerControlCallbackName && eventValues?.registerControlCallbackFunction)
// if the eventValues included an actionCode - validate it before proceeding //
///////////////////////////////////////////////////////////////////////////////
if (eventValues && eventValues.actionCode && !ProcessWidgetBlockUtils.isActionCodeValid(eventValues.actionCode, activeStep, processValues))
{ {
setFormError("Unrecognized action code: " + eventValues.actionCode); controlCallbacks[eventValues.registerControlCallbackName] = eventValues.registerControlCallbackFunction;
setControlCallbacks(controlCallbacks)
if (eventValues["_fieldToClearIfError"]) return (true)
{
/////////////////////////////////////////////////////////////////////////////
// if the eventValues included a _fieldToClearIfError, well, then do that. //
/////////////////////////////////////////////////////////////////////////////
formikSetFieldValueFunction(eventValues["_fieldToClearIfError"], "", false);
} }
return (false); ////////////////////////////////////////////////////////////////////////////////////////////////////////
// we don't validate these on the android frontend, and it seems fine - just let the app validate it? //
////////////////////////////////////////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////////////
// // if the eventValues included an actionCode - validate it before proceeding //
// ///////////////////////////////////////////////////////////////////////////////
// if (eventValues && eventValues.actionCode && !ProcessWidgetBlockUtils.isActionCodeValid(eventValues.actionCode, activeStep, processValues))
// {
// setFormError("Unrecognized action code: " + eventValues.actionCode);
// if (eventValues["_fieldToClearIfError"])
// {
// /////////////////////////////////////////////////////////////////////////////
// // if the eventValues included a _fieldToClearIfError, well, then do that. //
// /////////////////////////////////////////////////////////////////////////////
// formikSetFieldValueFunction(eventValues["_fieldToClearIfError"], "", false);
// }
// return (false);
// }
let doSubmit = false;
if(blockData?.blockTypeName == "BUTTON" && eventValues?.actionCode)
{
doSubmit = true
}
else if(blockData?.blockTypeName == "BUTTON" && eventValues?.controlCode)
{
handleControlCode(eventValues.controlCode);
doSubmit = false
}
else if(blockData?.blockTypeName == "INPUT_FIELD")
{
///////////////////////////////////////////////////////////////////////////////////////////////
// if action callback was fired from an input field, assume that means we're good to submit. //
///////////////////////////////////////////////////////////////////////////////////////////////
doSubmit = true
} }
////////////////// //////////////////
// ok - submit! // // ok - submit! //
////////////////// //////////////////
if(doSubmit)
{
handleSubmit(eventValues); handleSubmit(eventValues);
return (true); return (true);
} }
}
/*************************************************************************** /***************************************************************************
@ -412,7 +496,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
ProcessWidgetBlockUtils.dynamicEvaluationOfCompositeWidgetData(compositeWidgetData, processValues); ProcessWidgetBlockUtils.dynamicEvaluationOfCompositeWidgetData(compositeWidgetData, processValues);
renderedWidgets[key] = <Box key={key} pt={2}> renderedWidgets[key] = <Box key={key} pt={2}>
<CompositeWidget widgetMetaData={widgetMetaData} data={compositeWidgetData} actionCallback={blockWidgetActionCallback} /> <CompositeWidget widgetMetaData={widgetMetaData} data={compositeWidgetData} actionCallback={blockWidgetActionCallback} values={processValues} />
</Box>; </Box>;
setRenderedWidgets(renderedWidgets); setRenderedWidgets(renderedWidgets);
@ -574,6 +658,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"]; let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"];
const showHelp = helpHelpActive || hasHelpContent(step.helpContents, helpRoles); const showHelp = helpHelpActive || hasHelpContent(step.helpContents, helpRoles);
const formattedHelpContent = <HelpContent helpContents={step.helpContents} roles={helpRoles} helpContentKey={`process:${processName};step:${step?.name}`} />; const formattedHelpContent = <HelpContent helpContents={step.helpContents} roles={helpRoles} helpContentKey={`process:${processName};step:${step?.name}`} />;
const isFormatScanner = step?.format?.toLowerCase() == "scanner"
return ( return (
<> <>
@ -582,7 +667,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
// hide label on widgets - the Widget component itself provides the label // // hide label on widgets - the Widget component itself provides the label //
// for modals, show the process label, but not for full-screen processes (for them, it is in the breadcrumb) // // for modals, show the process label, but not for full-screen processes (for them, it is in the breadcrumb) //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
!isWidget && !isWidget && !isFormatScanner &&
<MDTypography variant={isWidget ? "h6" : "h5"} component="div" fontWeight="bold"> <MDTypography variant={isWidget ? "h6" : "h5"} component="div" fontWeight="bold">
{(isModal) ? `${overrideLabel ?? process.label}: ` : ""} {(isModal) ? `${overrideLabel ?? process.label}: ` : ""}
{step?.label} {step?.label}
@ -1044,7 +1129,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
if (doesStepHaveComponent(activeStep, QComponentType.WIDGET)) if (doesStepHaveComponent(activeStep, QComponentType.WIDGET))
{ {
ProcessWidgetBlockUtils.addFieldsForCompositeWidget(activeStep, (fieldMetaData) => ProcessWidgetBlockUtils.addFieldsForCompositeWidget(activeStep, processValues, (fieldMetaData) =>
{ {
const dynamicField = DynamicFormUtils.getDynamicField(fieldMetaData); const dynamicField = DynamicFormUtils.getDynamicField(fieldMetaData);
const validation = DynamicFormUtils.getValidationForField(fieldMetaData); const validation = DynamicFormUtils.getValidationForField(fieldMetaData);
@ -1827,7 +1912,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
) : ( ) : (
<MDButton variant="gradient" color="light" onClick={handleBack}>back</MDButton> <MDButton variant="gradient" color="light" onClick={handleBack}>back</MDButton>
)} )}
{processError || qJobRunning || !activeStep ? ( {processError || qJobRunning || !activeStep || activeStep?.format?.toLowerCase() == "scanner" ? (
<Box /> <Box />
) : ( ) : (
<> <>

View File

@ -71,11 +71,11 @@ export default class ProcessWidgetBlockUtils
} }
// else, continue... // else, continue...
} }
else if (block.blockTypeName == "ACTION_BUTTON") else if (block.blockTypeName == "BUTTON")
{ {
////////////////////////////////////////////////////////// //////////////////////////////////////////
// actually look at actionCodes on action button blocks // // look at actionCodes on button blocks //
////////////////////////////////////////////////////////// //////////////////////////////////////////
if (block.values?.actionCode == actionCode) if (block.values?.actionCode == actionCode)
{ {
return (true); return (true);
@ -182,7 +182,7 @@ export default class ProcessWidgetBlockUtils
/*************************************************************************** /***************************************************************************
** **
***************************************************************************/ ***************************************************************************/
public static addFieldsForCompositeWidget(step: QFrontendStepMetaData, addFieldCallback: (fieldMetaData: QFieldMetaData) => void) public static addFieldsForCompositeWidget(step: QFrontendStepMetaData, processValues: any, addFieldCallback: (fieldMetaData: QFieldMetaData) => void)
{ {
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
// private recursive function to walk the composite tree // // private recursive function to walk the composite tree //
@ -200,7 +200,7 @@ export default class ProcessWidgetBlockUtils
else if (block.blockTypeName == "INPUT_FIELD") else if (block.blockTypeName == "INPUT_FIELD")
{ {
const fieldMetaData = new QFieldMetaData(block.values?.fieldMetaData); const fieldMetaData = new QFieldMetaData(block.values?.fieldMetaData);
addFieldCallback(fieldMetaData) addFieldCallback(fieldMetaData);
} }
} }
} }
@ -210,14 +210,57 @@ export default class ProcessWidgetBlockUtils
} }
} }
///////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// foreach component, if it's an adhoc widget, call recursive helper on it // // foreach component, if it's an adhoc widget or a widget w/ its data in the processValues, then, call recursive helper on it //
///////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for (let component of step.components) for (let component of step.components)
{ {
if (component.type == QComponentType.WIDGET && component.values?.isAdHocWidget) if (component.type == QComponentType.WIDGET && component.values?.isAdHocWidget)
{ {
recursiveHelper(component.values as unknown as CompositeData) recursiveHelper(component.values as unknown as CompositeData);
}
else if (component.type == QComponentType.WIDGET && processValues[component.values?.widgetName])
{
recursiveHelper(processValues[component.values?.widgetName] as unknown as CompositeData);
}
}
}
/***************************************************************************
**
***************************************************************************/
public static processColorFromStyleMap(colorFromStyleMap?: string): string
{
if (colorFromStyleMap)
{
switch (colorFromStyleMap.toUpperCase())
{
case "SUCCESS":
return("#2BA83F");
case "WARNING":
return("#FBA132");
case "ERROR":
return("#FB4141");
case "INFO":
return("#458CFF");
case "MUTED":
return("#7b809a");
default:
{
if (colorFromStyleMap.match(/^[0-9A-F]{6}$/))
{
return(`#${colorFromStyleMap}`);
}
else if (colorFromStyleMap.match(/^[0-9A-F]{8}$/))
{
return(`#${colorFromStyleMap}`);
}
else
{
return(colorFromStyleMap);
}
}
} }
} }
} }