feat: add progressbar widget (#9574)

This commit is contained in:
Bhavin K 2021-12-29 17:31:19 +05:30 committed by GitHub
parent e995865b28
commit 323fa52455
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 389 additions and 0 deletions

View File

@ -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%");
});
});

View 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

View File

@ -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",

View File

@ -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 />

View File

@ -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");

View File

@ -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",

View File

@ -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()",

View 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;

View 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",
}

View 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

View 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;

View 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;