From 5016d76b15dc2128dc06748af3fbfe91fbadd15a Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 21 Jun 2023 11:53:37 -0500 Subject: [PATCH] Fix dropdown flickering --- src/qqq/components/widgets/Widget.tsx | 301 +++++++++++++++----------- 1 file changed, 178 insertions(+), 123 deletions(-) diff --git a/src/qqq/components/widgets/Widget.tsx b/src/qqq/components/widgets/Widget.tsx index 6ad8a89..da59312 100644 --- a/src/qqq/components/widgets/Widget.tsx +++ b/src/qqq/components/widgets/Widget.tsx @@ -30,8 +30,7 @@ import Tooltip from "@mui/material/Tooltip/Tooltip"; import Typography from "@mui/material/Typography"; import parse from "html-react-parser"; import React, {useEffect, useState} from "react"; -import {Link, useNavigate, NavigateFunction} from "react-router-dom"; -import {bool} from "yup"; +import {Link, NavigateFunction, useNavigate} from "react-router-dom"; import colors from "qqq/components/legacy/colors"; import DropdownMenu, {DropdownOption} from "qqq/components/widgets/components/DropdownMenu"; @@ -94,7 +93,9 @@ export class LabelComponent } - +/******************************************************************************* + ** + *******************************************************************************/ export class HeaderLink extends LabelComponent { label: string; @@ -118,7 +119,9 @@ export class HeaderLink extends LabelComponent } - +/******************************************************************************* + ** + *******************************************************************************/ export class AddNewRecordButton extends LabelComponent { table: QTableMetaData; @@ -152,6 +155,9 @@ export class AddNewRecordButton extends LabelComponent } +/******************************************************************************* + ** + *******************************************************************************/ export class ExportDataButton extends LabelComponent { callbackToExport: any; @@ -177,26 +183,30 @@ export class ExportDataButton extends LabelComponent } +/******************************************************************************* + ** + *******************************************************************************/ export class Dropdown extends LabelComponent { label: string; options: DropdownOption[]; - onChangeCallback: any + dropdownName: string; + onChangeCallback: any; - constructor(label: string, options: DropdownOption[], onChangeCallback: any) + constructor(label: string, options: DropdownOption[], dropdownName: string, onChangeCallback: any) { super(); this.label = label; this.options = options; + this.dropdownName = dropdownName; this.onChangeCallback = onChangeCallback; } render = (args: LabelComponentRenderArgs): JSX.Element => { let defaultValue = null; - const dropdownName = args.widgetProps.widgetData.dropdownNameList[args.componentIndex]; - const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${args.widgetProps.widgetMetaData.name}.${dropdownName}`; - if(args.widgetProps.storeDropdownSelections) + const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${args.widgetProps.widgetMetaData.name}.${this.dropdownName}`; + if (args.widgetProps.storeDropdownSelections) { /////////////////////////////////////////////////////////////////////////////////////// // see if an existing value is stored in local storage, and if so set it in dropdown // @@ -208,7 +218,7 @@ export class Dropdown extends LabelComponent return ( void; @@ -235,7 +248,7 @@ export class ReloadControl extends LabelComponent { return ( - + ); } @@ -245,59 +258,101 @@ export class ReloadControl extends LabelComponent export const WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT = "qqq.widgets.dropdownData"; +/******************************************************************************* + ** + *******************************************************************************/ function Widget(props: React.PropsWithChildren): JSX.Element { const navigate = useNavigate(); const [dropdownData, setDropdownData] = useState([]); const [fullScreenWidgetClassName, setFullScreenWidgetClassName] = useState(""); const [reloading, setReloading] = useState(false); + const [dropdownDataJSON, setDropdownDataJSON] = useState(""); + const [labelComponentsLeft, setLabelComponentsLeft] = useState([] as LabelComponent[]); + const [labelComponentsRight, setLabelComponentsRight] = useState([] as LabelComponent[]); function renderComponent(component: LabelComponent, componentIndex: number) { - return component.render({navigate: navigate, widgetProps: props, dropdownData: dropdownData, componentIndex: componentIndex, reloadFunction: doReload}) + return component.render({navigate: navigate, widgetProps: props, dropdownData: dropdownData, componentIndex: componentIndex, reloadFunction: doReload}); } - - /////////////////////////////////////////////////////////////////// - // make dropdowns from the widgetData appear as label-components // - /////////////////////////////////////////////////////////////////// - const effectiveLabelAdditionalComponentsRight: LabelComponent[] = []; - if(props.labelAdditionalComponentsRight) + useEffect(() => { - props.labelAdditionalComponentsRight.map((component) => effectiveLabelAdditionalComponentsRight.push(component)); - } - if(props.widgetData && props.widgetData.dropdownDataList) - { - props.widgetData.dropdownDataList?.map((dropdownData: any, index: number) => + //////////////////////////////////////////////////////////////////////////////// + // for initial render, put left-components from props into the state variable // + // plus others we can infer from other props // + //////////////////////////////////////////////////////////////////////////////// + const stateLabelComponentsLeft: LabelComponent[] = []; + if (props.reloadWidgetCallback && props.widgetData && props.showReloadControl && props.widgetMetaData.showReloadButton) { - effectiveLabelAdditionalComponentsRight.push(new Dropdown(props.widgetData.dropdownLabelList[index], dropdownData, handleDataChange)) - }); + stateLabelComponentsLeft.push(new ReloadControl(doReload)); + } + if (props.labelAdditionalComponentsLeft) + { + props.labelAdditionalComponentsLeft.map((component) => stateLabelComponentsLeft.push(component)); + } + setLabelComponentsLeft(stateLabelComponentsLeft); + }, []); + + useEffect(() => + { + ///////////////////////////////////////////////////////////////////////////////// + // for initial render, put right-components from props into the state variable // + ///////////////////////////////////////////////////////////////////////////////// + const stateLabelComponentsRight = [] as LabelComponent[]; + // console.log(`${props.widgetMetaData.name} init'ing right-components`); + if (props.labelAdditionalComponentsRight) + { + props.labelAdditionalComponentsRight.map((component) => stateLabelComponentsRight.push(component)); + } + setLabelComponentsRight(stateLabelComponentsRight); + }, []); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////// + // if we have widgetData, and it has a dropdown list, capture that in a state variable, if it's changed // + ////////////////////////////////////////////////////////////////////////////////////////////////////////// + if (props.widgetData && props.widgetData.dropdownDataList) + { + const currentDropdownDataJSON = JSON.stringify(props.widgetData.dropdownDataList); + if (currentDropdownDataJSON !== dropdownDataJSON) + { + // console.log(`${props.widgetMetaData.name} we have (new) dropdown data!!: ${currentDropdownDataJSON}`); + setDropdownDataJSON(currentDropdownDataJSON); + } } + useEffect(() => + { + /////////////////////////////////////////////////////////////////////////////////// + // if we've seen a change in the dropdown data, then update the right-components // + /////////////////////////////////////////////////////////////////////////////////// + // console.log(`${props.widgetMetaData.name} in useEffect post dropdownData change`); + if (props.widgetData && props.widgetData.dropdownDataList) + { + const updatedStateLabelComponentsRight = JSON.parse(JSON.stringify(labelComponentsRight)) as LabelComponent[]; + props.widgetData.dropdownDataList?.map((dropdownData: any, index: number) => + { + // console.log(`${props.widgetMetaData.name} building a Dropdown, data is: ${dropdownData}`); + updatedStateLabelComponentsRight.push(new Dropdown(props.widgetData.dropdownLabelList[index], dropdownData, props.widgetData.dropdownNameList[index], handleDataChange)); + }); + setLabelComponentsRight(updatedStateLabelComponentsRight); + } + }, [dropdownDataJSON]); + const doReload = () => { setReloading(true); reloadWidget(dropdownData); - } + }; useEffect(() => { setReloading(false); }, [props.widgetData]); - const effectiveLabelAdditionalComponentsLeft: LabelComponent[] = []; - if(props.reloadWidgetCallback && props.widgetData && props.showReloadControl && props.widgetMetaData.showReloadButton) - { - effectiveLabelAdditionalComponentsLeft.push(new ReloadControl(doReload)) - } - if(props.labelAdditionalComponentsLeft) - { - props.labelAdditionalComponentsLeft.map((component) => effectiveLabelAdditionalComponentsLeft.push(component)); - } - function handleDataChange(dropdownLabel: string, changedData: any) { - if(dropdownData) + if (dropdownData) { /////////////////////////////////////////// // find the index base on selected label // @@ -327,7 +382,7 @@ function Widget(props: React.PropsWithChildren): JSX.Element // if should store in local storage, do so now // // or remove if dropdown was cleared out // ///////////////////////////////////////////////// - if(props.storeDropdownSelections) + if (props.storeDropdownSelections) { if (changedData?.id) { @@ -371,7 +426,7 @@ function Widget(props: React.PropsWithChildren): JSX.Element const toggleFullScreenWidget = () => { - if(fullScreenWidgetClassName) + if (fullScreenWidgetClassName) { setFullScreenWidgetClassName(""); } @@ -385,17 +440,17 @@ function Widget(props: React.PropsWithChildren): JSX.Element const isSet = (v: any): boolean => { - return(v !== null && v !== undefined); + return (v !== null && v !== undefined); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // to avoid taking up the space of the Box with the label and icon and label-components (since it has a height), only output that box if we need any of the components // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// let needLabelBox = false; - if(hasPermission) + if (hasPermission) { - needLabelBox ||= (effectiveLabelAdditionalComponentsLeft && effectiveLabelAdditionalComponentsLeft.length > 0); - needLabelBox ||= (effectiveLabelAdditionalComponentsRight && effectiveLabelAdditionalComponentsRight.length > 0); + needLabelBox ||= (labelComponentsLeft && labelComponentsLeft.length > 0); + needLabelBox ||= (labelComponentsRight && labelComponentsRight.length > 0); needLabelBox ||= isSet(props.widgetMetaData?.icon); needLabelBox ||= isSet(props.widgetData?.label); needLabelBox ||= isSet(props.widgetMetaData?.label); @@ -406,90 +461,90 @@ function Widget(props: React.PropsWithChildren): JSX.Element { needLabelBox && - - - { - hasPermission ? - props.widgetMetaData?.icon && ( - - - {props.widgetMetaData.icon} - - - - ) : ( - - lock - - ) - } - { - ////////////////////////////////////////////////////////////////////////////////////////// - // first look for a label in the widget data, which would override that in the metadata // - ////////////////////////////////////////////////////////////////////////////////////////// - hasPermission && props.widgetData?.label? ( - - {props.widgetData.label} - - ) : ( - hasPermission && props.widgetMetaData?.label && ( - - {props.widgetMetaData.label} + + + { + hasPermission ? + props.widgetMetaData?.icon && ( + + + {props.widgetMetaData.icon} + + + ) : + ( + + lock + + ) + } + { + ////////////////////////////////////////////////////////////////////////////////////////// + // first look for a label in the widget data, which would override that in the metadata // + ////////////////////////////////////////////////////////////////////////////////////////// + hasPermission && props.widgetData?.label ? ( + + {props.widgetData.label} + ) : ( + hasPermission && props.widgetMetaData?.label && ( + + {props.widgetMetaData.label} + + ) ) - ) - } - { - hasPermission && ( - effectiveLabelAdditionalComponentsLeft.map((component, i) => - { - return ({renderComponent(component, i)}); - }) - ) - } + } + { + hasPermission && ( + labelComponentsLeft.map((component, i) => + { + return ({renderComponent(component, i)}); + }) + ) + } + + + { + hasPermission && ( + labelComponentsRight.map((component, i) => + { + return ({renderComponent(component, i)}); + }) + ) + } + - - { - hasPermission && ( - effectiveLabelAdditionalComponentsRight.map((component, i) => - { - return ({renderComponent(component, i)}); - }) - ) - } - - } { - props.widgetMetaData?.isCard && (reloading ? : ) + props.widgetMetaData?.isCard && (reloading ? : ) } { errorLoading ? ( @@ -514,7 +569,7 @@ function Widget(props: React.PropsWithChildren): JSX.Element ) } { - ! errorLoading && props?.footerHTML && ( + !errorLoading && props?.footerHTML && ( {parse(props.footerHTML)} ) }