feat: add progressbar widget (#9574)
This commit is contained in:
parent
e995865b28
commit
323fa52455
|
|
@ -0,0 +1,25 @@
|
|||
const explorer = require("../../../../locators/explorerlocators.json");
|
||||
|
||||
describe("ProgressBar Widget Functionality", function() {
|
||||
it("Add new Progress Bar", () => {
|
||||
cy.get(explorer.addWidget).click();
|
||||
cy.dragAndDropToCanvas("progressbarwidget", { x: 300, y: 300 });
|
||||
cy.get(".t--progressbar-widget").should("exist");
|
||||
});
|
||||
|
||||
it("Update Progress bar properties and validate", () => {
|
||||
// add progress value
|
||||
cy.testJsontext("progress", 30);
|
||||
// show result
|
||||
cy.get(".t--property-control-showresult .t--js-toggle").click({
|
||||
force: true,
|
||||
});
|
||||
cy.testJsontext("showresult", "true");
|
||||
cy.wait(200);
|
||||
cy.get(`.t--progressbar-widget > div[data-cy='${30}']`).should("exist");
|
||||
|
||||
cy.get(".t--progressbar-widget > div")
|
||||
.eq(1)
|
||||
.should("have.text", "30%");
|
||||
});
|
||||
});
|
||||
15
app/client/src/assets/icons/widget/progressbar-icon.svg
Normal file
15
app/client/src/assets/icons/widget/progressbar-icon.svg
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_172_5437)">
|
||||
<path d="M2 9H14V15H2V9Z" fill="#4B4848"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 9.75L15 9H17L14 11.25V9.75Z" fill="#4B4848"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 12.75L19 9H21L14 14.25V12.75Z" fill="#4B4848"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 14.25V12.75L19 15H21L22 14.25Z" fill="#4B4848"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 11.25V9.75L15 15H17L22 11.25Z" fill="#4B4848"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21 9H19L14 12.75V14.25L21 9ZM15 15H17L22 11.25V9.75L15 15ZM21 15H19L22 12.75V14.25L21 15ZM15 9H17L14 11.25V9.75L15 9Z" fill="#4B4848"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_172_5437">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 879 B |
|
|
@ -167,6 +167,10 @@ export const HelpMap: Record<string, { path: string; searchKey: string }> = {
|
|||
path: "/widget-reference/audio-recorder",
|
||||
searchKey: "Audio Recorder",
|
||||
},
|
||||
PROGRESSBAR_WIDGET: {
|
||||
path: "/widget-reference/progressbar",
|
||||
searchKey: "Progress Bar",
|
||||
},
|
||||
SWITCH_GROUP_WIDGET: {
|
||||
path: "/widget-reference/switch-group",
|
||||
searchKey: "Switch Group",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import { ReactComponent as StatboxIcon } from "assets/icons/widget/statbox.svg";
|
|||
import { ReactComponent as CheckboxGroupIcon } from "assets/icons/widget/checkbox-group.svg";
|
||||
import { ReactComponent as AudioRecorderIcon } from "assets/icons/widget/audio-recorder.svg";
|
||||
import { ReactComponent as ButtonGroupIcon } from "assets/icons/widget/button-group.svg";
|
||||
import { ReactComponent as ProgressBarIcon } from "assets/icons/widget/progressbar-icon.svg";
|
||||
import { ReactComponent as SwitchGroupIcon } from "assets/icons/widget/switch-group.svg";
|
||||
import { ReactComponent as CameraIcon } from "assets/icons/widget/camera.svg";
|
||||
|
||||
|
|
@ -228,6 +229,11 @@ export const WidgetIcons: {
|
|||
<ButtonGroupIcon />
|
||||
</StyledIconWrapper>
|
||||
),
|
||||
PROGRESSBAR_WIDGET: (props: IconProps) => (
|
||||
<StyledIconWrapper {...props}>
|
||||
<ProgressBarIcon />
|
||||
</StyledIconWrapper>
|
||||
),
|
||||
SWITCH_GROUP_WIDGET: (props: IconProps) => (
|
||||
<StyledIconWrapper {...props}>
|
||||
<SwitchGroupIcon />
|
||||
|
|
|
|||
|
|
@ -108,6 +108,9 @@ import SingleSelectTreeWidget, {
|
|||
import MultiSelectTreeWidget, {
|
||||
CONFIG as MULTI_SELECT_TREE_WIDGET_CONFIG,
|
||||
} from "widgets/MultiSelectTreeWidget";
|
||||
import ProgressBarWidget, {
|
||||
CONFIG as PROGRESSBAR_WIDGET_CONFIG,
|
||||
} from "widgets/ProgressBarWidget";
|
||||
import SwitchGroupWidget, {
|
||||
CONFIG as SWITCH_GROUP_WIDGET_CONFIG,
|
||||
} from "widgets/SwitchGroupWidget";
|
||||
|
|
@ -162,6 +165,7 @@ export const registerWidgets = () => {
|
|||
registerWidget(SingleSelectTreeWidget, SINGLE_SELECT_TREE_WIDGET_CONFIG);
|
||||
registerWidget(SwitchGroupWidget, SWITCH_GROUP_WIDGET_CONFIG);
|
||||
registerWidget(AudioWidget, AUDIO_WIDGET_CONFIG);
|
||||
registerWidget(ProgressBarWidget, PROGRESSBAR_WIDGET_CONFIG);
|
||||
registerWidget(CameraWidget, CAMERA_WIDGET_CONFIG);
|
||||
|
||||
log.debug("Widget registration took: ", performance.now() - start, "ms");
|
||||
|
|
|
|||
|
|
@ -424,6 +424,12 @@ export const entityDefinitions: Record<string, unknown> = {
|
|||
dataURL: "string",
|
||||
rawBinary: "string",
|
||||
},
|
||||
PROGRESSBAR_WIDGET: {
|
||||
"!doc": "Progress bar is a simple UI widget used to show progress",
|
||||
"!url": "https://docs.appsmith.com/widget-reference/progressbar",
|
||||
isVisible: isVisible,
|
||||
progress: "number",
|
||||
},
|
||||
SWITCH_GROUP_WIDGET: {
|
||||
"!doc":
|
||||
"Switch group widget allows users to create many switch components which can easily by used in a form",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ const RULES: Record<AutocompleteDataType, Array<string>> = {
|
|||
"CHART_WIDGET.xAxisName",
|
||||
"CHART_WIDGET.yAxisName",
|
||||
"CONTAINER_WIDGET.backgroundColor",
|
||||
"PROGRESSBAR_WIDGET.progress",
|
||||
],
|
||||
OBJECT: ["ACTION.data"],
|
||||
ARRAY: ["ACTION.data", "TABLE_WIDGET.selectedRowIndices"],
|
||||
|
|
@ -84,6 +85,7 @@ const RULES: Record<AutocompleteDataType, Array<string>> = {
|
|||
"FORM_BUTTON_WIDGET.isDisabled",
|
||||
"FILE_PICKER_WIDGET.isRequired",
|
||||
"MODAL_WIDGET.isOpen",
|
||||
"PROGRESSBAR_WIDGET.isVisible",
|
||||
],
|
||||
FUNCTION: [
|
||||
"ACTION.run()",
|
||||
|
|
|
|||
122
app/client/src/widgets/ProgressBarWidget/component/index.tsx
Normal file
122
app/client/src/widgets/ProgressBarWidget/component/index.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { Colors } from "constants/Colors";
|
||||
import { BarType } from "../constants";
|
||||
import { isNaN } from "lodash";
|
||||
|
||||
const ProgressBarWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const ProgressBar = styled.div<{ progress?: number; fillColor: string }>`
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: #e8e8e8;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
background: ${({ fillColor }) => fillColor};
|
||||
width: ${({ progress }) => (progress ? `${progress}%` : "")};
|
||||
transition: width 0.4s ease;
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const Label = styled.div`
|
||||
font-size: 14px;
|
||||
color: ${Colors.GREY_10};
|
||||
padding-left: 4px;
|
||||
line-height: 16px;
|
||||
`;
|
||||
|
||||
const StepWrapper = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
position: relative;
|
||||
margin: 0px -2px;
|
||||
`;
|
||||
|
||||
const StepContainer = styled.div`
|
||||
flex: 1;
|
||||
background: #e8e8e8;
|
||||
margin: 0px 1px;
|
||||
`;
|
||||
|
||||
const getProgressPosition = (
|
||||
percentage: number,
|
||||
stepSize: number,
|
||||
currentStep: number,
|
||||
) => {
|
||||
const currStepProgress = percentage - stepSize * currentStep;
|
||||
if (currStepProgress > stepSize) {
|
||||
return 100;
|
||||
} else if (currStepProgress < 0) {
|
||||
return 0;
|
||||
} else if (currStepProgress <= stepSize) {
|
||||
return (currStepProgress / stepSize) * 100;
|
||||
} else {
|
||||
// just placeholder for typescript
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
function StepProgressBar(props: ProgressBarComponentProps) {
|
||||
const { steps } = props;
|
||||
const stepSize = 100 / steps;
|
||||
|
||||
return (
|
||||
<StepWrapper>
|
||||
{[...Array(Number(props.steps))].map((_, index) => {
|
||||
const width = getProgressPosition(
|
||||
Number(props.progress),
|
||||
stepSize,
|
||||
index,
|
||||
);
|
||||
return (
|
||||
<StepContainer key={index}>
|
||||
<ProgressBar
|
||||
data-cy={width}
|
||||
fillColor={props.fillColor}
|
||||
progress={width}
|
||||
/>
|
||||
</StepContainer>
|
||||
);
|
||||
})}
|
||||
</StepWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
function ProgressBarComponent(props: ProgressBarComponentProps) {
|
||||
const isDeterminate =
|
||||
props.barType === BarType.DETERMINATE && !isNaN(Number(props.steps));
|
||||
return (
|
||||
<ProgressBarWrapper className="t--progressbar-widget">
|
||||
{isDeterminate ? (
|
||||
<StepProgressBar {...props} />
|
||||
) : (
|
||||
<ProgressBar
|
||||
data-cy={props.progress}
|
||||
fillColor={props.fillColor}
|
||||
progress={props.progress}
|
||||
/>
|
||||
)}
|
||||
{props.showResult && <Label>{props.progress}%</Label>}
|
||||
</ProgressBarWrapper>
|
||||
);
|
||||
}
|
||||
export interface ProgressBarComponentProps {
|
||||
progress?: number;
|
||||
showResult: boolean;
|
||||
fillColor: string;
|
||||
barType: BarType;
|
||||
steps: number;
|
||||
}
|
||||
|
||||
export default ProgressBarComponent;
|
||||
7
app/client/src/widgets/ProgressBarWidget/constants.ts
Normal file
7
app/client/src/widgets/ProgressBarWidget/constants.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// This file contains common constants which can be used across the widget configuration file (index.ts), widget and component folders.
|
||||
export const PROGRESSBAR_WIDGET_CONSTANT = "";
|
||||
|
||||
export enum BarType {
|
||||
INDETERMINATE = "indeterminate",
|
||||
DETERMINATE = "determinate",
|
||||
}
|
||||
15
app/client/src/widgets/ProgressBarWidget/icon.svg
Normal file
15
app/client/src/widgets/ProgressBarWidget/icon.svg
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_172_5437)">
|
||||
<path d="M2 9H14V15H2V9Z" fill="#4B4848"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 9.75L15 9H17L14 11.25V9.75Z" fill="#4B4848"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 12.75L19 9H21L14 14.25V12.75Z" fill="#4B4848"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 14.25V12.75L19 15H21L22 14.25Z" fill="#4B4848"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 11.25V9.75L15 15H17L22 11.25Z" fill="#4B4848"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21 9H19L14 12.75V14.25L21 9ZM15 15H17L22 11.25V9.75L15 15ZM21 15H19L22 12.75V14.25L21 15ZM15 9H17L14 11.25V9.75L15 9Z" fill="#4B4848"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_172_5437">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 879 B |
33
app/client/src/widgets/ProgressBarWidget/index.ts
Normal file
33
app/client/src/widgets/ProgressBarWidget/index.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import Widget from "./widget";
|
||||
import IconSVG from "./icon.svg";
|
||||
import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { BarType } from "./constants";
|
||||
|
||||
export const CONFIG = {
|
||||
type: Widget.getWidgetType(),
|
||||
name: "Progress Bar", // The display name which will be made in uppercase and show in the widgets panel ( can have spaces )
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: false, // Defines if this widget adds any meta properties
|
||||
isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets
|
||||
defaults: {
|
||||
widgetName: "ProgressBar",
|
||||
rows: 0.9 * GRID_DENSITY_MIGRATION_V1,
|
||||
columns: 7 * GRID_DENSITY_MIGRATION_V1,
|
||||
isVisible: true,
|
||||
fillColor: Colors.GREEN,
|
||||
showResult: false,
|
||||
barType: BarType.INDETERMINATE,
|
||||
progress: 50,
|
||||
steps: 1,
|
||||
version: 1,
|
||||
},
|
||||
properties: {
|
||||
derived: Widget.getDerivedPropertiesMap(),
|
||||
default: Widget.getDefaultPropertiesMap(),
|
||||
meta: Widget.getMetaPropertiesMap(),
|
||||
config: Widget.getPropertyPaneConfig(),
|
||||
},
|
||||
};
|
||||
|
||||
export default Widget;
|
||||
150
app/client/src/widgets/ProgressBarWidget/widget/index.tsx
Normal file
150
app/client/src/widgets/ProgressBarWidget/widget/index.tsx
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import React from "react";
|
||||
|
||||
import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
|
||||
import { DerivedPropertiesMap } from "utils/WidgetFactory";
|
||||
|
||||
import ProgressBarComponent from "../component";
|
||||
|
||||
import { ValidationTypes } from "constants/WidgetValidation";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { BarType } from "../constants";
|
||||
|
||||
class ProgressBarWidget extends BaseWidget<
|
||||
ProgressBarWidgetProps,
|
||||
WidgetState
|
||||
> {
|
||||
static getPropertyPaneConfig() {
|
||||
return [
|
||||
{
|
||||
sectionName: "General",
|
||||
children: [
|
||||
{
|
||||
helpText: "Sets progress bar type",
|
||||
propertyName: "barType",
|
||||
label: "Type",
|
||||
controlType: "DROP_DOWN",
|
||||
options: [
|
||||
{
|
||||
label: "Indeterminate",
|
||||
value: BarType.INDETERMINATE,
|
||||
},
|
||||
{
|
||||
label: "Determinate",
|
||||
value: BarType.DETERMINATE,
|
||||
},
|
||||
],
|
||||
defaultValue: BarType.INDETERMINATE,
|
||||
isBindProperty: false,
|
||||
isTriggerProperty: false,
|
||||
},
|
||||
{
|
||||
helpText: "Provide progress value",
|
||||
propertyName: "progress",
|
||||
label: "Progress",
|
||||
controlType: "INPUT_TEXT",
|
||||
placeholderText: "Enter progress value",
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
isJSConvertible: true,
|
||||
defaultValue: 50,
|
||||
validation: {
|
||||
type: ValidationTypes.NUMBER,
|
||||
params: { min: 0, max: 100, default: 50 },
|
||||
},
|
||||
},
|
||||
{
|
||||
helpText: "Sets a number of steps",
|
||||
propertyName: "steps",
|
||||
label: "Number of steps",
|
||||
controlType: "INPUT_TEXT",
|
||||
placeholderText: "Enter number of steps",
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
isJSConvertible: true,
|
||||
validation: {
|
||||
type: ValidationTypes.NUMBER,
|
||||
params: { min: 1, max: 100, default: 1, natural: true },
|
||||
},
|
||||
hidden: (props: ProgressBarWidgetProps) => {
|
||||
return props.barType !== BarType.DETERMINATE;
|
||||
},
|
||||
dependencies: ["barType"],
|
||||
},
|
||||
{
|
||||
helpText: "Controls the visibility of progress value",
|
||||
propertyName: "showResult",
|
||||
label: "Show result",
|
||||
controlType: "SWITCH",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: { type: ValidationTypes.BOOLEAN },
|
||||
},
|
||||
{
|
||||
helpText: "Controls the visibility of the widget",
|
||||
propertyName: "isVisible",
|
||||
label: "Visible",
|
||||
controlType: "SWITCH",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: { type: ValidationTypes.BOOLEAN },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionName: "Styles",
|
||||
children: [
|
||||
{
|
||||
helpText: "Controls the progress color of progress bar",
|
||||
propertyName: "fillColor",
|
||||
label: "Fill Color",
|
||||
controlType: "COLOR_PICKER",
|
||||
defaultColor: Colors.GREEN,
|
||||
isBindProperty: true,
|
||||
isJSConvertible: true,
|
||||
isTriggerProperty: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
static getDerivedPropertiesMap(): DerivedPropertiesMap {
|
||||
return {};
|
||||
}
|
||||
|
||||
static getDefaultPropertiesMap(): Record<string, string> {
|
||||
return {};
|
||||
}
|
||||
|
||||
static getMetaPropertiesMap(): Record<string, any> {
|
||||
return {};
|
||||
}
|
||||
|
||||
getPageView() {
|
||||
return (
|
||||
<ProgressBarComponent
|
||||
barType={this.props.barType}
|
||||
fillColor={this.props.fillColor}
|
||||
progress={this.props.progress}
|
||||
showResult={this.props.showResult}
|
||||
steps={this.props.steps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
static getWidgetType(): string {
|
||||
return "PROGRESSBAR_WIDGET";
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProgressBarWidgetProps extends WidgetProps {
|
||||
progress?: number;
|
||||
showResult: boolean;
|
||||
fillColor: string;
|
||||
barType: BarType;
|
||||
steps: number;
|
||||
}
|
||||
|
||||
export default ProgressBarWidget;
|
||||
Loading…
Reference in New Issue
Block a user