mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
CE-1727 - New blocks and styles for (mobile-style) widgets in processes, plus callbacks and contextValues
This commit is contained in:
@ -22,6 +22,9 @@
|
||||
|
||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||
import {Alert, Skeleton} from "@mui/material";
|
||||
import ActionButtonBlock from "qqq/components/widgets/blocks/ActionButtonBlock";
|
||||
import AudioBlock from "qqq/components/widgets/blocks/AudioBlock";
|
||||
import InputFieldBlock from "qqq/components/widgets/blocks/InputFieldBlock";
|
||||
import React from "react";
|
||||
import BigNumberBlock from "qqq/components/widgets/blocks/BigNumberBlock";
|
||||
import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
|
||||
@ -32,19 +35,21 @@ import TableSubRowDetailRowBlock from "qqq/components/widgets/blocks/TableSubRow
|
||||
import TextBlock from "qqq/components/widgets/blocks/TextBlock";
|
||||
import UpOrDownNumberBlock from "qqq/components/widgets/blocks/UpOrDownNumberBlock";
|
||||
import CompositeWidget from "qqq/components/widgets/CompositeWidget";
|
||||
import ImageBlock from "./blocks/ImageBlock";
|
||||
|
||||
|
||||
interface WidgetBlockProps
|
||||
{
|
||||
widgetMetaData: QWidgetMetaData;
|
||||
block: BlockData;
|
||||
actionCallback?: (blockData: BlockData) => boolean;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Component to render a single Block in the widget framework!
|
||||
*******************************************************************************/
|
||||
export default function WidgetBlock({widgetMetaData, block}: WidgetBlockProps): JSX.Element
|
||||
export default function WidgetBlock({widgetMetaData, block, actionCallback}: WidgetBlockProps): JSX.Element
|
||||
{
|
||||
if(!block)
|
||||
{
|
||||
@ -64,7 +69,7 @@ export default function WidgetBlock({widgetMetaData, block}: WidgetBlockProps):
|
||||
if(block.blockTypeName == "COMPOSITE")
|
||||
{
|
||||
// @ts-ignore - special case for composite type block...
|
||||
return (<CompositeWidget widgetMetaData={widgetMetaData} data={block} />);
|
||||
return (<CompositeWidget widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />);
|
||||
}
|
||||
|
||||
switch(block.blockTypeName)
|
||||
@ -83,6 +88,14 @@ export default function WidgetBlock({widgetMetaData, block}: WidgetBlockProps):
|
||||
return (<DividerBlock widgetMetaData={widgetMetaData} data={block} />);
|
||||
case "BIG_NUMBER":
|
||||
return (<BigNumberBlock widgetMetaData={widgetMetaData} data={block} />);
|
||||
case "INPUT_FIELD":
|
||||
return (<InputFieldBlock widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />);
|
||||
case "ACTION_BUTTON":
|
||||
return (<ActionButtonBlock widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />);
|
||||
case "AUDIO":
|
||||
return (<AudioBlock widgetMetaData={widgetMetaData} data={block} />);
|
||||
case "IMAGE":
|
||||
return (<ImageBlock widgetMetaData={widgetMetaData} data={block} actionCallback={actionCallback} />);
|
||||
default:
|
||||
return (<Alert sx={{m: "0.5rem"}} color="warning">Unsupported block type: {block.blockTypeName}</Alert>)
|
||||
}
|
||||
|
60
src/qqq/components/widgets/blocks/ActionButtonBlock.tsx
Normal file
60
src/qqq/components/widgets/blocks/ActionButtonBlock.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {standardWidth} from "qqq/components/buttons/DefaultButtons";
|
||||
import MDButton from "qqq/components/legacy/MDButton";
|
||||
import BlockElementWrapper from "qqq/components/widgets/blocks/BlockElementWrapper";
|
||||
import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockModels";
|
||||
import React from "react";
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Block that renders ... an action button...
|
||||
**
|
||||
*******************************************************************************/
|
||||
export default function ActionButtonBlock({widgetMetaData, data, actionCallback}: StandardBlockComponentProps): JSX.Element
|
||||
{
|
||||
const icon = data.values.iconName ? <Icon>{data.values.iconName}</Icon> : null;
|
||||
|
||||
function onClick()
|
||||
{
|
||||
if(actionCallback)
|
||||
{
|
||||
actionCallback(data, {actionCode: data.values?.actionCode})
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log("ActionButtonBlock onClick with no actionCallback present, so, noop");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<BlockElementWrapper metaData={widgetMetaData} data={data} slot="">
|
||||
<Box mx={1} my={1} minWidth={standardWidth}>
|
||||
<MDButton type="button" variant="gradient" color="dark" size="small" fullWidth startIcon={icon} onClick={onClick}>
|
||||
{data.values.label ?? "Action"}
|
||||
</MDButton>
|
||||
</Box>
|
||||
</BlockElementWrapper>
|
||||
);
|
||||
}
|
40
src/qqq/components/widgets/blocks/AudioBlock.tsx
Normal file
40
src/qqq/components/widgets/blocks/AudioBlock.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import BlockElementWrapper from "qqq/components/widgets/blocks/BlockElementWrapper";
|
||||
import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockModels";
|
||||
import DumpJsonBox from "qqq/utils/DumpJsonBox";
|
||||
import React from "react";
|
||||
|
||||
/*******************************************************************************
|
||||
** Block that renders ... an audio tag
|
||||
**
|
||||
** <audio src=${path} ${autoPlay} ${showControls} />
|
||||
*******************************************************************************/
|
||||
export default function AudioBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<BlockElementWrapper metaData={widgetMetaData} data={data} slot="">
|
||||
<audio src={data.values?.path} autoPlay={data.values?.autoPlay} controls={data.values?.showControls} />
|
||||
</BlockElementWrapper>
|
||||
);
|
||||
}
|
59
src/qqq/components/widgets/blocks/ImageBlock.tsx
Normal file
59
src/qqq/components/widgets/blocks/ImageBlock.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import BlockElementWrapper from "qqq/components/widgets/blocks/BlockElementWrapper";
|
||||
import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockModels";
|
||||
import DumpJsonBox from "qqq/utils/DumpJsonBox";
|
||||
import React from "react";
|
||||
|
||||
/*******************************************************************************
|
||||
** Block that renders ... an image tag
|
||||
**
|
||||
** <audio src=${path} ${autoPlay} ${showControls} />
|
||||
*******************************************************************************/
|
||||
export default function AudioBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element
|
||||
{
|
||||
let imageStyle: any = {};
|
||||
|
||||
if(data.styles?.width)
|
||||
{
|
||||
imageStyle.width = data.styles?.width;
|
||||
}
|
||||
|
||||
if(data.styles?.height)
|
||||
{
|
||||
imageStyle.height = data.styles?.height;
|
||||
}
|
||||
|
||||
if(data.styles?.bordered)
|
||||
{
|
||||
imageStyle.border = "1px solid #C0C0C0";
|
||||
imageStyle.borderRadius = "0.5rem";
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<BlockElementWrapper metaData={widgetMetaData} data={data} slot="">
|
||||
<img src={data.values?.path} alt={data.values?.alt} style={imageStyle} />
|
||||
</BlockElementWrapper>
|
||||
);
|
||||
}
|
128
src/qqq/components/widgets/blocks/InputFieldBlock.tsx
Normal file
128
src/qqq/components/widgets/blocks/InputFieldBlock.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||
import Box from "@mui/material/Box";
|
||||
import QDynamicFormField from "qqq/components/forms/DynamicFormField";
|
||||
import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
|
||||
import BlockElementWrapper from "qqq/components/widgets/blocks/BlockElementWrapper";
|
||||
import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockModels";
|
||||
import React, {SyntheticEvent, useState} from "react";
|
||||
|
||||
/*******************************************************************************
|
||||
** Block that renders ... a text input
|
||||
**
|
||||
*******************************************************************************/
|
||||
export default function InputFieldBlock({widgetMetaData, data, actionCallback}: StandardBlockComponentProps): JSX.Element
|
||||
{
|
||||
const [blurCount, setBlurCount] = useState(0)
|
||||
|
||||
const fieldMetaData = new QFieldMetaData(data.values.fieldMetaData);
|
||||
const dynamicField = DynamicFormUtils.getDynamicField(fieldMetaData);
|
||||
|
||||
let autoFocus = data.values.autoFocus as boolean
|
||||
let value = data.values.value;
|
||||
if(value == null || value == undefined)
|
||||
{
|
||||
value = "";
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// for an autoFocus field... //
|
||||
// we're finding that if we blur it when clicking an action button, that //
|
||||
// an un-desirable "now it's been touched, so show an error" happens. //
|
||||
// so let us remove the default blur handler, for the first (auto) focus/blur //
|
||||
// cycle, and we seem to have a better time. //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
let onBlurRest: {onBlur?: any} = {}
|
||||
if(autoFocus && blurCount == 0)
|
||||
{
|
||||
onBlurRest.onBlur = (event: React.SyntheticEvent) =>
|
||||
{
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
setBlurCount(blurCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
function eventHandler(event: KeyboardEvent)
|
||||
{
|
||||
if(data.values.submitOnEnter && event.key == "Enter")
|
||||
{
|
||||
// @ts-ignore target.value...
|
||||
const inputValue = event.target.value?.trim()
|
||||
|
||||
// todo - make this behavior opt-in for inputBlocks?
|
||||
if(inputValue && `${inputValue}`.startsWith("->"))
|
||||
{
|
||||
const actionCode = inputValue.substring(2);
|
||||
if(actionCallback)
|
||||
{
|
||||
actionCallback(data, {actionCode: actionCode, _fieldToClearIfError: fieldMetaData.name});
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// return, so we don't submit the actionCode as text //
|
||||
///////////////////////////////////////////////////////
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(fieldMetaData.isRequired && inputValue == "")
|
||||
{
|
||||
console.log("input field is required, but missing value, so not submitting");
|
||||
return;
|
||||
}
|
||||
|
||||
if(actionCallback)
|
||||
{
|
||||
console.log("InputFieldBlock calling actionCallback for submitOnEnter");
|
||||
|
||||
let values: {[name: string]: any} = {};
|
||||
values[fieldMetaData.name] = inputValue;
|
||||
|
||||
actionCallback(data, values);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log("InputFieldBlock was set as submitOnEnter, but no actionCallback was present, so, noop");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const labelElement = <Box fontSize="1rem" fontWeight="500" marginBottom="0.25rem">
|
||||
<label htmlFor={fieldMetaData.name}>{fieldMetaData.label}</label>
|
||||
</Box>
|
||||
|
||||
return (
|
||||
<Box mt="0.5rem">
|
||||
<BlockElementWrapper metaData={widgetMetaData} data={data} slot="">
|
||||
<>
|
||||
{labelElement}
|
||||
<QDynamicFormField name={fieldMetaData.name} displayFormat={null} label="" formFieldObject={dynamicField} type={fieldMetaData.type} value={value} autoFocus={autoFocus} onKeyUp={eventHandler} {...onBlurRest} />
|
||||
</>
|
||||
</BlockElementWrapper>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -19,8 +19,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import BlockElementWrapper from "qqq/components/widgets/blocks/BlockElementWrapper";
|
||||
import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockModels";
|
||||
import DumpJsonBox from "qqq/utils/DumpJsonBox";
|
||||
|
||||
/*******************************************************************************
|
||||
** Block that renders ... just some text.
|
||||
@ -29,9 +31,54 @@ import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockMo
|
||||
*******************************************************************************/
|
||||
export default function TextBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element
|
||||
{
|
||||
let color = "rgba(0, 0, 0, 0.87)";
|
||||
if (data.styles?.standardColor)
|
||||
{
|
||||
switch (data.styles?.standardColor)
|
||||
{
|
||||
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 = {};
|
||||
if (data.styles?.isAlert)
|
||||
{
|
||||
boxStyle =
|
||||
{
|
||||
border: `1px solid ${color}`,
|
||||
background: `${color}40`,
|
||||
padding: "0.5rem",
|
||||
borderRadius: "0.5rem",
|
||||
};
|
||||
}
|
||||
|
||||
const text = data.values.interpolatedText ?? data.values.text;
|
||||
const lines = text.split("\n");
|
||||
|
||||
return (
|
||||
<BlockElementWrapper metaData={widgetMetaData} data={data} slot="">
|
||||
<span style={{fontSize: "1.000rem"}}>{data.values.text}</span>
|
||||
<Box display="inline-block" lineHeight="1.2" sx={boxStyle}>
|
||||
<span style={{fontSize: "1rem", color: color}}>
|
||||
{lines.map((line: string, index: number) =>
|
||||
(
|
||||
<div key={index}>{line}</div>
|
||||
))
|
||||
}</span>
|
||||
</Box>
|
||||
</BlockElementWrapper>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user