From 323fa52455561201459f862545dc35dac4e0b416 Mon Sep 17 00:00:00 2001 From: Bhavin K <58818598+techbhavin@users.noreply.github.com> Date: Wed, 29 Dec 2021 17:31:19 +0530 Subject: [PATCH] feat: add progressbar widget (#9574) --- .../DisplayWidgets/ProgressBar_spec.js | 25 +++ .../assets/icons/widget/progressbar-icon.svg | 15 ++ app/client/src/constants/HelpConstants.ts | 4 + app/client/src/icons/WidgetIcons.tsx | 6 + app/client/src/utils/WidgetRegistry.tsx | 4 + .../utils/autocomplete/EntityDefinitions.ts | 6 + .../utils/autocomplete/dataTypeSortRules.ts | 2 + .../ProgressBarWidget/component/index.tsx | 122 ++++++++++++++ .../widgets/ProgressBarWidget/constants.ts | 7 + .../src/widgets/ProgressBarWidget/icon.svg | 15 ++ .../src/widgets/ProgressBarWidget/index.ts | 33 ++++ .../ProgressBarWidget/widget/index.tsx | 150 ++++++++++++++++++ 12 files changed, 389 insertions(+) create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/ProgressBar_spec.js create mode 100644 app/client/src/assets/icons/widget/progressbar-icon.svg create mode 100644 app/client/src/widgets/ProgressBarWidget/component/index.tsx create mode 100644 app/client/src/widgets/ProgressBarWidget/constants.ts create mode 100644 app/client/src/widgets/ProgressBarWidget/icon.svg create mode 100644 app/client/src/widgets/ProgressBarWidget/index.ts create mode 100644 app/client/src/widgets/ProgressBarWidget/widget/index.tsx diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/ProgressBar_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/ProgressBar_spec.js new file mode 100644 index 0000000000..de195505bc --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/ProgressBar_spec.js @@ -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%"); + }); +}); diff --git a/app/client/src/assets/icons/widget/progressbar-icon.svg b/app/client/src/assets/icons/widget/progressbar-icon.svg new file mode 100644 index 0000000000..a61c2afb7d --- /dev/null +++ b/app/client/src/assets/icons/widget/progressbar-icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/app/client/src/constants/HelpConstants.ts b/app/client/src/constants/HelpConstants.ts index ba079d7b7a..4393bcf9a4 100644 --- a/app/client/src/constants/HelpConstants.ts +++ b/app/client/src/constants/HelpConstants.ts @@ -167,6 +167,10 @@ export const HelpMap: Record = { 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", diff --git a/app/client/src/icons/WidgetIcons.tsx b/app/client/src/icons/WidgetIcons.tsx index 3d3df1bd5d..3e4839943a 100644 --- a/app/client/src/icons/WidgetIcons.tsx +++ b/app/client/src/icons/WidgetIcons.tsx @@ -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: { ), + PROGRESSBAR_WIDGET: (props: IconProps) => ( + + + + ), SWITCH_GROUP_WIDGET: (props: IconProps) => ( diff --git a/app/client/src/utils/WidgetRegistry.tsx b/app/client/src/utils/WidgetRegistry.tsx index 9bd545a788..3903610ce3 100644 --- a/app/client/src/utils/WidgetRegistry.tsx +++ b/app/client/src/utils/WidgetRegistry.tsx @@ -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"); diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index 7f3cfd0090..dd6671a329 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -424,6 +424,12 @@ export const entityDefinitions: Record = { 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", diff --git a/app/client/src/utils/autocomplete/dataTypeSortRules.ts b/app/client/src/utils/autocomplete/dataTypeSortRules.ts index f6ef0dd04c..9573f10af9 100644 --- a/app/client/src/utils/autocomplete/dataTypeSortRules.ts +++ b/app/client/src/utils/autocomplete/dataTypeSortRules.ts @@ -46,6 +46,7 @@ const RULES: Record> = { "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> = { "FORM_BUTTON_WIDGET.isDisabled", "FILE_PICKER_WIDGET.isRequired", "MODAL_WIDGET.isOpen", + "PROGRESSBAR_WIDGET.isVisible", ], FUNCTION: [ "ACTION.run()", diff --git a/app/client/src/widgets/ProgressBarWidget/component/index.tsx b/app/client/src/widgets/ProgressBarWidget/component/index.tsx new file mode 100644 index 0000000000..4d9295e530 --- /dev/null +++ b/app/client/src/widgets/ProgressBarWidget/component/index.tsx @@ -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 ( + + {[...Array(Number(props.steps))].map((_, index) => { + const width = getProgressPosition( + Number(props.progress), + stepSize, + index, + ); + return ( + + + + ); + })} + + ); +} + +function ProgressBarComponent(props: ProgressBarComponentProps) { + const isDeterminate = + props.barType === BarType.DETERMINATE && !isNaN(Number(props.steps)); + return ( + + {isDeterminate ? ( + + ) : ( + + )} + {props.showResult && } + + ); +} +export interface ProgressBarComponentProps { + progress?: number; + showResult: boolean; + fillColor: string; + barType: BarType; + steps: number; +} + +export default ProgressBarComponent; diff --git a/app/client/src/widgets/ProgressBarWidget/constants.ts b/app/client/src/widgets/ProgressBarWidget/constants.ts new file mode 100644 index 0000000000..ca7e69b9c1 --- /dev/null +++ b/app/client/src/widgets/ProgressBarWidget/constants.ts @@ -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", +} diff --git a/app/client/src/widgets/ProgressBarWidget/icon.svg b/app/client/src/widgets/ProgressBarWidget/icon.svg new file mode 100644 index 0000000000..a61c2afb7d --- /dev/null +++ b/app/client/src/widgets/ProgressBarWidget/icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/app/client/src/widgets/ProgressBarWidget/index.ts b/app/client/src/widgets/ProgressBarWidget/index.ts new file mode 100644 index 0000000000..0ef5e78b01 --- /dev/null +++ b/app/client/src/widgets/ProgressBarWidget/index.ts @@ -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; diff --git a/app/client/src/widgets/ProgressBarWidget/widget/index.tsx b/app/client/src/widgets/ProgressBarWidget/widget/index.tsx new file mode 100644 index 0000000000..92cf878a29 --- /dev/null +++ b/app/client/src/widgets/ProgressBarWidget/widget/index.tsx @@ -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 { + return {}; + } + + static getMetaPropertiesMap(): Record { + return {}; + } + + getPageView() { + return ( + + ); + } + + static getWidgetType(): string { + return "PROGRESSBAR_WIDGET"; + } +} + +export interface ProgressBarWidgetProps extends WidgetProps { + progress?: number; + showResult: boolean; + fillColor: string; + barType: BarType; + steps: number; +} + +export default ProgressBarWidget;