From f84b565f20191797f50a9546d1b31dba46a49391 Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Tue, 3 Jun 2025 19:50:01 +0500 Subject: [PATCH 01/51] Added support for grids similar to rjsf --- .../jsonSchemaFormComp/JsonFormsRenderer.tsx | 71 +++++++++++-------- .../jsonSchemaFormComp/jsonSchemaFormComp.tsx | 7 -- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/JsonFormsRenderer.tsx b/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/JsonFormsRenderer.tsx index cbdab8a0f..bc1eaaf65 100644 --- a/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/JsonFormsRenderer.tsx +++ b/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/JsonFormsRenderer.tsx @@ -15,13 +15,11 @@ import { Steps, } from "antd"; import styled from "styled-components"; -import type { JsonSchema } from "@jsonforms/core"; import type { JSONSchema7 } from "json-schema"; import { debounce } from "lodash"; import dayjs from "dayjs"; import { trans } from "i18n"; import type { - JsonFormsUiSchema, FieldUiSchema, Layout, Categorization, @@ -30,10 +28,14 @@ import type { Category, Control } from "./types"; -import type { SwitchChangeEventHandler } from "antd/es/switch"; +import { useContainerWidth } from "./jsonSchemaFormComp"; + const { TextArea } = Input; -const Container = styled.div` +const Container = styled.div +` + gap: 16px; + width: 100%; .ant-form-item { margin-bottom: 16px; } @@ -62,11 +64,6 @@ const Container = styled.div` } `; -interface HorizontalLayout { - type: "HorizontalLayout"; - elements: Control[]; -} - const JsonFormsRenderer: React.FC = ({ schema, data, @@ -78,6 +75,7 @@ const JsonFormsRenderer: React.FC = ({ validationState: externalValidationState, onValidationChange, }) => { + const containerWidth = useContainerWidth(); // Local state to handle immediate updates const [localData, setLocalData] = useState(data); // Track focused field @@ -116,7 +114,7 @@ const JsonFormsRenderer: React.FC = ({ if (!uiSchema) return undefined; // For JSONForms UI schema, we need to find the Control element that matches the path - if (uiSchema.type === "HorizontalLayout" && Array.isArray(uiSchema.elements)) { + if (Array.isArray(uiSchema.elements)) { const control = uiSchema.elements.find((element: any) => { if (element.type === "Control") { // Convert the scope path to match our field path @@ -666,24 +664,41 @@ const JsonFormsRenderer: React.FC = ({ // Fallback to default rendering if not a categorization return ( - -
- {Object.entries(schema.properties || {}).map( - ([key, fieldSchema]: [string, any]) => - renderField(key, fieldSchema, localData?.[key]) - )} - - - -
-
+ +
+ + {Object.entries(schema.properties || {}).map(([key, fieldSchema]) => { + const fieldUiSchema = uiSchema?.[key] || {}; + const colSpan = calculateColSpan(fieldUiSchema, containerWidth); + + return ( + + {renderField(key, fieldSchema, localData?.[key])} + + ); + })} + + + + +
+
); }; -export default React.memo(JsonFormsRenderer); +const calculateColSpan = (uiSchema: any, containerWidth: number) => { + const colSpan = uiSchema?.["ui:colSpan"] || { xs: 24, sm: 24, md: 12, lg: 12, xl: 8 }; + if (containerWidth > 1200 && colSpan.xl) return { span: colSpan.xl }; + if (containerWidth > 992 && colSpan.lg) return { span: colSpan.lg }; + if (containerWidth > 768 && colSpan.md) return { span: colSpan.md }; + if (containerWidth > 576 && colSpan.sm) return { span: colSpan.sm }; + return { span: 24 }; +}; + +export default React.memo(JsonFormsRenderer); \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/jsonSchemaFormComp.tsx b/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/jsonSchemaFormComp.tsx index f0b83c4f5..0705a745b 100644 --- a/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/jsonSchemaFormComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/jsonSchemaFormComp/jsonSchemaFormComp.tsx @@ -483,13 +483,6 @@ let FormBasicComp = (function () { tooltip: "Define custom error messages for form fields. Use __errors array for field-specific errors.", }) ) - // : ( - // children.validationState.propertyView({ - // key: "validationState", - // label: trans("jsonSchemaForm.validationState"), - // tooltip: "Current validation state of the form fields. Shows errors and touched state for each field.", - // }) - // ) } )} From 6a3ce06a3005545345a959ee556947df25ed3a95 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Tue, 3 Jun 2025 22:35:45 +0500 Subject: [PATCH 02/51] [Feat]: #1585 Add handlers for ColumnTypes like buttons, select, links and dropdown --- .../columnTypeComps/columnDropdownComp.tsx | 16 ++++-- .../column/columnTypeComps/columnLinkComp.tsx | 20 +++---- .../columnTypeComps/columnLinksComp.tsx | 51 +++++++++++------- .../columnTypeComps/columnSelectComp.tsx | 52 +++++++++++++++++-- .../column/simpleColumnTypeComps.tsx | 14 ++--- 5 files changed, 108 insertions(+), 45 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx index d71ad03cb..9055413de 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx @@ -15,6 +15,7 @@ import { ButtonStyle } from "comps/controls/styleControlConstants"; import { Button100 } from "comps/comps/buttonComp/buttonCompConstants"; import styled from "styled-components"; import { ButtonType } from "antd/es/button"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; const StyledButton = styled(Button100)` display: flex; @@ -28,18 +29,21 @@ const StyledIconWrapper = styled(IconWrapper)` margin: 0; `; +const DropdownEventOptions = [clickEvent] as const; + const childrenMap = { buttonType: dropdownControl(ButtonTypeOptions, "primary"), label: withDefault(StringControl, 'Menu'), prefixIcon: IconControl, suffixIcon: IconControl, options: DropdownOptionControl, + onEvent: eventHandlerControl(DropdownEventOptions), }; const getBaseValue: ColumnTypeViewFn = (props) => props.label; // Memoized dropdown menu component -const DropdownMenu = React.memo(({ items, options }: { items: any[]; options: any[] }) => { +const DropdownMenu = React.memo(({ items, options, onEvent }: { items: any[]; options: any[]; onEvent?: (eventName: string) => void }) => { const mountedRef = useRef(true); // Cleanup on unmount @@ -54,7 +58,9 @@ const DropdownMenu = React.memo(({ items, options }: { items: any[]; options: an const item = items.find((o) => o.key === key); const itemIndex = options.findIndex(option => option.label === item?.label); item && options[itemIndex]?.onEvent("click"); - }, [items, options]); + // Also trigger the dropdown's main event handler + onEvent?.("click"); + }, [items, options, onEvent]); const handleMouseDown = useCallback((e: React.MouseEvent) => { e.stopPropagation(); @@ -78,6 +84,7 @@ const DropdownView = React.memo((props: { prefixIcon: ReactNode; suffixIcon: ReactNode; options: any[]; + onEvent?: (eventName: string) => void; }) => { const mountedRef = useRef(true); @@ -120,8 +127,8 @@ const DropdownView = React.memo((props: { const buttonStyle = useStyle(ButtonStyle); const menu = useMemo(() => ( - - ), [items, props.options]); + + ), [items, props.options, props.onEvent]); return ( ); }) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx index c82b7326a..512329ee3 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx @@ -11,12 +11,15 @@ import { disabledPropertyView } from "comps/utils/propertyUtils"; import styled, { css } from "styled-components"; import { styleControl } from "comps/controls/styleControl"; import { TableColumnLinkStyle } from "comps/controls/styleControlConstants"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); +const LinkEventOptions = [clickEvent] as const; + const childrenMap = { text: StringControl, - onClick: ActionSelectorControlInContext, + onEvent: eventHandlerControl(LinkEventOptions), disabled: BoolCodeControl, style: styleControl(TableColumnLinkStyle), }; @@ -34,12 +37,12 @@ const StyledLink = styled.a<{ $disabled: boolean }>` `; // Memoized link component -export const ColumnLink = React.memo(({ disabled, label, onClick }: { disabled: boolean; label: string; onClick?: () => void }) => { +export const ColumnLink = React.memo(({ disabled, label, onEvent }: { disabled: boolean; label: string; onEvent?: (eventName: string) => void }) => { const handleClick = useCallback(() => { - if (!disabled && onClick) { - onClick(); + if (!disabled && onEvent) { + onEvent("click"); } - }, [disabled, onClick]); + }, [disabled, onEvent]); return ( { const value = props.changeValue ?? getBaseValue(props, dispatch); - return ; + return ; }, (nodeValue) => nodeValue.text.value, getBaseValue @@ -125,10 +128,7 @@ export const LinkComp = (function () { tooltip: ColumnValueTooltip, })} {disabledPropertyView(children)} - {children.onClick.propertyView({ - label: trans("table.action"), - placement: "table", - })} + {children.onEvent.propertyView()} )) .setStylePropertyViewFn((children) => ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx index 4ecd308dd..b36f2acfc 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx @@ -10,6 +10,7 @@ import { trans } from "i18n"; import styled from "styled-components"; import { ColumnLink } from "comps/comps/tableComp/column/columnTypeComps/columnLinkComp"; import { LightActiveTextColor, PrimaryColor } from "constants/style"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; const MenuLinkWrapper = styled.div` > a { @@ -37,33 +38,16 @@ const MenuWrapper = styled.div` } `; -// Memoized menu item component -const MenuItem = React.memo(({ option, index }: { option: any; index: number }) => { - const handleClick = useCallback(() => { - if (!option.disabled && option.onClick) { - option.onClick(); - } - }, [option.disabled, option.onClick]); - - return ( - - - - ); -}); - -MenuItem.displayName = 'MenuItem'; +const LinksEventOptions = [clickEvent] as const; +// Update OptionItem to include event handlers const OptionItem = new MultiCompBuilder( { label: StringControl, onClick: ActionSelectorControlInContext, hidden: BoolCodeControl, disabled: BoolCodeControl, + onEvent: eventHandlerControl(LinksEventOptions), }, (props) => { return props; @@ -79,11 +63,38 @@ const OptionItem = new MultiCompBuilder( })} {hiddenPropertyView(children)} {disabledPropertyView(children)} + {children.onEvent.propertyView()} ); }) .build(); +// Memoized menu item component +const MenuItem = React.memo(({ option, index }: { option: any; index: number }) => { + const handleClick = useCallback(() => { + if (!option.disabled) { + if (option.onClick) { + option.onClick(); + } + if (option.onEvent) { + option.onEvent("click"); + } + } + }, [option.disabled, option.onClick, option.onEvent]); + + return ( + + + + ); +}); + +MenuItem.displayName = 'MenuItem'; + // Memoized menu component const LinksMenu = React.memo(({ options }: { options: any[] }) => { const mountedRef = useRef(true); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx index de76a4dd8..6162abea7 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx @@ -1,13 +1,17 @@ import React, { useState, useRef, useEffect, useCallback, useMemo } from "react"; import { SelectUIView } from "comps/comps/selectInputComp/selectCompConstants"; -import { SelectOptionControl } from "comps/controls/optionsControl"; -import { StringControl } from "comps/controls/codeControl"; +import { StringControl, BoolCodeControl } from "comps/controls/codeControl"; +import { IconControl } from "comps/controls/iconControl"; +import { MultiCompBuilder } from "comps/generators"; +import { optionsControl } from "comps/controls/optionsControl"; +import { disabledPropertyView, hiddenPropertyView } from "comps/utils/propertyUtils"; import { trans } from "i18n"; import { ColumnTypeCompBuilder, ColumnTypeViewFn } from "../columnTypeCompBuilder"; import { ColumnValueTooltip } from "../simpleColumnTypeComps"; import { styled } from "styled-components"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; const Wrapper = styled.div` display: inline-flex; @@ -75,9 +79,43 @@ const Wrapper = styled.div` } `; +const SelectOptionEventOptions = [clickEvent] as const; + +// Create a new option type with event handlers for each option +const SelectOptionWithEvents = new MultiCompBuilder( + { + value: StringControl, + label: StringControl, + prefixIcon: IconControl, + disabled: BoolCodeControl, + hidden: BoolCodeControl, + onEvent: eventHandlerControl(SelectOptionEventOptions), + }, + (props) => props +) + .setPropertyViewFn((children) => ( + <> + {children.label.propertyView({ label: trans("label") })} + {children.value.propertyView({ label: trans("value") })} + {children.prefixIcon.propertyView({ label: trans("button.prefixIcon") })} + {disabledPropertyView(children)} + {hiddenPropertyView(children)} + {children.onEvent.propertyView()} + + )) + .build(); + +const SelectOptionWithEventsControl = optionsControl(SelectOptionWithEvents, { + initOptions: [ + { label: trans("optionsControl.optionI", { i: 1 }), value: "1" }, + { label: trans("optionsControl.optionI", { i: 2 }), value: "2" }, + ], + uniqField: "value", +}); + const childrenMap = { text: StringControl, - options: SelectOptionControl, + options: SelectOptionWithEventsControl, }; const getBaseValue: ColumnTypeViewFn = (props) => props.text; @@ -106,7 +144,13 @@ const SelectEdit = React.memo((props: SelectEditProps) => { if (!mountedRef.current) return; props.onChange(val); setCurrentValue(val); - }, [props.onChange]); + + // Trigger the specific option's event handler + const selectedOption = props.options.find(option => option.value === val); + if (selectedOption && selectedOption.onEvent) { + selectedOption.onEvent("click"); + } + }, [props.onChange, props.options]); const handleEvent = useCallback(async (eventName: string) => { if (!mountedRef.current) return [] as unknown[]; diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index 3d5096cc8..ba264c5e4 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -13,6 +13,7 @@ import React, { useCallback, useEffect, useMemo } from "react"; import { CSSProperties } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); @@ -31,10 +32,12 @@ export const ButtonTypeOptions = [ }, ] as const; +const ButtonEventOptions = [clickEvent] as const; + const childrenMap = { text: StringControl, buttonType: dropdownControl(ButtonTypeOptions, "primary"), - onClick: ActionSelectorControlInContext, + onEvent: eventHandlerControl(ButtonEventOptions), loading: BoolCodeControl, disabled: BoolCodeControl, prefixIcon: IconControl, @@ -49,8 +52,8 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn { - props.onClick?.(); - }, [props.onClick]); + props.onEvent("click"); + }, [props.onEvent]); const buttonStyle = useMemo(() => ({ margin: 0, @@ -100,10 +103,7 @@ export const ButtonComp = (function () { })} {loadingPropertyView(children)} {disabledPropertyView(children)} - {children.onClick.propertyView({ - label: trans("table.action"), - placement: "table", - })} + {children.onEvent.propertyView()} )) .build(); From 70eddf10f2b1862412963bb870e5ba8f237efecc Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Wed, 4 Jun 2025 00:26:08 +0500 Subject: [PATCH 03/51] Added double click to the table event hanlders --- .../src/comps/comps/tableComp/selectionControl.tsx | 10 ++++++++++ .../lowcoder/src/comps/comps/tableComp/tableTypes.tsx | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/selectionControl.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/selectionControl.tsx index 60e292d0d..80e8e0340 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/selectionControl.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/selectionControl.tsx @@ -74,6 +74,12 @@ export const SelectionControl = (function () { onEvent("rowSelectChange"); } }, + onDoubleClick: () => { + onEvent("doubleClick"); + if (getKey(record) !== props.selectedRowKey) { + onEvent("rowSelectChange"); + } + } }; }, }; @@ -101,6 +107,10 @@ export const SelectionControl = (function () { changeSelectedRowKey(record); onEvent("rowClick"); }, + onDoubleClick: () => { + changeSelectedRowKey(record); + onEvent("doubleClick"); + } }; }, }; diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableTypes.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableTypes.tsx index 47db799b8..f40f18c73 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableTypes.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableTypes.tsx @@ -144,6 +144,11 @@ export const TableEventOptions = [ value: "refresh", description: trans("table.refresh"), }, + { + label: trans("event.doubleClick"), + value: "doubleClick", + description: trans("event.doubleClickDesc"), + } ] as const; export type TableEventOptionValues = typeof TableEventOptions[number]['value']; From 780ca306c15388a34b7b3253990e612ac36bfa27 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 4 Jun 2025 15:33:18 +0500 Subject: [PATCH 04/51] fixed input state change not updating temporary state value --- .../textInputComp/textInputConstants.tsx | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx b/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx index c2ab8801b..fc25e03e7 100644 --- a/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx +++ b/client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx @@ -11,7 +11,7 @@ import { stringExposingStateControl } from "comps/controls/codeStateControl"; import { LabelControl } from "comps/controls/labelControl"; import { InputLikeStyleType, LabelStyleType, heightCalculator, widthCalculator } from "comps/controls/styleControlConstants"; import { Section, sectionNames, ValueFromOption } from "lowcoder-design"; -import _, { debounce } from "lodash"; +import { fromPairs } from "lodash"; import { css } from "styled-components"; import { EMAIL_PATTERN, URL_PATTERN } from "util/stringUtils"; import { MultiBaseComp, RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; @@ -84,7 +84,7 @@ type ValidationParams = { customRule: string; }; -const valueInfoMap = _.fromPairs( +const valueInfoMap = fromPairs( TextInputValidationOptions.map((option) => [option.value, option]) ); @@ -216,26 +216,19 @@ export const useTextInputProps = (props: RecordConstructorToView { + const onChangeRef = useRef( + (value: string) => { props.value.onChange(value); - }, 1000) + } ); - // Cleanup debounced function on unmount - useEffect(() => { - return () => { - debouncedOnChangeRef.current.cancel(); - }; - }, []); - const handleChange = (e: ChangeEvent) => { const value = e.target.value; setLocalInputValue(value); changeRef.current = true; touchRef.current = true; - debouncedOnChangeRef.current?.(value); + onChangeRef.current?.(value); }; // Cleanup refs on unmount @@ -244,6 +237,7 @@ export const useTextInputProps = (props: RecordConstructorToView Date: Wed, 4 Jun 2025 23:20:33 +0500 Subject: [PATCH 05/51] [Fix]: Workspace switch submenu overflow for Mobile Screens --- client/packages/lowcoder/src/pages/common/profileDropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/packages/lowcoder/src/pages/common/profileDropdown.tsx b/client/packages/lowcoder/src/pages/common/profileDropdown.tsx index cf9e6d792..4f083cc18 100644 --- a/client/packages/lowcoder/src/pages/common/profileDropdown.tsx +++ b/client/packages/lowcoder/src/pages/common/profileDropdown.tsx @@ -224,7 +224,7 @@ export default function ProfileDropdown(props: DropDownProps) { const switchOrgMenu = { key: 'switchOrg', label: trans("profile.switchOrg"), - popupOffset: [4, -12], + popupOffset: checkIsMobile(window.innerWidth) ? [-200, 36] : [4, -12], children: [ { key: 'joinedOrg', From f23c3099da6d13ba2df63328f513d4a043615d19 Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Thu, 5 Jun 2025 01:46:17 +0500 Subject: [PATCH 06/51] Added hide toggle for columns in responsive layout --- .../src/comps/comps/responsiveLayout/responsiveLayout.tsx | 2 +- .../packages/lowcoder/src/comps/controls/optionsControl.tsx | 4 ++++ translations/locales/en.js | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/client/packages/lowcoder/src/comps/comps/responsiveLayout/responsiveLayout.tsx b/client/packages/lowcoder/src/comps/comps/responsiveLayout/responsiveLayout.tsx index 0ce837560..c9c8229ed 100644 --- a/client/packages/lowcoder/src/comps/comps/responsiveLayout/responsiveLayout.tsx +++ b/client/packages/lowcoder/src/comps/comps/responsiveLayout/responsiveLayout.tsx @@ -234,7 +234,7 @@ const ResponsiveLayout = (props: ResponsiveLayoutProps) => { {columns.map((column) => { const id = String(column.id); const childDispatch = wrapDispatch(wrapDispatch(dispatch, "containers"), id); - if (!containers[id]) return null; + if (!containers[id] || column.hidden) return null; const containerProps = containers[id].children; // Use the actual minWidth from column configuration instead of calculated width diff --git a/client/packages/lowcoder/src/comps/controls/optionsControl.tsx b/client/packages/lowcoder/src/comps/controls/optionsControl.tsx index e32c32c09..4ddb7463d 100644 --- a/client/packages/lowcoder/src/comps/controls/optionsControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/optionsControl.tsx @@ -588,6 +588,7 @@ const ColumnOption = new MultiCompBuilder( radius: withDefault(RadiusControl, ""), margin: withDefault(StringControl, ""), padding: withDefault(StringControl, ""), + hidden: withDefault(BoolControl, false), }, (props) => props ) @@ -624,6 +625,9 @@ const ColumnOption = new MultiCompBuilder( preInputNode: , placeholder: '3px', })} + {children.hidden.propertyView({ + label: trans('style.hideColumn'), + })} )) .build(); diff --git a/translations/locales/en.js b/translations/locales/en.js index 0628515d6..5b69d23f0 100644 --- a/translations/locales/en.js +++ b/translations/locales/en.js @@ -589,6 +589,7 @@ export const en = { "chartBorderColor": "Border Color", "chartTextColor": "Text Color", "detailSize": "Detail Size", + "hideColumn": "Hide Column", "radiusTip": "Specifies the radius of the element's corners. Example: 5px, 50%, or 1em.", "gapTip": "Specifies the gap between rows and columns in a grid or flex container. Example: 10px, 1rem, or 5%.", From a621bdf54bd40805843014bac06ce160cc2a0997 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 5 Jun 2025 16:30:04 +0500 Subject: [PATCH 07/51] clear column type render comp to fix editing issue --- .../tableComp/column/columnTypeComps/simpleTextComp.tsx | 7 ++++--- .../src/comps/comps/tableComp/column/tableColumnComp.tsx | 2 ++ .../lowcoder/src/comps/comps/tableComp/tableComp.tsx | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx index 36d1d7ce9..a34537987 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx @@ -55,9 +55,10 @@ const SimpleTextEditView = React.memo(({ value, onChange, onChangeEnd }: SimpleT variant="borderless" onChange={handleChange} onBlur={onChangeEnd} - onPressEnter={onChangeEnd} - /> -)}); + onPressEnter={onChangeEnd} + /> + ); +}); const SimpleTextPropertyView = React.memo(({ children }: { children: RecordConstructorToComp }) => { return useMemo(() => ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx index b3dbe77c9..7866cb813 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx @@ -434,6 +434,8 @@ export class ColumnComp extends ColumnInitComp { ) ) ); + // clear render comp cache when change set is cleared + this.children.render.dispatch(RenderComp.clearAction()); } dispatchClearInsertSet() { diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx index fee2da523..721f64565 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx @@ -548,7 +548,8 @@ let TableTmpComp = withViewFn(TableImplComp, (comp) => { const withEditorModeStatus = (Component:any) => (props:any) => { const editorModeStatus = useContext(EditorContext).editorModeStatus; - return ; + const {ref, ...otherProps} = props; + return ; }; // Use this HOC when defining TableTmpComp From 9187921addd3a39d437c7dee6146679cce80e2a8 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Thu, 5 Jun 2025 22:46:28 +0500 Subject: [PATCH 08/51] [Feat]: Add default, custom preset for tags and margins etc --- .../column/columnTypeComps/columnTagsComp.tsx | 98 ++++++++++++++++--- .../src/comps/controls/optionsControl.tsx | 70 ++++++++++++- 2 files changed, 154 insertions(+), 14 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx index 0f4f1e15f..b7092b67b 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx @@ -58,10 +58,58 @@ const TagsControl = codeControl | string>( function getTagColor(tagText : any, tagOptions: any[]) { const foundOption = tagOptions.find((option: { label: any; }) => option.label === tagText); - return foundOption ? foundOption.color : (function() { - const index = Math.abs(hashToNum(tagText)) % colors.length; - return colors[index]; - })(); + if (foundOption) { + if (foundOption.colorType === "preset") { + return foundOption.presetColor; + } else if (foundOption.colorType === "custom") { + return undefined; // For custom colors, we'll use style instead + } + // Backward compatibility - if no colorType specified, assume it's the old color field + return foundOption.color; + } + // Default fallback + const index = Math.abs(hashToNum(tagText)) % colors.length; + return colors[index]; +} + +function getTagStyle(tagText: any, tagOptions: any[]) { + const foundOption = tagOptions.find((option: { label: any; }) => option.label === tagText); + if (foundOption) { + const style: any = {}; + + // Handle color styling + if (foundOption.colorType === "custom") { + style.backgroundColor = foundOption.color; + style.color = foundOption.textColor; + style.border = `1px solid ${foundOption.color}`; + } + + // Add border styling if specified + if (foundOption.border) { + style.borderColor = foundOption.border; + if (!foundOption.colorType || foundOption.colorType !== "custom") { + style.border = `1px solid ${foundOption.border}`; + } + } + + // Add border radius if specified + if (foundOption.radius) { + style.borderRadius = foundOption.radius; + } + + // Add margin if specified + if (foundOption.margin) { + style.margin = foundOption.margin; + } + + // Add padding if specified + if (foundOption.padding) { + style.padding = foundOption.padding; + } + + return style; + } + return {}; } function getTagIcon(tagText: any, tagOptions: any[]) { @@ -286,13 +334,32 @@ const TagEdit = React.memo((props: TagEditPropsType) => { {tags.map((value, index) => ( {value.split(",")[1] ? ( - value.split(",").map((item, i) => ( - - {item} - - )) + value.split(",").map((item, i) => { + const tagColor = getTagColor(item, memoizedTagOptions); + const tagIcon = getTagIcon(item, memoizedTagOptions); + const tagStyle = getTagStyle(item, memoizedTagOptions); + + return ( + + {item} + + ); + }) ) : ( - + {value} )} @@ -316,9 +383,18 @@ export const ColumnTagsComp = (function () { const view = tags.map((tag, index) => { // The actual eval value is of type number or boolean const tagText = String(tag); + const tagColor = getTagColor(tagText, tagOptions); + const tagIcon = getTagIcon(tagText, tagOptions); + const tagStyle = getTagStyle(tagText, tagOptions); + return (
- + {tagText}
diff --git a/client/packages/lowcoder/src/comps/controls/optionsControl.tsx b/client/packages/lowcoder/src/comps/controls/optionsControl.tsx index e32c32c09..a0c58f121 100644 --- a/client/packages/lowcoder/src/comps/controls/optionsControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/optionsControl.tsx @@ -41,6 +41,26 @@ import { ColorControl } from "./colorControl"; import { StringStateControl } from "./codeStateControl"; import { reduceInContext } from "../utils/reduceContext"; +// Tag preset color options +const TAG_PRESET_COLORS = [ + { label: "Magenta", value: "magenta" }, + { label: "Red", value: "red" }, + { label: "Volcano", value: "volcano" }, + { label: "Orange", value: "orange" }, + { label: "Gold", value: "gold" }, + { label: "Lime", value: "lime" }, + { label: "Green", value: "green" }, + { label: "Cyan", value: "cyan" }, + { label: "Blue", value: "blue" }, + { label: "Geek Blue", value: "geekblue" }, + { label: "Purple", value: "purple" }, + { label: "Success", value: "success" }, + { label: "Processing", value: "processing" }, + { label: "Error", value: "error" }, + { label: "Warning", value: "warning" }, + { label: "Default", value: "default" }, +] as const; + const OptionTypes = [ { label: trans("prop.manual"), @@ -729,24 +749,68 @@ let ColoredTagOption = new MultiCompBuilder( { label: StringControl, icon: IconControl, - color: withDefault(ColorControl, ""), + colorType: withDefault(dropdownControl([ + { label: "Preset", value: "preset" }, + { label: "Custom", value: "custom" }, + ] as const, "preset"), "preset"), + presetColor: withDefault(dropdownControl(TAG_PRESET_COLORS, "blue"), "blue"), + color: withDefault(ColorControl, "#1890ff"), + textColor: withDefault(ColorControl, "#ffffff"), + border: withDefault(ColorControl, ""), + radius: withDefault(RadiusControl, ""), + margin: withDefault(StringControl, ""), + padding: withDefault(StringControl, ""), }, (props) => props ).build(); ColoredTagOption = class extends ColoredTagOption implements OptionCompProperty { propertyView(param: { autoMap?: boolean }) { + const colorType = this.children.colorType.getView(); return ( <> {this.children.label.propertyView({ label: trans("coloredTagOptionControl.tag") })} {this.children.icon.propertyView({ label: trans("coloredTagOptionControl.icon") })} - {this.children.color.propertyView({ label: trans("coloredTagOptionControl.color") })} + {this.children.colorType.propertyView({ + label: "Color Type", + radioButton: true + })} + {colorType === "preset" && this.children.presetColor.propertyView({ + label: "Preset Color" + })} + {colorType === "custom" && ( + <> + {this.children.color.propertyView({ label: trans("coloredTagOptionControl.color") })} + {this.children.textColor.propertyView({ label: "Text Color" })} + + )} + {this.children.border.propertyView({ + label: trans('style.border') + })} + {this.children.radius.propertyView({ + label: trans('style.borderRadius'), + preInputNode: , + placeholder: '3px', + })} + {this.children.margin.propertyView({ + label: trans('style.margin'), + preInputNode: , + placeholder: '3px', + })} + {this.children.padding.propertyView({ + label: trans('style.padding'), + preInputNode: , + placeholder: '3px', + })} ); } }; export const ColoredTagOptionControl = optionsControl(ColoredTagOption, { - initOptions: [{ label: "Tag1", icon: "/icon:solid/tag", color: "#f50" }, { label: "Tag2", icon: "/icon:solid/tag", color: "#2db7f5" }], + initOptions: [ + { label: "Tag1", icon: "/icon:solid/tag", colorType: "preset", presetColor: "blue" }, + { label: "Tag2", icon: "/icon:solid/tag", colorType: "preset", presetColor: "green" } + ], uniqField: "label", }); \ No newline at end of file From a00e63495a24415b50640cd9ff620af5369fe7a8 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 6 Jun 2025 01:23:11 +0500 Subject: [PATCH 09/51] fixed modal z-index after optimisations --- .../src/components/Modal/handler.tsx | 9 ++++----- .../lowcoder/src/comps/hooks/modalComp.tsx | 15 ++++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/client/packages/lowcoder-design/src/components/Modal/handler.tsx b/client/packages/lowcoder-design/src/components/Modal/handler.tsx index c51a6858f..2293236d6 100644 --- a/client/packages/lowcoder-design/src/components/Modal/handler.tsx +++ b/client/packages/lowcoder-design/src/components/Modal/handler.tsx @@ -1,5 +1,5 @@ import styled, { css } from "styled-components"; -import { memo, useMemo } from "react"; +import { RefObject } from "react"; type ResizeHandleAxis = "s" | "w" | "e" | "n" | "sw" | "nw" | "se" | "ne"; type ReactRef = { @@ -84,10 +84,9 @@ const ResizeHandle = styled.div<{ $axis: string }>` ${(props) => (["sw", "nw", "se", "ne"].indexOf(props.$axis) >= 0 ? CornerHandle : "")}; `; -// Memoize Handle component -const Handle = memo((axis: ResizeHandleAxis, ref: ReactRef) => { - return ; -}); +const Handle = (resizeHandle: ResizeHandleAxis, ref: RefObject) => { + return ; +}; Handle.displayName = 'Handle'; diff --git a/client/packages/lowcoder/src/comps/hooks/modalComp.tsx b/client/packages/lowcoder/src/comps/hooks/modalComp.tsx index 2977ad4b9..5c98ddb89 100644 --- a/client/packages/lowcoder/src/comps/hooks/modalComp.tsx +++ b/client/packages/lowcoder/src/comps/hooks/modalComp.tsx @@ -14,7 +14,7 @@ import { Layers } from "constants/Layers"; import { HintPlaceHolder, Modal, Section, sectionNames } from "lowcoder-design"; import { trans } from "i18n"; import { changeChildAction } from "lowcoder-core"; -import { CSSProperties, useCallback, useMemo, useRef } from "react"; +import { CSSProperties, useCallback, useEffect, useMemo, useRef } from "react"; import { ResizeHandle } from "react-resizable"; import styled, { css } from "styled-components"; import { useUserViewMode } from "util/hooks"; @@ -118,6 +118,12 @@ let TmpModalComp = (function () { const appID = useApplicationId(); const containerRef = useRef(null); + useEffect(() => { + return () => { + containerRef.current = null; + }; + }, []); + // Memoize body style const bodyStyle = useMemo(() => ({ padding: 0, @@ -171,11 +177,9 @@ let TmpModalComp = (function () { // Memoize container getter const getContainer = useCallback(() => { - if (!containerRef.current) { - containerRef.current = document.querySelector(`#${CanvasContainerID}`) || document.body; - } + containerRef.current = document.querySelector(`#${CanvasContainerID}`) || document.body; return containerRef.current; - }, []); + }, [CanvasContainerID]); // Memoize event handlers const handleCancel = useCallback((e: React.MouseEvent) => { @@ -228,6 +232,7 @@ let TmpModalComp = (function () { mask={props.showMask} className={clsx(`app-${appID}`, props.className)} data-testid={props.dataTestId as string} + destroyOnHidden > Date: Fri, 6 Jun 2025 01:26:34 +0500 Subject: [PATCH 10/51] Updated Boolean toggle with Boolean Code Control --- .../packages/lowcoder/src/comps/controls/optionsControl.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/packages/lowcoder/src/comps/controls/optionsControl.tsx b/client/packages/lowcoder/src/comps/controls/optionsControl.tsx index 4ddb7463d..9e927331d 100644 --- a/client/packages/lowcoder/src/comps/controls/optionsControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/optionsControl.tsx @@ -30,6 +30,7 @@ import { Option, WidthIcon, ImageCompIcon, + CloseEyeIcon, } from "lowcoder-design"; import styled from "styled-components"; import { lastValueIfEqual } from "util/objectUtils"; @@ -588,7 +589,7 @@ const ColumnOption = new MultiCompBuilder( radius: withDefault(RadiusControl, ""), margin: withDefault(StringControl, ""), padding: withDefault(StringControl, ""), - hidden: withDefault(BoolControl, false), + hidden: withDefault(BoolCodeControl, false), }, (props) => props ) @@ -627,6 +628,7 @@ const ColumnOption = new MultiCompBuilder( })} {children.hidden.propertyView({ label: trans('style.hideColumn'), + preInputNode: })} )) From 1006e08e76744958f8214b50288c8319de855334 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Fri, 6 Jun 2025 12:35:11 -0400 Subject: [PATCH 11/51] add endpoint "user/myorg" --- .../api/home/UserHomeApiServiceImpl.java | 21 ----------- .../api/usermanagement/UserController.java | 36 +++++++++++++++++++ .../api/usermanagement/UserEndpoints.java | 15 +++++++- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java index ee02ce0ab..d8520a9d5 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java @@ -165,28 +165,7 @@ public Mono getUserHomePageView(ApplicationType applicationTyp .zipWith(folderApiService.getElements(null, applicationType, null, null).collectList()) .map(tuple2 -> { Organization organization = tuple2.getT1(); - List list = tuple2.getT2(); - List applicationInfoViews = list.stream() - .map(o -> { - if (o instanceof ApplicationInfoView applicationInfoView) { - return applicationInfoView; - } - return null; - }) - .filter(Objects::nonNull) - .toList(); - List folderInfoViews = list.stream() - .map(o -> { - if (o instanceof FolderInfoView folderInfoView) { - return folderInfoView; - } - return null; - }) - .filter(Objects::nonNull) - .toList(); userHomepageVO.setOrganization(organization); - userHomepageVO.setHomeApplicationViews(applicationInfoViews); - userHomepageVO.setFolderInfoViews(folderInfoViews); return userHomepageVO; }); }); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java index 6cd8d99fd..3592f0a86 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java @@ -4,13 +4,17 @@ import org.apache.commons.lang3.StringUtils; import org.lowcoder.api.authentication.dto.OrganizationDomainCheckResult; import org.lowcoder.api.authentication.service.AuthenticationApiService; +import org.lowcoder.api.framework.view.PageResponseView; import org.lowcoder.api.framework.view.ResponseView; import org.lowcoder.api.home.SessionUserService; import org.lowcoder.api.home.UserHomeApiService; +import org.lowcoder.api.usermanagement.view.OrgView; import org.lowcoder.api.usermanagement.view.UpdateUserRequest; import org.lowcoder.api.usermanagement.view.UserProfileView; import org.lowcoder.domain.organization.model.MemberRole; +import org.lowcoder.domain.organization.model.OrgMember; import org.lowcoder.domain.organization.service.OrgMemberService; +import org.lowcoder.domain.organization.service.OrganizationService; import org.lowcoder.domain.user.constant.UserStatusType; import org.lowcoder.domain.user.model.User; import org.lowcoder.domain.user.model.UserDetail; @@ -23,6 +27,7 @@ import org.springframework.http.codec.multipart.Part; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import static org.lowcoder.sdk.exception.BizError.INVALID_USER_STATUS; @@ -41,6 +46,7 @@ public class UserController implements UserEndpoints private final CommonConfig commonConfig; private final AuthenticationApiService authenticationApiService; private final OrgMemberService orgMemberService; + private final OrganizationService organizationService; @Override public Mono> createUserAndAddToOrg(@PathVariable String orgId, CreateUserRequest request) { @@ -62,6 +68,36 @@ public Mono> getUserProfile(ServerWebExchange exchange) { .switchIfEmpty(Mono.just(ResponseView.success(view)))); } + @Override + public Mono> getUserOrgs(ServerWebExchange exchange, + @RequestParam(required = false) String orgName, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "10") Integer pageSize) { + return sessionUserService.getVisitor() + .flatMap(user -> { + // Get all active organizations for the user + Flux orgMemberFlux = orgMemberService.getAllActiveOrgs(user.getId()); + + // If orgName filter is provided, filter organizations by name + if (StringUtils.isNotBlank(orgName)) { + return orgMemberFlux + .flatMap(orgMember -> organizationService.getById(orgMember.getOrgId())) + .filter(org -> StringUtils.containsIgnoreCase(org.getName(), orgName)) + .map(OrgView::new) + .collectList() + .map(orgs -> PageResponseView.success(orgs, pageNum, pageSize, orgs.size())); + } + + // If no filter, return all organizations + return orgMemberFlux + .flatMap(orgMember -> organizationService.getById(orgMember.getOrgId())) + .map(OrgView::new) + .collectList() + .map(orgs -> PageResponseView.success(orgs, pageNum, pageSize, orgs.size())); + }) + .map(ResponseView::success); + } + @Override public Mono> newUserGuidanceShown() { return sessionUserService.getVisitorId() diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserEndpoints.java index 2de3af919..955bb70bf 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserEndpoints.java @@ -48,6 +48,20 @@ public interface UserEndpoints @GetMapping("/me") public Mono> getUserProfile(ServerWebExchange exchange); + @Operation( + tags = {TAG_USER_MANAGEMENT}, + operationId = "getUserOrgs", + summary = "Get User Organizations", + description = "Retrieve a paginated list of organizations for the current user, filtered by organization name if provided." + ) + @GetMapping("/myorg") + public Mono> getUserOrgs( + ServerWebExchange exchange, + @RequestParam(required = false) String orgName, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "10") Integer pageSize + ); + @Operation( tags = TAG_USER_MANAGEMENT, operationId = "newUserGuidanceShown", @@ -218,5 +232,4 @@ public record MarkUserStatusRequest(String type, Object value) { public record CreateUserRequest(String email, String password) { } - } From 7339ba1b1d8bb6ac5258e5f7f1e258c9368a9542 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 6 Jun 2025 22:11:35 +0500 Subject: [PATCH 12/51] added control in autoComplete comp to filter options by input value --- .../autoCompleteComp/autoCompleteComp.tsx | 283 ++++++++++-------- .../packages/lowcoder/src/i18n/locales/en.ts | 1 + 2 files changed, 159 insertions(+), 125 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx b/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx index a8211777d..f7e2eaa67 100644 --- a/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useEffect, useState } from "react"; +import { ReactNode, useEffect, useState, useCallback } from "react"; import { Input, Section, sectionNames } from "lowcoder-design"; import { BoolControl } from "comps/controls/boolControl"; import { styleControl } from "comps/controls/styleControl"; @@ -78,6 +78,7 @@ const childrenMap = { prefixIcon: IconControl, suffixIcon: IconControl, items: jsonControl(convertAutoCompleteData, autoCompleteDate), + filterOptionsByInput: BoolControl.DEFAULT_TRUE, ignoreCase: BoolControl.DEFAULT_TRUE, searchFirstPY: BoolControl.DEFAULT_TRUE, searchCompletePY: BoolControl, @@ -118,10 +119,11 @@ let AutoCompleteCompBase = (function () { autoCompleteType, autocompleteIconColor, componentSize, + filterOptionsByInput, } = props; - const getTextInputValidate = () => { + const getTextInputValidate = useCallback(() => { return { value: { value: props.value.value }, required: props.required, @@ -131,7 +133,15 @@ let AutoCompleteCompBase = (function () { regex: props.regex, customRule: props.customRule, }; - }; + }, [ + props.value.value, + props.required, + props.minLength, + props.maxLength, + props.validationType, + props.regex, + props.customRule, + ]); const [activationFlag, setActivationFlag] = useState(false); const [searchtext, setsearchtext] = useState(props.value.value); @@ -154,6 +164,113 @@ let AutoCompleteCompBase = (function () { props.customRule, ]); + const handleFilterOptions = useCallback((inputValue: string, option: any) => { + if (ignoreCase) { + if ( + option?.label && + option?.label + .toUpperCase() + .indexOf(inputValue.toUpperCase()) !== -1 + ) + return true; + } else { + if (option?.label && option?.label.indexOf(inputValue) !== -1) + return true; + } + if ( + chineseEnv && + searchFirstPY && + option?.label && + option.label + .spell("first") + .toString() + .toLowerCase() + .indexOf(inputValue.toLowerCase()) >= 0 + ) + return true; + if ( + chineseEnv && + searchCompletePY && + option?.label && + option.label + .spell() + .toString() + .toLowerCase() + .indexOf(inputValue.toLowerCase()) >= 0 + ) + return true; + if (!searchLabelOnly) { + if (ignoreCase) { + if ( + option?.value && + option?.value + .toUpperCase() + .indexOf(inputValue.toUpperCase()) !== -1 + ) + return true; + } else { + if ( + option?.value && + option?.value.indexOf(inputValue) !== -1 + ) + return true; + } + if ( + chineseEnv && + searchFirstPY && + option?.value && + option.value + .spell("first") + .toString() + .toLowerCase() + .indexOf(inputValue.toLowerCase()) >= 0 + ) + return true; + if ( + chineseEnv && + searchCompletePY && + option?.value && + option.value + .spell() + .toString() + .toLowerCase() + .indexOf(inputValue.toLowerCase()) >= 0 + ) + return true; + } + return false; + }, [filterOptionsByInput, ignoreCase, chineseEnv, searchFirstPY, searchCompletePY, searchLabelOnly]); + + const handleChange = useCallback((value: string) => { + props.valueInItems.onChange(false); + setvalidateState(textInputValidate(getTextInputValidate())); + setsearchtext(value); + props.value.onChange(value); + props.onEvent("change"); + }, [props.valueInItems, getTextInputValidate, props.value, props.onEvent]); + + const handleSelect = useCallback((data: string, option: any) => { + setsearchtext(option[valueOrLabel]); + props.valueInItems.onChange(true); + props.value.onChange(option[valueOrLabel]); + props.onEvent("submit"); + }, [valueOrLabel, props.valueInItems, props.value, props.onEvent]); + + const handleFocus = useCallback(() => { + setActivationFlag(true); + props.onEvent("focus"); + }, [props.onEvent]); + + const handleBlur = useCallback(() => { + props.onEvent("blur"); + }, [props.onEvent]); + + const popupRender = useCallback((originNode: ReactNode) => ( + + {originNode} + + ), [props.childrenInputFieldStyle]); + return props.label({ required: props.required, children: ( @@ -163,117 +280,24 @@ let AutoCompleteCompBase = (function () { value={searchtext} options={items} style={{ width: "100%" }} - onChange={(value: string, option) => { - props.valueInItems.onChange(false); - setvalidateState(textInputValidate(getTextInputValidate())); - setsearchtext(value); - props.value.onChange(value); - props.onEvent("change") - }} - onFocus={() => { - setActivationFlag(true) - props.onEvent("focus") - }} - onBlur={() => props.onEvent("blur")} - onSelect={(data: string, option) => { - setsearchtext(option[valueOrLabel]); - props.valueInItems.onChange(true); - props.value.onChange(option[valueOrLabel]); - props.onEvent("submit"); - }} - filterOption={(inputValue: string, option) => { - if (ignoreCase) { - if ( - option?.label && - option?.label - .toUpperCase() - .indexOf(inputValue.toUpperCase()) !== -1 - ) - return true; - } else { - if (option?.label && option?.label.indexOf(inputValue) !== -1) - return true; - } - if ( - chineseEnv && - searchFirstPY && - option?.label && - option.label - .spell("first") - .toString() - .toLowerCase() - .indexOf(inputValue.toLowerCase()) >= 0 - ) - return true; - if ( - chineseEnv && - searchCompletePY && - option?.label && - option.label - .spell() - .toString() - .toLowerCase() - .indexOf(inputValue.toLowerCase()) >= 0 - ) - return true; - if (!searchLabelOnly) { - if (ignoreCase) { - if ( - option?.value && - option?.value - .toUpperCase() - .indexOf(inputValue.toUpperCase()) !== -1 - ) - return true; - } else { - if ( - option?.value && - option?.value.indexOf(inputValue) !== -1 - ) - return true; - } - if ( - chineseEnv && - searchFirstPY && - option?.value && - option.value - .spell("first") - .toString() - .toLowerCase() - .indexOf(inputValue.toLowerCase()) >= 0 - ) - return true; - if ( - chineseEnv && - searchCompletePY && - option?.value && - option.value - .spell() - .toString() - .toLowerCase() - .indexOf(inputValue.toLowerCase()) >= 0 - ) - return true; - } - return false; - }} - popupRender={(originNode: ReactNode) => ( - - {originNode} - - )} + onChange={handleChange} + onFocus={handleFocus} + onBlur={handleBlur} + onSelect={handleSelect} + filterOption={!filterOptionsByInput ? false : handleFilterOptions} + popupRender={popupRender} > - + ), @@ -306,24 +330,33 @@ let AutoCompleteCompBase = (function () { tooltip: itemsDataTooltip, placeholder: '[]', })} - {getDayJSLocale() === 'zh-cn' && + {children.filterOptionsByInput.propertyView({ + label: trans('autoComplete.filterOptionsByInput'), + })} + {children.filterOptionsByInput.getView() && getDayJSLocale() === 'zh-cn' && ( children.searchFirstPY.propertyView({ label: trans('autoComplete.searchFirstPY'), - })} - {getDayJSLocale() === 'zh-cn' && + }) + )} + {children.filterOptionsByInput.getView() && getDayJSLocale() === 'zh-cn' && ( children.searchCompletePY.propertyView({ label: trans('autoComplete.searchCompletePY'), - })} - {children.searchLabelOnly.propertyView({ + }) + )} + {children.filterOptionsByInput.getView() && children.searchLabelOnly.propertyView({ label: trans('autoComplete.searchLabelOnly'), })} - {children.ignoreCase.propertyView({ - label: trans('autoComplete.ignoreCase'), - })} - {children.valueOrLabel.propertyView({ - label: trans('autoComplete.checkedValueFrom'), - radioButton: true, - })} + {children.filterOptionsByInput.getView() && ( + children.ignoreCase.propertyView({ + label: trans('autoComplete.ignoreCase'), + }) + )} + {children.filterOptionsByInput.getView() && ( + children.valueOrLabel.propertyView({ + label: trans('autoComplete.checkedValueFrom'), + radioButton: true, + }) + )} diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index a88b9debd..c379004bc 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -4475,6 +4475,7 @@ export const en = { "autoComplete": { "value": "Auto Complete Value", "checkedValueFrom": "Checked Value From", + "filterOptionsByInput": "Filter Options By Input", "ignoreCase": "Search Ignore Case", "searchLabelOnly": "Search Label Only", "searchFirstPY": "Search First Pinyin", From 3637da2bf918e5419d62071e78ae6f24fd474338 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 6 Jun 2025 22:25:32 +0500 Subject: [PATCH 13/51] show/hide summary row's column based on dynamic columns settings in table comp --- .../src/comps/comps/tableComp/tableCompView.tsx | 2 ++ .../src/comps/comps/tableComp/tableSummaryComp.tsx | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx index 7d5d26e13..fa53d2f50 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx @@ -1039,6 +1039,8 @@ export const TableCompView = React.memo((props: { summaryRows={parseInt(summaryRows)} columns={columns} summaryRowStyle={summaryRowStyle} + dynamicColumn={dynamicColumn} + dynamicColumnConfig={dynamicColumnConfig} /> ); } diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx index 58a7871aa..bd7a5d0ff 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx @@ -186,6 +186,8 @@ export const TableSummary = memo(function TableSummary(props: { columns: ColumnComp[]; summaryRowStyle: TableSummaryRowStyleType; istoolbarPositionBelow: boolean; + dynamicColumn: boolean; + dynamicColumnConfig: string[]; }) { const { columns, @@ -195,10 +197,18 @@ export const TableSummary = memo(function TableSummary(props: { expandableRows, multiSelectEnabled, istoolbarPositionBelow, + dynamicColumn, + dynamicColumnConfig, } = props; const visibleColumns = useMemo(() => { let cols = columns.filter(col => !col.getView().hide); + if (dynamicColumn) { + cols = cols.filter(col => { + const colView = col.getView(); + return dynamicColumnConfig.includes(colView.isCustom ? colView.title : colView.dataIndex) + }) + } if (expandableRows) { cols.unshift(new ColumnComp({})); } @@ -206,7 +216,7 @@ export const TableSummary = memo(function TableSummary(props: { cols.unshift(new ColumnComp({})); } return cols; - }, [columns, expandableRows, multiSelectEnabled]); + }, [columns, expandableRows, multiSelectEnabled, dynamicColumn, dynamicColumnConfig]); const renderSummaryCell = useCallback((column: ColumnComp, rowIndex: number, index: number) => { const summaryColumn = column.children.summaryColumns.getView()[rowIndex].getView(); From 9752906febb8936fb55c9980ed46dd9db3389d5c Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 6 Jun 2025 22:38:22 +0500 Subject: [PATCH 14/51] [Feat]: Add event handlers on more column types --- .../columnTypeComps/ColumnNumberComp.tsx | 23 ++++++++++- .../columnTypeComps/columnAvatarsComp.tsx | 21 ++++++++-- .../column/columnTypeComps/columnImgComp.tsx | 24 ++++++++++-- .../columnTypeComps/columnLinksComp.tsx | 18 ++++++--- .../columnTypeComps/columnMarkdownComp.tsx | 18 +++++++-- .../columnTypeComps/columnSelectComp.tsx | 11 +++++- .../column/columnTypeComps/columnTagsComp.tsx | 35 ++++++++++++++++- .../column/columnTypeComps/simpleTextComp.tsx | 39 +++++++++++++++---- .../src/comps/controls/optionsControl.tsx | 2 + 9 files changed, 163 insertions(+), 28 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx index f221b547d..78bba9380 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx @@ -9,6 +9,7 @@ import { withDefault } from "comps/generators"; import styled from "styled-components"; import { IconControl } from "comps/controls/iconControl"; import { hasIcon } from "comps/utils"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; const InputNumberWrapper = styled.div` .ant-input-number { @@ -25,6 +26,15 @@ const InputNumberWrapper = styled.div` } `; +const NumberViewWrapper = styled.div` + cursor: pointer; + display: flex; + align-items: center; + gap: 4px; +`; + +const NumberEventOptions = [clickEvent] as const; + const childrenMap = { text: NumberControl, step: withDefault(NumberControl, 1), @@ -34,6 +44,7 @@ const childrenMap = { prefixIcon: IconControl, suffixIcon: IconControl, suffix: StringControl, + onEvent: eventHandlerControl(NumberEventOptions), }; const getBaseValue: ColumnTypeViewFn = (props) => props.text; @@ -46,6 +57,7 @@ type NumberViewProps = { suffixIcon: ReactNode; float: boolean; precision: number; + onEvent?: (eventName: string) => void; }; type NumberEditProps = { @@ -66,8 +78,14 @@ const ColumnNumberView = React.memo((props: NumberViewProps) => { return result; }, [props.value, props.float, props.precision]); + const handleClick = useCallback(() => { + if (props.onEvent) { + props.onEvent("click"); + } + }, [props.onEvent]); + return ( - <> + {hasIcon(props.prefixIcon) && ( {props.prefixIcon} )} @@ -75,7 +93,7 @@ const ColumnNumberView = React.memo((props: NumberViewProps) => { {hasIcon(props.suffixIcon) && ( {props.suffixIcon} )} - + ); }); @@ -197,6 +215,7 @@ export const ColumnNumberComp = (function () { children.step.dispatchChangeValueAction(String(newValue)); } })} + {children.onEvent.propertyView()} ); }) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx index c34b6dfbb..a62704ff6 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx @@ -38,6 +38,8 @@ const Container = styled.div<{ $style: AvatarGroupStyleType | undefined, alignme cursor: pointer; `; +const AvatarEventOptions = [clickEvent, refreshEvent] as const; + const DropdownOption = new MultiCompBuilder( { src: StringControl, @@ -46,6 +48,7 @@ const DropdownOption = new MultiCompBuilder( color: ColorControl, backgroundColor: ColorControl, Tooltip: StringControl, + onEvent: eventHandlerControl(AvatarEventOptions), }, (props) => props ) @@ -63,6 +66,7 @@ const DropdownOption = new MultiCompBuilder( {children.color.propertyView({ label: trans("style.fill") })} {children.backgroundColor.propertyView({ label: trans("style.background") })} {children.Tooltip.propertyView({ label: trans("badge.tooltip") })} + {children.onEvent.propertyView()} ); }) @@ -83,14 +87,16 @@ const MemoizedAvatar = React.memo(({ style, autoColor, avatarSize, - onEvent + onEvent, + onItemEvent }: { item: any; index: number; style: any; autoColor: boolean; avatarSize: number; - onEvent: (event: string) => void; + onEvent: (event: string) => void; + onItemEvent?: (event: string) => void; }) => { const mountedRef = useRef(true); @@ -103,8 +109,15 @@ const MemoizedAvatar = React.memo(({ const handleClick = useCallback(() => { if (!mountedRef.current) return; + + // Trigger individual avatar event first + if (onItemEvent) { + onItemEvent("click"); + } + + // Then trigger main component event onEvent("click"); - }, [onEvent]); + }, [onEvent, onItemEvent]); return ( @@ -114,6 +127,7 @@ const MemoizedAvatar = React.memo(({ style={{ color: item.color ? item.color : (style.fill !== '#FFFFFF' ? style.fill : '#FFFFFF'), backgroundColor: item.backgroundColor ? item.backgroundColor : (autoColor ? MacaroneList[index % MacaroneList.length] : style.background), + cursor: 'pointer', }} size={avatarSize} onClick={handleClick} @@ -162,6 +176,7 @@ const MemoizedAvatarGroup = React.memo(({ autoColor={autoColor} avatarSize={avatarSize} onEvent={onEvent} + onItemEvent={item.onEvent} /> ))} diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnImgComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnImgComp.tsx index b062f8fc4..d3d204101 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnImgComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnImgComp.tsx @@ -10,20 +10,28 @@ import { withDefault } from "comps/generators"; import { TacoImage } from "lowcoder-design"; import styled from "styled-components"; import { DEFAULT_IMG_URL } from "@lowcoder-ee/util/stringUtils"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); const childrenMap = { src: withDefault(StringControl, "{{currentCell}}"), size: withDefault(NumberControl, "50"), + onEvent: eventHandlerControl([clickEvent]), }; const StyledTacoImage = styled(TacoImage)` - pointer-events: auto; + pointer-events: auto !important; + cursor: pointer !important; + + &:hover { + opacity: 0.8; + transition: opacity 0.2s ease; + } `; // Memoized image component -const ImageView = React.memo(({ src, size }: { src: string; size: number }) => { +const ImageView = React.memo(({ src, size, onEvent }: { src: string; size: number; onEvent?: (eventName: string) => void }) => { const mountedRef = useRef(true); // Cleanup on unmount @@ -33,10 +41,19 @@ const ImageView = React.memo(({ src, size }: { src: string; size: number }) => { }; }, []); + const handleClick = useCallback(() => { + console.log("Image clicked!", { src, onEvent: !!onEvent }); // Debug log + if (mountedRef.current && onEvent) { + onEvent("click"); + } + }, [onEvent, src]); + return ( ); }); @@ -96,7 +113,7 @@ export const ImageComp = (function () { childrenMap, (props, dispatch) => { const value = props.changeValue ?? getBaseValue(props, dispatch); - return ; + return ; }, (nodeValue) => nodeValue.src.value, getBaseValue @@ -118,6 +135,7 @@ export const ImageComp = (function () { {children.size.propertyView({ label: trans("table.imageSize"), })} + {children.onEvent.propertyView()} ); }) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx index b36f2acfc..3d35aa31d 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx @@ -70,7 +70,7 @@ const OptionItem = new MultiCompBuilder( .build(); // Memoized menu item component -const MenuItem = React.memo(({ option, index }: { option: any; index: number }) => { +const MenuItem = React.memo(({ option, index, onMainEvent }: { option: any; index: number; onMainEvent?: (eventName: string) => void }) => { const handleClick = useCallback(() => { if (!option.disabled) { if (option.onClick) { @@ -79,8 +79,12 @@ const MenuItem = React.memo(({ option, index }: { option: any; index: number }) if (option.onEvent) { option.onEvent("click"); } + // Trigger the main component's event handler + if (onMainEvent) { + onMainEvent("click"); + } } - }, [option.disabled, option.onClick, option.onEvent]); + }, [option.disabled, option.onClick, option.onEvent, onMainEvent]); return ( @@ -96,7 +100,7 @@ const MenuItem = React.memo(({ option, index }: { option: any; index: number }) MenuItem.displayName = 'MenuItem'; // Memoized menu component -const LinksMenu = React.memo(({ options }: { options: any[] }) => { +const LinksMenu = React.memo(({ options, onEvent }: { options: any[]; onEvent?: (eventName: string) => void }) => { const mountedRef = useRef(true); // Cleanup on unmount @@ -111,9 +115,9 @@ const LinksMenu = React.memo(({ options }: { options: any[] }) => { .filter((o) => !o.hidden) .map((option, index) => ({ key: index, - label: + label: })), - [options] + [options, onEvent] ); return ( @@ -130,11 +134,12 @@ export const ColumnLinksComp = (function () { options: manualOptionsControl(OptionItem, { initOptions: [{ label: trans("table.option1") }], }), + onEvent: eventHandlerControl(LinksEventOptions), }; return new ColumnTypeCompBuilder( childrenMap, (props) => { - return ; + return ; }, () => "" ) @@ -144,6 +149,7 @@ export const ColumnLinksComp = (function () { newOptionLabel: trans("table.option"), title: trans("table.optionList"), })} + {children.onEvent.propertyView()} )) .build(); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnMarkdownComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnMarkdownComp.tsx index e8fcd9a4b..17ad78efd 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnMarkdownComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnMarkdownComp.tsx @@ -9,10 +9,12 @@ import { StringControl } from "comps/controls/codeControl"; import { trans } from "i18n"; import { markdownCompCss, TacoMarkDown } from "lowcoder-design"; import styled from "styled-components"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; const Wrapper = styled.div` ${markdownCompCss}; max-height: 32px; + cursor: pointer; > .markdown-body { margin: 0; @@ -22,16 +24,25 @@ const Wrapper = styled.div` } `; +const MarkdownEventOptions = [clickEvent] as const; + const childrenMap = { text: StringControl, + onEvent: eventHandlerControl(MarkdownEventOptions), }; const getBaseValue: ColumnTypeViewFn = (props) => props.text; // Memoized markdown view component -const MarkdownView = React.memo(({ value }: { value: string }) => { +const MarkdownView = React.memo(({ value, onEvent }: { value: string; onEvent?: (eventName: string) => void }) => { + const handleClick = useCallback(() => { + if (onEvent) { + onEvent("click"); + } + }, [onEvent]); + return ( - + {value} ); @@ -92,7 +103,7 @@ export const ColumnMarkdownComp = (function () { childrenMap, (props, dispatch) => { const value = props.changeValue ?? getBaseValue(props, dispatch); - return ; + return ; }, (nodeValue) => nodeValue.text.value, getBaseValue @@ -110,6 +121,7 @@ export const ColumnMarkdownComp = (function () { label: trans("table.columnValue"), tooltip: ColumnValueTooltip, })} + {children.onEvent.propertyView()} )) .build(); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx index 6162abea7..ee15dda64 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx @@ -116,6 +116,7 @@ const SelectOptionWithEventsControl = optionsControl(SelectOptionWithEvents, { const childrenMap = { text: StringControl, options: SelectOptionWithEventsControl, + onEvent: eventHandlerControl(SelectOptionEventOptions), }; const getBaseValue: ColumnTypeViewFn = (props) => props.text; @@ -125,6 +126,7 @@ type SelectEditProps = { onChange: (value: string) => void; onChangeEnd: () => void; options: any[]; + onMainEvent?: (eventName: string) => void; }; const SelectEdit = React.memo((props: SelectEditProps) => { @@ -150,7 +152,12 @@ const SelectEdit = React.memo((props: SelectEditProps) => { if (selectedOption && selectedOption.onEvent) { selectedOption.onEvent("click"); } - }, [props.onChange, props.options]); + + // Also trigger the main component's event handler + if (props.onMainEvent) { + props.onMainEvent("click"); + } + }, [props.onChange, props.options, props.onMainEvent]); const handleEvent = useCallback(async (eventName: string) => { if (!mountedRef.current) return [] as unknown[]; @@ -203,6 +210,7 @@ export const ColumnSelectComp = (function () { options={props.otherProps?.options || []} onChange={props.onChange} onChangeEnd={props.onChangeEnd} + onMainEvent={props.otherProps?.onEvent} /> ) @@ -217,6 +225,7 @@ export const ColumnSelectComp = (function () { {children.options.propertyView({ title: trans("optionsControl.optionList"), })} + {children.onEvent.propertyView()} ); }) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx index b7092b67b..3bdbbed9d 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx @@ -16,6 +16,7 @@ import { hashToNum } from "util/stringUtils"; import { CustomSelect, PackUpIcon } from "lowcoder-design"; import { ScrollBar } from "lowcoder-design"; import { ColoredTagOptionControl } from "comps/controls/optionsControl"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; const colors = PresetStatusColorTypes; @@ -120,6 +121,7 @@ function getTagIcon(tagText: any, tagOptions: any[]) { const childrenMap = { text: TagsControl, tagColors: ColoredTagOptionControl, + onEvent: eventHandlerControl([clickEvent]), }; const getBaseValue: ColumnTypeViewFn = ( @@ -229,6 +231,7 @@ export const DropdownStyled = styled.div` export const TagStyled = styled(Tag)` margin-right: 8px; + cursor: pointer; svg { margin-right: 4px; } @@ -298,6 +301,14 @@ const TagEdit = React.memo((props: TagEditPropsType) => { setOpen(false); }, [props.onChangeEnd]); + const handleTagClick = useCallback((tagText: string, e: React.MouseEvent) => { + e.stopPropagation(); + const foundOption = memoizedTagOptions.find(option => option.label === tagText); + if (foundOption && foundOption.onEvent) { + foundOption.onEvent("click"); + } + }, [memoizedTagOptions]); + return ( { key={i} style={{ marginRight: tagStyle.margin ? undefined : "8px", + cursor: "pointer", ...tagStyle }} + onClick={(e) => handleTagClick(item, e)} > {item}
@@ -358,7 +371,11 @@ const TagEdit = React.memo((props: TagEditPropsType) => { color={getTagColor(value, memoizedTagOptions)} icon={getTagIcon(value, memoizedTagOptions)} key={index} - style={getTagStyle(value, memoizedTagOptions)} + style={{ + cursor: "pointer", + ...getTagStyle(value, memoizedTagOptions) + }} + onClick={(e) => handleTagClick(value, e)} > {value} @@ -380,6 +397,18 @@ export const ColumnTagsComp = (function () { let value = props.changeValue ?? getBaseValue(props, dispatch); value = typeof value === "string" && value.split(",")[1] ? value.split(",") : value; const tags = _.isArray(value) ? value : (value.length ? [value] : []); + + const handleTagClick = (tagText: string) => { + const foundOption = tagOptions.find(option => option.label === tagText); + if (foundOption && foundOption.onEvent) { + foundOption.onEvent("click"); + } + // Also trigger the main component's event handler + if (props.onEvent) { + props.onEvent("click"); + } + }; + const view = tags.map((tag, index) => { // The actual eval value is of type number or boolean const tagText = String(tag); @@ -394,6 +423,7 @@ export const ColumnTagsComp = (function () { icon={tagIcon} key={index} style={tagStyle} + onClick={() => handleTagClick(tagText)} > {tagText} @@ -425,8 +455,9 @@ export const ColumnTagsComp = (function () { tooltip: ColumnValueTooltip, })} {children.tagColors.propertyView({ - title: "test", + title: "Tag Options", })} + {children.onEvent.propertyView()} )) .build(); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx index a34537987..aba505252 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx @@ -7,11 +7,23 @@ import { IconControl } from "comps/controls/iconControl"; import { hasIcon } from "comps/utils"; import React, { useCallback, useMemo } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import styled from "styled-components"; + +const TextEventOptions = [clickEvent] as const; + +const TextWrapper = styled.div` + cursor: pointer; + display: flex; + align-items: center; + gap: 4px; +`; const childrenMap = { text: StringOrNumberControl, prefixIcon: IconControl, suffixIcon: IconControl, + onEvent: eventHandlerControl(TextEventOptions), }; // Memoize the base value function to prevent unnecessary string creation @@ -27,6 +39,7 @@ interface SimpleTextContentProps { value: string | number; prefixIcon?: React.ReactNode; suffixIcon?: React.ReactNode; + onEvent?: (eventName: string) => void; } interface SimpleTextEditViewProps { @@ -35,13 +48,21 @@ interface SimpleTextEditViewProps { onChangeEnd: () => void; } -const SimpleTextContent = React.memo(({ value, prefixIcon, suffixIcon }: SimpleTextContentProps) => ( - <> - {hasIcon(prefixIcon) && } - {value} - {hasIcon(suffixIcon) && } - -)); +const SimpleTextContent = React.memo(({ value, prefixIcon, suffixIcon, onEvent }: SimpleTextContentProps) => { + const handleClick = useCallback(() => { + if (onEvent) { + onEvent("click"); + } + }, [onEvent]); + + return ( + + {hasIcon(prefixIcon) && } + {value} + {hasIcon(suffixIcon) && } + + ); +}); const SimpleTextEditView = React.memo(({ value, onChange, onChangeEnd }: SimpleTextEditViewProps) => { const handleChange = useCallback((e: React.ChangeEvent) => { @@ -73,8 +94,9 @@ const SimpleTextPropertyView = React.memo(({ children }: { children: RecordConst {children.suffixIcon.propertyView({ label: trans("button.suffixIcon"), })} + {children.onEvent.propertyView()} - ), [children.text, children.prefixIcon, children.suffixIcon]); + ), [children.text, children.prefixIcon, children.suffixIcon, children.onEvent]); }); export const SimpleTextComp = new ColumnTypeCompBuilder( @@ -86,6 +108,7 @@ export const SimpleTextComp = new ColumnTypeCompBuilder( value={value} prefixIcon={props.prefixIcon} suffixIcon={props.suffixIcon} + onEvent={props.onEvent} /> ); }, diff --git a/client/packages/lowcoder/src/comps/controls/optionsControl.tsx b/client/packages/lowcoder/src/comps/controls/optionsControl.tsx index a0c58f121..0105c7685 100644 --- a/client/packages/lowcoder/src/comps/controls/optionsControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/optionsControl.tsx @@ -760,6 +760,7 @@ let ColoredTagOption = new MultiCompBuilder( radius: withDefault(RadiusControl, ""), margin: withDefault(StringControl, ""), padding: withDefault(StringControl, ""), + onEvent: ButtonEventHandlerControl, }, (props) => props ).build(); @@ -802,6 +803,7 @@ ColoredTagOption = class extends ColoredTagOption implements OptionCompProperty preInputNode: , placeholder: '3px', })} + {this.children.onEvent.propertyView()} ); } From e9ec6deb2f3f304a2e8c8881c4fe9845d6e1745f Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Sat, 7 Jun 2025 03:44:40 +0500 Subject: [PATCH 15/51] Added double click to almost all components --- client/packages/lowcoder/src/comps/comps/avatar.tsx | 4 +++- .../lowcoder/src/comps/comps/avatarGroup.tsx | 7 +++++-- .../src/comps/comps/commentComp/commentComp.tsx | 7 ++++++- .../packages/lowcoder/src/comps/comps/iconComp.tsx | 4 +++- .../packages/lowcoder/src/comps/comps/imageComp.tsx | 4 +++- .../column/columnTypeComps/columnAvatarsComp.tsx | 10 ++++++++-- .../column/columnTypeComps/columnDropdownComp.tsx | 10 ++++++++-- .../column/columnTypeComps/columnLinkComp.tsx | 11 +++++++++-- .../column/columnTypeComps/columnLinksComp.tsx | 10 +++++++--- .../column/columnTypeComps/columnSelectComp.tsx | 13 ++++++++----- .../tableComp/column/simpleColumnTypeComps.tsx | 9 +++++++-- .../packages/lowcoder/src/comps/comps/textComp.tsx | 9 ++++++--- .../src/comps/comps/timelineComp/timelineComp.tsx | 8 ++++++++ .../src/comps/controls/eventHandlerControl.tsx | 3 ++- 14 files changed, 83 insertions(+), 26 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/avatar.tsx b/client/packages/lowcoder/src/comps/comps/avatar.tsx index bbd39f73e..f07de98ca 100644 --- a/client/packages/lowcoder/src/comps/comps/avatar.tsx +++ b/client/packages/lowcoder/src/comps/comps/avatar.tsx @@ -25,6 +25,7 @@ import { IconControl } from "comps/controls/iconControl"; import { clickEvent, eventHandlerControl, + doubleClickEvent, } from "../controls/eventHandlerControl"; import { Avatar, AvatarProps, Badge, Dropdown, Menu } from "antd"; import { LeftRightControl, dropdownControl } from "../controls/dropdownControl"; @@ -106,7 +107,7 @@ padding: ${props=>props.$style.padding}; background: ${props=>props.$style.background}; text-decoration: ${props => props.$style.textDecoration}; ` -const EventOptions = [clickEvent] as const; +const EventOptions = [clickEvent, doubleClickEvent] as const; const sharpOptions = [ { label: trans("avatarComp.square"), value: "square" }, { label: trans("avatarComp.circle"), value: "circle" }, @@ -183,6 +184,7 @@ const AvatarView = (props: RecordConstructorToView) => { src={src.value} // $cursorPointer={eventsCount > 0} onClick={() => props.onEvent("click")} + onDoubleClick={() => props.onEvent("doubleClick")} > {title.value} diff --git a/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx b/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx index 4cc2567c6..8f35bd4f4 100644 --- a/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx +++ b/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx @@ -8,7 +8,7 @@ import { hiddenPropertyView } from "comps/utils/propertyUtils"; import { trans } from "i18n"; import { NumberControl, StringControl } from "comps/controls/codeControl"; import { Avatar, Tooltip } from "antd"; -import { clickEvent, eventHandlerControl, refreshEvent } from "../controls/eventHandlerControl"; +import { clickEvent, doubleClickEvent, eventHandlerControl, refreshEvent } from "../controls/eventHandlerControl"; import styled from "styled-components"; import { useContext, ReactElement, useEffect } from "react"; import { MultiCompBuilder, stateComp, withDefault } from "../generators"; @@ -77,7 +77,7 @@ const DropdownOption = new MultiCompBuilder( )) .build(); -const EventOptions = [clickEvent, refreshEvent] as const; +const EventOptions = [clickEvent, refreshEvent, doubleClickEvent] as const; export const alignOptions = [ { label: , value: "flex-start" }, @@ -128,6 +128,9 @@ const AvatarGroupView = (props: RecordConstructorToView & { props.onEvent("click") props.dispatch(changeChildAction("currentAvatar", item as JSONObject, false)); }} + onDoubleClick={() => { + props.onEvent("doubleClick") + }} > {item.label} diff --git a/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx b/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx index 4fb21b69f..d79fa542d 100644 --- a/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx @@ -25,6 +25,7 @@ import { eventHandlerControl, deleteEvent, mentionEvent, + doubleClickEvent, } from "comps/controls/eventHandlerControl"; import { EditorContext } from "comps/editorState"; @@ -80,6 +81,7 @@ dayjs.extend(relativeTime); const EventOptions = [ clickEvent, + doubleClickEvent, submitEvent, deleteEvent, mentionEvent, @@ -290,7 +292,10 @@ const CommentCompBase = ( props.onEvent("click")}> +
props.onEvent("click")} + onDoubleClick={() => props.onEvent("doubleClick")} + > {item?.user?.name} ) => { $animationStyle={props.animationStyle} style={style} onClick={() => props.onEvent("click")} + onDoubleClick={() => props.onEvent("doubleClick")} > { props.sourceMode === 'standard' ? (props.icon || '') diff --git a/client/packages/lowcoder/src/comps/comps/imageComp.tsx b/client/packages/lowcoder/src/comps/comps/imageComp.tsx index ec4190bc6..b0bd4d3dd 100644 --- a/client/packages/lowcoder/src/comps/comps/imageComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/imageComp.tsx @@ -3,6 +3,7 @@ import { Section, sectionNames } from "lowcoder-design"; import { clickEvent, eventHandlerControl, + doubleClickEvent, } from "../controls/eventHandlerControl"; import { StringStateControl } from "../controls/codeStateControl"; import { UICompBuilder, withDefault } from "../generators"; @@ -112,7 +113,7 @@ const getStyle = (style: ImageStyleType) => { `; }; -const EventOptions = [clickEvent] as const; +const EventOptions = [clickEvent, doubleClickEvent] as const; const ModeOptions = [ { label: "URL", value: "standard" }, { label: "Asset Library", value: "asset-library" }, @@ -212,6 +213,7 @@ const ContainerImg = (props: RecordConstructorToView) => { preview={props.supportPreview ? {src: props.previewSrc || props.src.value } : false} fallback={DEFAULT_IMG_URL} onClick={() => props.onEvent("click")} + onDoubleClick={() => props.onEvent("doubleClick")} />
diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx index c34b6dfbb..11141281f 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx @@ -9,7 +9,7 @@ import { avatarGroupStyle, AvatarGroupStyleType } from "comps/controls/styleCont import { AlignCenter, AlignLeft, AlignRight } from "lowcoder-design"; import { NumberControl } from "comps/controls/codeControl"; import { Avatar, Tooltip } from "antd"; -import { clickEvent, eventHandlerControl, refreshEvent } from "comps/controls/eventHandlerControl"; +import { clickEvent, eventHandlerControl, refreshEvent, doubleClickEvent } from "comps/controls/eventHandlerControl"; import React, { ReactElement, useCallback, useEffect, useRef } from "react"; import { IconControl } from "comps/controls/iconControl"; import { ColorControl } from "comps/controls/colorControl"; @@ -68,7 +68,7 @@ const DropdownOption = new MultiCompBuilder( }) .build(); -const EventOptions = [clickEvent, refreshEvent] as const; +const EventOptions = [clickEvent, refreshEvent, doubleClickEvent] as const; export const alignOptions = [ { label: , value: "flex-start" }, @@ -106,6 +106,11 @@ const MemoizedAvatar = React.memo(({ onEvent("click"); }, [onEvent]); + const handleDoubleClick = useCallback(() => { + if (!mountedRef.current) return; + onEvent("doubleClick"); + }, [onEvent]); + return ( {item.label} diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx index 9055413de..d7efb3aff 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx @@ -15,7 +15,7 @@ import { ButtonStyle } from "comps/controls/styleControlConstants"; import { Button100 } from "comps/comps/buttonComp/buttonCompConstants"; import styled from "styled-components"; import { ButtonType } from "antd/es/button"; -import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; const StyledButton = styled(Button100)` display: flex; @@ -29,7 +29,7 @@ const StyledIconWrapper = styled(IconWrapper)` margin: 0; `; -const DropdownEventOptions = [clickEvent] as const; +const DropdownEventOptions = [clickEvent, doubleClickEvent] as const; const childrenMap = { buttonType: dropdownControl(ButtonTypeOptions, "primary"), @@ -67,10 +67,16 @@ const DropdownMenu = React.memo(({ items, options, onEvent }: { items: any[]; op e.preventDefault(); }, []); + const handleDoubleClick = useCallback((e: React.MouseEvent) => { + if (!mountedRef.current) return; + onEvent?.("doubleClick"); + }, [onEvent]); + return ( ); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx index 512329ee3..183d87889 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx @@ -11,11 +11,11 @@ import { disabledPropertyView } from "comps/utils/propertyUtils"; import styled, { css } from "styled-components"; import { styleControl } from "comps/controls/styleControl"; import { TableColumnLinkStyle } from "comps/controls/styleControlConstants"; -import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); -const LinkEventOptions = [clickEvent] as const; +const LinkEventOptions = [clickEvent, doubleClickEvent] as const; const childrenMap = { text: StringControl, @@ -44,10 +44,17 @@ export const ColumnLink = React.memo(({ disabled, label, onEvent }: { disabled: } }, [disabled, onEvent]); + const handleDoubleClick = useCallback(() => { + if (!disabled && onEvent) { + onEvent("doubleClick"); + } + }, [disabled, onEvent]); + return ( {label} diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx index b36f2acfc..89ed76672 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx @@ -10,7 +10,7 @@ import { trans } from "i18n"; import styled from "styled-components"; import { ColumnLink } from "comps/comps/tableComp/column/columnTypeComps/columnLinkComp"; import { LightActiveTextColor, PrimaryColor } from "constants/style"; -import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; const MenuLinkWrapper = styled.div` > a { @@ -38,7 +38,7 @@ const MenuWrapper = styled.div` } `; -const LinksEventOptions = [clickEvent] as const; +const LinksEventOptions = [clickEvent, doubleClickEvent] as const; // Update OptionItem to include event handlers const OptionItem = new MultiCompBuilder( @@ -76,11 +76,15 @@ const MenuItem = React.memo(({ option, index }: { option: any; index: number }) if (option.onClick) { option.onClick(); } + if (option.onDoubleClick) { + option.onDoubleClick(); + } if (option.onEvent) { option.onEvent("click"); } + } - }, [option.disabled, option.onClick, option.onEvent]); + }, [option.disabled, option.onClick, option.onEvent, option.onDoubleClick]); return ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx index 6162abea7..a751b033a 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx @@ -11,7 +11,7 @@ import { trans } from "i18n"; import { ColumnTypeCompBuilder, ColumnTypeViewFn } from "../columnTypeCompBuilder"; import { ColumnValueTooltip } from "../simpleColumnTypeComps"; import { styled } from "styled-components"; -import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; const Wrapper = styled.div` display: inline-flex; @@ -79,7 +79,7 @@ const Wrapper = styled.div` } `; -const SelectOptionEventOptions = [clickEvent] as const; +const SelectOptionEventOptions = [clickEvent, doubleClickEvent] as const; // Create a new option type with event handlers for each option const SelectOptionWithEvents = new MultiCompBuilder( @@ -144,11 +144,14 @@ const SelectEdit = React.memo((props: SelectEditProps) => { if (!mountedRef.current) return; props.onChange(val); setCurrentValue(val); - // Trigger the specific option's event handler const selectedOption = props.options.find(option => option.value === val); - if (selectedOption && selectedOption.onEvent) { - selectedOption.onEvent("click"); + if (selectedOption?.onEvent) { + if (selectedOption.onEvent.isBind("click")) { + selectedOption.onEvent("click"); + } else if (selectedOption.onEvent.isBind("doubleClick")) { + selectedOption.onEvent("doubleClick"); + } } }, [props.onChange, props.options]); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index ba264c5e4..0df62e2f0 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -13,7 +13,7 @@ import React, { useCallback, useEffect, useMemo } from "react"; import { CSSProperties } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; -import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); @@ -32,7 +32,7 @@ export const ButtonTypeOptions = [ }, ] as const; -const ButtonEventOptions = [clickEvent] as const; +const ButtonEventOptions = [clickEvent, doubleClickEvent] as const; const childrenMap = { text: StringControl, @@ -55,6 +55,10 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn { + props.onEvent("doubleClick"); + }, [props.onEvent]); + const buttonStyle = useMemo(() => ({ margin: 0, width: iconOnly ? 'auto' : undefined, @@ -71,6 +75,7 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn {/* prevent the button from disappearing */} {hasText ? props.text : (iconOnly ? null : " ")} diff --git a/client/packages/lowcoder/src/comps/comps/textComp.tsx b/client/packages/lowcoder/src/comps/comps/textComp.tsx index 93b3d79ae..73f776ac3 100644 --- a/client/packages/lowcoder/src/comps/comps/textComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/textComp.tsx @@ -20,13 +20,13 @@ import { PaddingControl } from "../controls/paddingControl"; import React, { useContext, useEffect, useRef, useMemo } from "react"; import { EditorContext } from "comps/editorState"; -import { clickEvent, eventHandlerControl } from "../controls/eventHandlerControl"; +import { clickEvent, doubleClickEvent, eventHandlerControl } from "../controls/eventHandlerControl"; import { NewChildren } from "../generators/uiCompBuilder"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "../generators/multi"; import { BoolControl } from "../controls/boolControl"; -const EventOptions = [clickEvent] as const; +const EventOptions = [clickEvent, doubleClickEvent] as const; const getStyle = (style: TextStyleType) => { return css` @@ -227,7 +227,9 @@ const TextView = React.memo((props: ToViewReturn) => { const handleClick = React.useCallback(() => { props.onEvent("click"); }, [props.onEvent]); - + const handleDoubleClick = React.useCallback(() => { + props.onEvent("doubleClick"); + }, [props.onEvent]); const containerStyle = useMemo(() => ({ justifyContent: props.horizontalAlignment, alignItems: props.autoHeight ? "center" : props.verticalAlignment, @@ -247,6 +249,7 @@ const TextView = React.memo((props: ToViewReturn) => { $styleConfig={props.style} style={containerStyle} onClick={handleClick} + onDoubleClick={handleDoubleClick} > {content} diff --git a/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx b/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx index db45ba023..e7743f0f8 100644 --- a/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx @@ -30,6 +30,7 @@ import { import { clickEvent, eventHandlerControl, + doubleClickEvent, } from "comps/controls/eventHandlerControl"; import { TimeLineStyle, @@ -69,6 +70,7 @@ const TimelineWrapper = styled.div<{ const EventOptions = [ clickEvent, + doubleClickEvent, ] as const; const modeOptions = [ @@ -142,6 +144,12 @@ const TimelineComp = ( dispatch(changeChildAction("clickedIndex", index, false)); onEvent("click"); }} + onDoubleClick={(e) => { + e.preventDefault(); + dispatch(changeChildAction("clickedObject", value, false)); + dispatch(changeChildAction("clickedIndex", index, false)); + onEvent("doubleClick"); + }} // for responsiveness style={{ cursor: "pointer", diff --git a/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx b/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx index d8c26d7ad..289a212ea 100644 --- a/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx @@ -378,7 +378,7 @@ export const doubleClickEvent: EventConfigType = { }; export const rightClickEvent: EventConfigType = { label: trans("event.rightClick"), - value: "doubleClick", + value: "rightClick", description: trans("event.rightClickDesc"), }; @@ -704,6 +704,7 @@ export const InputEventHandlerControl = eventHandlerControl([ export const ButtonEventHandlerControl = eventHandlerControl([ clickEvent, + doubleClickEvent, ] as const); export const ChangeEventHandlerControl = eventHandlerControl([ From 8f2b188963e74b6051590cb5e9fd8bc7e108632e Mon Sep 17 00:00:00 2001 From: Thomasr Date: Wed, 11 Jun 2025 02:42:31 -0400 Subject: [PATCH 16/51] Optimized Plugin Loading for Improved Performance - Implemented parallel plugin loading using parallelStream() in loadPlugins to reduce overall loading time. - Ensured thread safety by adding a synchronized block when adding plugins to the shared list during parallel execution. - Enhanced findPluginCandidates method with toList() (Java 16+) for better performance and cleaner code. - Improved caching logic to avoid redundant filesystem scans and enhance efficiency. - Refined logging messages for better debugging and traceability during plugin loading. - Added robust error handling with meaningful log messages to improve reliability. --- .../plugin/PathBasedPluginLoader.java | 131 ++++++++---------- 1 file changed, 57 insertions(+), 74 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/PathBasedPluginLoader.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/PathBasedPluginLoader.java index 11f5bd953..7c13cdc57 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/PathBasedPluginLoader.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/PathBasedPluginLoader.java @@ -12,128 +12,111 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.ServiceLoader; +import java.util.*; @Slf4j @RequiredArgsConstructor @Component -public class PathBasedPluginLoader implements PluginLoader -{ +public class PathBasedPluginLoader implements PluginLoader { private final CommonConfig common; private final ApplicationHome applicationHome; - + + // Cache for plugin JAR paths to avoid redundant filesystem scans + private static final Map> cachedPluginJars = new HashMap<>(); + @Override - public List loadPlugins() - { + public List loadPlugins() { List plugins = new ArrayList<>(); - + + // Find plugin JARs using caching List pluginJars = findPluginsJars(); - if (pluginJars.isEmpty()) - { + if (pluginJars.isEmpty()) { + log.debug("No plugin JARs found."); return plugins; } - for (String pluginJar : pluginJars) - { + // Load plugins from JARs + pluginJars.parallelStream().forEach(pluginJar -> { log.debug("Inspecting plugin jar candidate: {}", pluginJar); List loadedPlugins = loadPluginCandidates(pluginJar); - if (loadedPlugins.isEmpty()) - { + if (loadedPlugins.isEmpty()) { log.debug(" - no plugins found in the jar file"); + } else { + synchronized (plugins) { + plugins.addAll(loadedPlugins); + } } - else - { - for (LowcoderPlugin plugin : loadedPlugins) - { - plugins.add(plugin); - } - } - } - + }); + return plugins; } - - protected List findPluginsJars() - { + + protected List findPluginsJars() { + String cacheKey = common.getPluginDirs().toString(); + + // Use cached JAR paths if available + if (cachedPluginJars.containsKey(cacheKey)) { + log.debug("Using cached plugin jar candidates for key: {}", cacheKey); + return cachedPluginJars.get(cacheKey); + } + List candidates = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(common.getPluginDirs())) - { - for (String pluginDir : common.getPluginDirs()) - { + if (CollectionUtils.isNotEmpty(common.getPluginDirs())) { + for (String pluginDir : common.getPluginDirs()) { final Path pluginPath = getAbsoluteNormalizedPath(pluginDir); - if (pluginPath != null) - { + if (pluginPath != null) { candidates.addAll(findPluginCandidates(pluginPath)); } } } - + + // Cache the results + cachedPluginJars.put(cacheKey, candidates); return candidates; } - - protected List findPluginCandidates(Path pluginsDir) - { - List pluginCandidates = new ArrayList<>(); - try - { - Files.walk(pluginsDir) - .filter(Files::isRegularFile) - .filter(path -> StringUtils.endsWithIgnoreCase(path.toAbsolutePath().toString(), ".jar")) - .forEach(path -> pluginCandidates.add(path.toString())); - } - catch(IOException cause) - { + protected List findPluginCandidates(Path pluginsDir) { + try { + return Files.walk(pluginsDir) + .filter(Files::isRegularFile) + .filter(path -> StringUtils.endsWithIgnoreCase(path.toAbsolutePath().toString(), ".jar")) + .map(Path::toString) + .toList(); // Use Java 16+ `toList()` for better performance + } catch (IOException cause) { log.error("Error walking plugin folder! - {}", cause.getMessage()); + return Collections.emptyList(); } - - return pluginCandidates; } - - protected List loadPluginCandidates(String pluginJar) - { + + protected List loadPluginCandidates(String pluginJar) { List pluginCandidates = new ArrayList<>(); - try - { + try { Path pluginPath = Path.of(pluginJar); PluginClassLoader pluginClassLoader = new PluginClassLoader(pluginPath.getFileName().toString(), pluginPath); ServiceLoader pluginServices = ServiceLoader.load(LowcoderPlugin.class, pluginClassLoader); - if (pluginServices != null ) - { - Iterator pluginIterator = pluginServices.iterator(); - while(pluginIterator.hasNext()) - { - LowcoderPlugin plugin = pluginIterator.next(); + if (pluginServices != null) { + for (LowcoderPlugin plugin : pluginServices) { log.debug(" - loaded plugin: {} - {}", plugin.pluginId(), plugin.description()); pluginCandidates.add(plugin); } } - } - catch(Throwable cause) - { + } catch (Throwable cause) { log.warn("Error loading plugin!", cause); } - + return pluginCandidates; } - - private Path getAbsoluteNormalizedPath(String path) - { - if (StringUtils.isNotBlank(path)) - { + + private Path getAbsoluteNormalizedPath(String path) { + if (StringUtils.isNotBlank(path)) { Path absPath = Path.of(path); - if (!absPath.isAbsolute()) - { + if (!absPath.isAbsolute()) { absPath = Path.of(applicationHome.getDir().getAbsolutePath(), absPath.toString()); } return absPath.normalize().toAbsolutePath(); } - return null; } -} +} \ No newline at end of file From 400c53add5ba5bf039be748b43949f31f7140195 Mon Sep 17 00:00:00 2001 From: Ludo Mikula Date: Wed, 11 Jun 2025 10:58:02 +0200 Subject: [PATCH 17/51] fix: add default values for environment variables --- .../service/ServerSettingServiceImpl.java | 58 +++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/serversetting/service/ServerSettingServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/serversetting/service/ServerSettingServiceImpl.java index 8512772fb..64e1847ab 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/serversetting/service/ServerSettingServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/serversetting/service/ServerSettingServiceImpl.java @@ -1,21 +1,29 @@ package org.lowcoder.domain.serversetting.service; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.lowcoder.domain.serversetting.model.ServerSetting; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.*; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import javax.annotation.PostConstruct; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +@RequiredArgsConstructor @Slf4j @Service public class ServerSettingServiceImpl implements ServerSettingService { + + private final Environment environment; private final ServerSettingRepository repository; + private final List EXCLUDED_KEYS = List.of("LOWCODER_MONGODB_EXPOSED", "LOWCODER_PUID", "LOWCODER_PGID", @@ -33,11 +41,6 @@ public class ServerSettingServiceImpl implements ServerSettingService { "LOWCODER_NODE_SERVICE_SECRET", "LOWCODER_NODE_SERVICE_SECRET_SALT"); - @Autowired - public ServerSettingServiceImpl(ServerSettingRepository repository) { - this.repository = repository; - } - @Override public Mono> getServerSettingsMap() { return repository.findAll().collectMap(ServerSetting::getKey, ServerSetting::getValue); @@ -45,9 +48,18 @@ public Mono> getServerSettingsMap() { @PostConstruct public void saveEnvironmentVariables() { - Map envVariables = System.getenv(); - Flux.fromIterable(envVariables.keySet()) - .filter(key -> key.startsWith("LOWCODER_")) + + Map defaults = getEnvironmentVariablesDefaults(); + + Map envVariables = new TreeMap<>(System.getenv().entrySet().stream() + .filter(entry -> StringUtils.startsWith(entry.getKey(), "LOWCODER_")) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + + Map merged = new TreeMap<>(defaults); + merged.keySet().removeAll(envVariables.keySet()); + merged.putAll(envVariables); + + Flux.fromIterable(merged.keySet()) .map(key -> { String value = envVariables.getOrDefault(key, ""); if(EXCLUDED_KEYS.contains(key)) { @@ -61,4 +73,30 @@ public void saveEnvironmentVariables() { .flatMap(repository::save) .subscribe(); } + + + private Map getEnvironmentVariablesDefaults() { + Map defaults = new HashMap<>(); + + MutablePropertySources propertySources = ((AbstractEnvironment) environment).getPropertySources(); + StreamSupport.stream(propertySources.spliterator(), false) + .filter(EnumerablePropertySource.class::isInstance) + .map(EnumerablePropertySource.class::cast) + .forEach(propertySource -> { + String[] names = propertySource.getPropertyNames(); + if (names.length > 0) { + Arrays.stream(names).forEach(name -> { + String rawValue = Objects.toString(propertySource.getProperty(name), ""); + if (rawValue != null && StringUtils.contains(rawValue, "${LOWCODER_")) { + String defaultValue = StringUtils.substringBetween(rawValue, "${", "}"); + String[] keyValue = StringUtils.split(defaultValue, ":"); + if (keyValue.length == 2 && !defaults.containsKey(keyValue[0])) { + defaults.put(keyValue[0], keyValue[1]); + } + } + }); + } + }); + return defaults; + } } From b93592b8fbc5a07565db1b550040f3411d2967b8 Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Wed, 11 Jun 2025 19:06:23 +0500 Subject: [PATCH 18/51] Update en.ts --- client/packages/lowcoder/src/i18n/locales/en.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index c379004bc..c88dde8eb 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -591,6 +591,7 @@ export const en = { "chartBorderColor": "Border Color", "chartTextColor": "Text Color", "detailSize": "Detail Size", + "hideColumn": "Hide Column", "radiusTip": "Specifies the radius of the element's corners. Example: 5px, 50%, or 1em.", "gapTip": "Specifies the gap between rows and columns in a grid or flex container. Example: 10px, 1rem, or 5%.", From a65010b3d4f7aca1e39433d4b01af2b5fb5ac4c4 Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Wed, 11 Jun 2025 20:31:17 +0500 Subject: [PATCH 19/51] Updated event value --- .../lowcoder/src/comps/controls/eventHandlerControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx b/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx index 289a212ea..f3a751eb6 100644 --- a/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx @@ -378,7 +378,7 @@ export const doubleClickEvent: EventConfigType = { }; export const rightClickEvent: EventConfigType = { label: trans("event.rightClick"), - value: "rightClick", + value: "doubleClick", description: trans("event.rightClickDesc"), }; From 0431090d8b90a69522f84f3852cdff8345db9dd2 Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Thu, 12 Jun 2025 03:24:34 +0500 Subject: [PATCH 20/51] Fixed single click events on firing double click --- .../comps/tableComp/selectionControl.tsx | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/selectionControl.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/selectionControl.tsx index 80e8e0340..037516d91 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/selectionControl.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/selectionControl.tsx @@ -7,6 +7,11 @@ import { TableOnEventView } from "./tableTypes"; import { OB_ROW_ORI_INDEX, RecordType } from "comps/comps/tableComp/tableUtils"; import { ControlNodeCompBuilder } from "comps/generators/controlCompBuilder"; +// double-click detection constants +const DOUBLE_CLICK_THRESHOLD = 300; // ms +let lastClickTime = 0; +let clickTimer: ReturnType; + const modeOptions = [ { label: trans("selectionControl.single"), @@ -38,8 +43,9 @@ export function getSelectedRowKeys( return [selection.children.selectedRowKey.getView()]; case "multiple": return selection.children.selectedRowKeys.getView(); + default: + return []; } - return []; } export const SelectionControl = (function () { @@ -50,40 +56,52 @@ export const SelectionControl = (function () { }; return new ControlNodeCompBuilder(childrenMap, (props, dispatch) => { const changeSelectedRowKey = (record: RecordType) => { - if (getKey(record) !== props.selectedRowKey) { - dispatch(changeChildAction("selectedRowKey", getKey(record), false)); + const key = getKey(record); + if (key !== props.selectedRowKey) { + dispatch(changeChildAction("selectedRowKey", key, false)); } }; + return (onEvent: TableOnEventView) => { + const handleClick = (record: RecordType) => { + return () => { + const now = Date.now(); + clearTimeout(clickTimer); + if (now - lastClickTime < DOUBLE_CLICK_THRESHOLD) { + + changeSelectedRowKey(record); + onEvent("doubleClick"); + if (getKey(record) !== props.selectedRowKey) { + onEvent("rowSelectChange"); + } + } else { + clickTimer = setTimeout(() => { + changeSelectedRowKey(record); + onEvent("rowClick"); + if (getKey(record) !== props.selectedRowKey) { + onEvent("rowSelectChange"); + } + }, DOUBLE_CLICK_THRESHOLD); + } + lastClickTime = now; + }; + }; + if (props.mode === "single" || props.mode === "close") { return { rowKey: getKey, rowClassName: (record: RecordType, index: number, indent: number) => { - // Turn off row selection mode, only do visual shutdown, selectedRow still takes effect if (props.mode === "close") { return ""; } return getKey(record) === props.selectedRowKey ? "ant-table-row-selected" : ""; }, - onRow: (record: RecordType, index: number | undefined) => { - return { - onClick: () => { - changeSelectedRowKey(record); - onEvent("rowClick"); - if (getKey(record) !== props.selectedRowKey) { - onEvent("rowSelectChange"); - } - }, - onDoubleClick: () => { - onEvent("doubleClick"); - if (getKey(record) !== props.selectedRowKey) { - onEvent("rowSelectChange"); - } - } - }; - }, + onRow: (record: RecordType, index: number | undefined) => ({ + onClick: handleClick(record), + }), }; } + const result: TableRowSelection = { type: "checkbox", selectedRowKeys: props.selectedRowKeys, @@ -92,7 +110,6 @@ export const SelectionControl = (function () { dispatch(changeChildAction("selectedRowKeys", selectedRowKeys as string[], false)); onEvent("rowSelectChange"); }, - // click checkbox also trigger row click event onSelect: (record: RecordType) => { changeSelectedRowKey(record); onEvent("rowClick"); @@ -101,18 +118,9 @@ export const SelectionControl = (function () { return { rowKey: getKey, rowSelection: result, - onRow: (record: RecordType) => { - return { - onClick: () => { - changeSelectedRowKey(record); - onEvent("rowClick"); - }, - onDoubleClick: () => { - changeSelectedRowKey(record); - onEvent("doubleClick"); - } - }; - }, + onRow: (record: RecordType) => ({ + onClick: handleClick(record), + }), }; }; }) @@ -123,4 +131,4 @@ export const SelectionControl = (function () { }) ) .build(); -})(); +})(); \ No newline at end of file From 51b92051b2bcff23cd0f94a4b48a9068d00845b5 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 12 Jun 2025 11:33:09 +0500 Subject: [PATCH 21/51] small fix for summary rows --- .../lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx index bd7a5d0ff..56e4584c2 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx @@ -203,7 +203,7 @@ export const TableSummary = memo(function TableSummary(props: { const visibleColumns = useMemo(() => { let cols = columns.filter(col => !col.getView().hide); - if (dynamicColumn) { + if (dynamicColumn && dynamicColumnConfig?.length) { cols = cols.filter(col => { const colView = col.getView(); return dynamicColumnConfig.includes(colView.isCustom ? colView.title : colView.dataIndex) From 1798dbdd234f0029cbb2be84ef8bceb84ad3d20f Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 12 Jun 2025 16:46:58 +0500 Subject: [PATCH 22/51] expose selected option with autocomplete comp --- .../autoCompleteComp/autoCompleteComp.tsx | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx b/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx index f7e2eaa67..2b33bf376 100644 --- a/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx @@ -43,6 +43,7 @@ import { default as AutoComplete } from "antd/es/auto-complete"; import { RefControl } from "comps/controls/refControl"; import { booleanExposingStateControl, + jsonValueExposingStateControl, } from "comps/controls/codeStateControl"; import { getDayJSLocale } from "i18n/dayjsLocale"; @@ -88,6 +89,7 @@ const childrenMap = { autocompleteIconColor: dropdownControl(autocompleteIconColor, "blue"), componentSize: dropdownControl(componentSize, "small"), valueInItems: booleanExposingStateControl("valueInItems"), + selectedOption: jsonValueExposingStateControl("selectedOption", {}), style: styleControl(InputFieldStyle , 'style'), labelStyle: styleControl(LabelStyle , 'labelStyle'), inputFieldStyle: styleControl(InputLikeStyle , 'inputFieldStyle'), @@ -247,14 +249,18 @@ let AutoCompleteCompBase = (function () { setsearchtext(value); props.value.onChange(value); props.onEvent("change"); - }, [props.valueInItems, getTextInputValidate, props.value, props.onEvent]); + if(!Boolean(value)) { + props.selectedOption.onChange({}); + } + }, [props.valueInItems, getTextInputValidate, props.value, props.onEvent, props.selectedOption]); const handleSelect = useCallback((data: string, option: any) => { setsearchtext(option[valueOrLabel]); props.valueInItems.onChange(true); props.value.onChange(option[valueOrLabel]); + props.selectedOption.onChange(option); props.onEvent("submit"); - }, [valueOrLabel, props.valueInItems, props.value, props.onEvent]); + }, [valueOrLabel, props.valueInItems, props.value, props.onEvent, props.selectedOption]); const handleFocus = useCallback(() => { setActivationFlag(true); @@ -313,17 +319,7 @@ let AutoCompleteCompBase = (function () { .setPropertyViewFn((children) => { return ( <> -
- {children.autoCompleteType.getView() === 'normal' && - children.prefixIcon.propertyView({ - label: trans('button.prefixIcon'), - })} - {children.autoCompleteType.getView() === 'normal' && - children.suffixIcon.propertyView({ - label: trans('button.suffixIcon'), - })} - {allowClearPropertyView(children)} -
+
{children.items.propertyView({ label: trans('autoComplete.value'), @@ -351,25 +347,35 @@ let AutoCompleteCompBase = (function () { label: trans('autoComplete.ignoreCase'), }) )} - {children.filterOptionsByInput.getView() && ( - children.valueOrLabel.propertyView({ - label: trans('autoComplete.checkedValueFrom'), - radioButton: true, - }) - )} + {children.valueOrLabel.propertyView({ + label: trans('autoComplete.checkedValueFrom'), + radioButton: true, + })}
- {children.label.getPropertyView()} - {}
{hiddenPropertyView(children)}
+ +
+ {children.autoCompleteType.getView() === 'normal' && + children.prefixIcon.propertyView({ + label: trans('button.prefixIcon'), + })} + {children.autoCompleteType.getView() === 'normal' && + children.suffixIcon.propertyView({ + label: trans('button.suffixIcon'), + })} + {allowClearPropertyView(children)} +
+ + {}
{children.style.getPropertyView()} @@ -389,9 +395,6 @@ let AutoCompleteCompBase = (function () { > {children.animationStyle.getPropertyView()}
-
- {children.tabIndex.propertyView({ label: trans("prop.tabIndex") })} -
); }) @@ -415,6 +418,7 @@ AutoCompleteCompBase = class extends AutoCompleteCompBase { export const AutoCompleteComp = withExposingConfigs(AutoCompleteCompBase, [ new NameConfig("value", trans("export.inputValueDesc")), new NameConfig("valueInItems", trans("autoComplete.valueInItems")), + new NameConfig("selectedOption", trans("autoComplete.selectedOption")), NameConfigPlaceHolder, NameConfigRequired, ...TextInputConfigs, From 55ffef580fe32516d5fcc4e67cf1900e7a1f2242 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 12 Jun 2025 19:17:59 +0500 Subject: [PATCH 23/51] fixed datatime column editing issue in table --- .../comps/tableComp/column/columnTypeComps/columnDateComp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDateComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDateComp.tsx index 99bee383e..55168b151 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDateComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDateComp.tsx @@ -127,7 +127,7 @@ const Wrapper = styled.div` export function formatDate(date: string, format: string) { let mom = dayjs(date); - if (isNumber(Number(date)) && date !== "") { + if (isNumber(Number(date)) && !isNaN(Number(date)) && date !== "") { mom = dayjs(Number(date)); } if (!mom.isValid()) { From 4dbce10475a6c1ace48d172f5408e8884ad4c482 Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Thu, 12 Jun 2025 20:14:06 +0500 Subject: [PATCH 24/51] Updated Double Click event on all components --- .../lowcoder/src/comps/comps/avatar.tsx | 6 ++-- .../lowcoder/src/comps/comps/avatarGroup.tsx | 6 ++-- .../src/comps/comps/buttonComp/buttonComp.tsx | 3 +- .../comps/buttonComp/floatButtonComp.tsx | 3 +- .../comps/comps/commentComp/commentComp.tsx | 10 +++--- .../comps/comps/containerComp/cardComp.tsx | 6 ++-- .../lowcoder/src/comps/comps/iconComp.tsx | 4 +-- .../lowcoder/src/comps/comps/imageComp.tsx | 4 +-- .../comps/comps/meetingComp/controlButton.tsx | 3 +- .../columnTypeComps/ColumnNumberComp.tsx | 9 +++--- .../columnTypeComps/columnAvatarsComp.tsx | 9 ++---- .../columnTypeComps/columnDropdownComp.tsx | 9 ++---- .../column/columnTypeComps/columnLinkComp.tsx | 10 ++---- .../columnTypeComps/columnLinksComp.tsx | 23 +++++--------- .../column/columnTypeComps/simpleTextComp.tsx | 10 +++--- .../column/simpleColumnTypeComps.tsx | 8 ++--- .../lowcoder/src/comps/comps/textComp.tsx | 8 ++--- .../comps/comps/timelineComp/timelineComp.tsx | 9 ++---- .../comps/controls/eventHandlerControl.tsx | 1 + .../src/comps/utils/componentClickHandler.tsx | 31 +++++++++++++++++++ 20 files changed, 85 insertions(+), 87 deletions(-) create mode 100644 client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx diff --git a/client/packages/lowcoder/src/comps/comps/avatar.tsx b/client/packages/lowcoder/src/comps/comps/avatar.tsx index f07de98ca..c9d5b9602 100644 --- a/client/packages/lowcoder/src/comps/comps/avatar.tsx +++ b/client/packages/lowcoder/src/comps/comps/avatar.tsx @@ -35,6 +35,8 @@ import { BadgeBasicSection, badgeChildren } from "./badgeComp/badgeConstants"; import { DropdownOptionControl } from "../controls/optionsControl"; import { ReactElement, useContext, useEffect } from "react"; import { CompNameContext, EditorContext } from "../editorState"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; + const AvatarWrapper = styled(Avatar) ` background: ${(props) => props.$style.background}; @@ -182,9 +184,7 @@ const AvatarView = (props: RecordConstructorToView) => { shape={shape} $style={props.avatarStyle} src={src.value} - // $cursorPointer={eventsCount > 0} - onClick={() => props.onEvent("click")} - onDoubleClick={() => props.onEvent("doubleClick")} + onClick={ComponentClickHandler({onEvent: props.onEvent})} > {title.value} diff --git a/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx b/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx index 8f35bd4f4..9181c5c21 100644 --- a/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx +++ b/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx @@ -19,6 +19,7 @@ import { optionsControl } from "../controls/optionsControl"; import { BoolControl } from "../controls/boolControl"; import { dropdownControl } from "../controls/dropdownControl"; import { JSONObject } from "util/jsonTypes"; +import { ComponentClickHandler } from "../utils/componentClickHandler"; const MacaroneList = [ '#fde68a', @@ -125,12 +126,9 @@ const AvatarGroupView = (props: RecordConstructorToView & { }} size={props.avatarSize} onClick={() => { - props.onEvent("click") + ComponentClickHandler({onEvent: props.onEvent})(); props.dispatch(changeChildAction("currentAvatar", item as JSONObject, false)); }} - onDoubleClick={() => { - props.onEvent("doubleClick") - }} > {item.label} diff --git a/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx b/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx index 6f657c1e8..e7218dd70 100644 --- a/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx @@ -29,6 +29,7 @@ import { AnimationStyle } from "@lowcoder-ee/comps/controls/styleControlConstant import { styleControl } from "@lowcoder-ee/comps/controls/styleControl"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; const FormLabel = styled(CommonBlueLabel)` font-size: 13px; @@ -193,7 +194,7 @@ const ButtonView = React.memo((props: ToViewReturn) => { try { if (isDefault(props.type)) { - props.onEvent("click"); + ComponentClickHandler({onEvent: props.onEvent})() } else { submitForm(editorState, props.form); } diff --git a/client/packages/lowcoder/src/comps/comps/buttonComp/floatButtonComp.tsx b/client/packages/lowcoder/src/comps/comps/buttonComp/floatButtonComp.tsx index 223650ef4..71fbbba20 100644 --- a/client/packages/lowcoder/src/comps/comps/buttonComp/floatButtonComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/buttonComp/floatButtonComp.tsx @@ -17,6 +17,7 @@ import styled from "styled-components"; import { ButtonEventHandlerControl } from "comps/controls/eventHandlerControl"; import { manualOptionsControl } from "comps/controls/optionsControl"; import { useContext, useEffect } from "react"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; const StyledFloatButton = styled(FloatButton)<{ $animationStyle: AnimationStyleType; @@ -105,7 +106,7 @@ const FloatButtonView = (props: RecordConstructorToView) => $animationStyle={props.animationStyle} key={button?.id} icon={button?.icon} - onClick={() => button.onEvent("click")} + onClick={ComponentClickHandler({onEvent: button.onEvent})} tooltip={button?.label} description={button?.description} badge={{ count: button?.badge, color: props.badgeStyle.badgeColor, dot: props?.dot }} diff --git a/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx b/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx index d79fa542d..8e7ab8dfc 100644 --- a/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx @@ -26,10 +26,10 @@ import { deleteEvent, mentionEvent, doubleClickEvent, -} from "comps/controls/eventHandlerControl"; - +} from "comps/controls/eventHandlerControl"; import { EditorContext } from "comps/editorState"; + // Introducing styles import { AnimationStyle, @@ -67,6 +67,7 @@ import dayjs from "dayjs"; // import "dayjs/locale/zh-cn"; import { getInitialsAndColorCode } from "util/stringUtils"; import { default as CloseOutlined } from "@ant-design/icons/CloseOutlined"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; dayjs.extend(relativeTime); // dayjs.locale("zh-cn"); @@ -176,7 +177,7 @@ const CommentCompBase = ( const generateCommentAvatar = (item: commentDataTYPE) => { return ( props.onEvent("click")} + onClick={ComponentClickHandler({onEvent: props.onEvent})} // If there is an avatar, no background colour is set, and if displayName is not null, displayName is called using getInitialsAndColorCode style={{ backgroundColor: item?.user?.avatar @@ -293,8 +294,7 @@ const CommentCompBase = ( avatar={generateCommentAvatar(item)} title={
props.onEvent("click")} - onDoubleClick={() => props.onEvent("doubleClick")} + onClick={ComponentClickHandler({onEvent: props.onEvent})} > {item?.user?.name} { props.container.showHeader = false; - // 注入容器参数 props.container.style = Object.assign(props.container.style, { CONTAINER_BODY_PADDING: props.style.containerBodyPadding, border: '#00000000', @@ -233,7 +233,7 @@ export const ContainerBaseComp = (function () { $cardType={props.cardType} onMouseEnter={() => props.onEvent('focus')} onMouseLeave={() => props.onEvent('blur')} - onClick={() => props.onEvent('click')} + onClick={ComponentClickHandler({onEvent: props.onEvent})} > !item.hidden).map(item => { return ( item.onEvent('click')} + onClick={ComponentClickHandler({onEvent: props.onEvent})} disabled={item.disabled} $style={props.style} > diff --git a/client/packages/lowcoder/src/comps/comps/iconComp.tsx b/client/packages/lowcoder/src/comps/comps/iconComp.tsx index e50bd002c..12a3da198 100644 --- a/client/packages/lowcoder/src/comps/comps/iconComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/iconComp.tsx @@ -33,6 +33,7 @@ import { useContext } from "react"; import { EditorContext } from "comps/editorState"; import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl"; import { dropdownControl } from "../controls/dropdownControl"; +import { ComponentClickHandler } from "../utils/componentClickHandler"; const Container = styled.div<{ $sourceMode: string; @@ -135,8 +136,7 @@ const IconView = (props: RecordConstructorToView) => { $sourceMode={props.sourceMode} $animationStyle={props.animationStyle} style={style} - onClick={() => props.onEvent("click")} - onDoubleClick={() => props.onEvent("doubleClick")} + onClick={ComponentClickHandler({onEvent: props.onEvent})} > { props.sourceMode === 'standard' ? (props.icon || '') diff --git a/client/packages/lowcoder/src/comps/comps/imageComp.tsx b/client/packages/lowcoder/src/comps/comps/imageComp.tsx index b0bd4d3dd..e82d2bab2 100644 --- a/client/packages/lowcoder/src/comps/comps/imageComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/imageComp.tsx @@ -38,6 +38,7 @@ import { StringControl } from "../controls/codeControl"; import { PositionControl } from "comps/controls/dropdownControl"; import { dropdownControl } from "../controls/dropdownControl"; import { AssetType, IconscoutControl } from "../controls/iconscoutControl"; +import { ComponentClickHandler } from "../utils/componentClickHandler"; const Container = styled.div<{ $style: ImageStyleType | undefined, @@ -212,8 +213,7 @@ const ContainerImg = (props: RecordConstructorToView) => { draggable={false} preview={props.supportPreview ? {src: props.previewSrc || props.src.value } : false} fallback={DEFAULT_IMG_URL} - onClick={() => props.onEvent("click")} - onDoubleClick={() => props.onEvent("doubleClick")} + onClick={ComponentClickHandler({onEvent: props.onEvent})} />
diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx index 313358815..90da85e8c 100644 --- a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx +++ b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx @@ -41,6 +41,7 @@ import { useResizeDetector } from "react-resize-detector"; import { useContext } from "react"; import { Tooltip } from "antd"; import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; const Container = styled.div<{ $style: any }>` height: 100%; @@ -285,7 +286,7 @@ let ButtonTmpComp = (function () { } onClick={() => isDefault(props.type) - ? props.onEvent("click") + ? ComponentClickHandler({onEvent: props.onEvent})() : submitForm(editorState, props.form) } > diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx index 78bba9380..af77fa472 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx @@ -9,7 +9,8 @@ import { withDefault } from "comps/generators"; import styled from "styled-components"; import { IconControl } from "comps/controls/iconControl"; import { hasIcon } from "comps/utils"; -import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; const InputNumberWrapper = styled.div` .ant-input-number { @@ -33,7 +34,7 @@ const NumberViewWrapper = styled.div` gap: 4px; `; -const NumberEventOptions = [clickEvent] as const; +const NumberEventOptions = [clickEvent, doubleClickEvent] as const; const childrenMap = { text: NumberControl, @@ -79,9 +80,7 @@ const ColumnNumberView = React.memo((props: NumberViewProps) => { }, [props.value, props.float, props.precision]); const handleClick = useCallback(() => { - if (props.onEvent) { - props.onEvent("click"); - } + props.onEvent && ComponentClickHandler({onEvent: props.onEvent})() }, [props.onEvent]); return ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx index a33100154..a8f864a77 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx @@ -17,6 +17,7 @@ import { optionsControl } from "comps/controls/optionsControl"; import { BoolControl } from "comps/controls/boolControl"; import { dropdownControl } from "comps/controls/dropdownControl"; import { JSONObject } from "util/jsonTypes"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; const MacaroneList = [ '#fde68a', @@ -116,14 +117,9 @@ const MemoizedAvatar = React.memo(({ } // Then trigger main component event - onEvent("click"); + ComponentClickHandler({onEvent})() }, [onEvent, onItemEvent]); - const handleDoubleClick = useCallback(() => { - if (!mountedRef.current) return; - onEvent("doubleClick"); - }, [onEvent]); - return ( {item.label} diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx index d7efb3aff..cb426e296 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx @@ -16,6 +16,7 @@ import { Button100 } from "comps/comps/buttonComp/buttonCompConstants"; import styled from "styled-components"; import { ButtonType } from "antd/es/button"; import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; const StyledButton = styled(Button100)` display: flex; @@ -59,7 +60,7 @@ const DropdownMenu = React.memo(({ items, options, onEvent }: { items: any[]; op const itemIndex = options.findIndex(option => option.label === item?.label); item && options[itemIndex]?.onEvent("click"); // Also trigger the dropdown's main event handler - onEvent?.("click"); + onEvent && ComponentClickHandler({onEvent})(); }, [items, options, onEvent]); const handleMouseDown = useCallback((e: React.MouseEvent) => { @@ -67,16 +68,10 @@ const DropdownMenu = React.memo(({ items, options, onEvent }: { items: any[]; op e.preventDefault(); }, []); - const handleDoubleClick = useCallback((e: React.MouseEvent) => { - if (!mountedRef.current) return; - onEvent?.("doubleClick"); - }, [onEvent]); - return ( ); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx index 183d87889..e285898d7 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx @@ -12,6 +12,7 @@ import styled, { css } from "styled-components"; import { styleControl } from "comps/controls/styleControl"; import { TableColumnLinkStyle } from "comps/controls/styleControlConstants"; import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); @@ -40,13 +41,7 @@ const StyledLink = styled.a<{ $disabled: boolean }>` export const ColumnLink = React.memo(({ disabled, label, onEvent }: { disabled: boolean; label: string; onEvent?: (eventName: string) => void }) => { const handleClick = useCallback(() => { if (!disabled && onEvent) { - onEvent("click"); - } - }, [disabled, onEvent]); - - const handleDoubleClick = useCallback(() => { - if (!disabled && onEvent) { - onEvent("doubleClick"); + ComponentClickHandler({onEvent})(); } }, [disabled, onEvent]); @@ -54,7 +49,6 @@ export const ColumnLink = React.memo(({ disabled, label, onEvent }: { disabled: {label} diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx index e62db2c74..ed5190c54 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx @@ -11,6 +11,7 @@ import styled from "styled-components"; import { ColumnLink } from "comps/comps/tableComp/column/columnTypeComps/columnLinkComp"; import { LightActiveTextColor, PrimaryColor } from "constants/style"; import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; const MenuLinkWrapper = styled.div` > a { @@ -73,22 +74,14 @@ const OptionItem = new MultiCompBuilder( const MenuItem = React.memo(({ option, index, onMainEvent }: { option: any; index: number; onMainEvent?: (eventName: string) => void }) => { const handleClick = useCallback(() => { if (!option.disabled) { - if (option.onClick) { - option.onClick(); - } - if (option.onDoubleClick) { - option.onDoubleClick(); - } - if (option.onEvent) { - option.onEvent("click"); - } - - // Trigger the main component's event handler - if (onMainEvent) { - onMainEvent("click"); - } + // Handle both option's event and main event through ComponentClickHandler + const combinedHandler = (event: "click" | "doubleClick") => { + option.onEvent?.(event); + onMainEvent?.(event); + }; + ComponentClickHandler({onEvent: combinedHandler})(); } - }, [option.disabled, option.onClick, option.onEvent, onMainEvent, option.onDoubleClick]); + }, [option.disabled, option.onEvent, onMainEvent]); return ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx index aba505252..3bb7aeb50 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx @@ -7,10 +7,11 @@ import { IconControl } from "comps/controls/iconControl"; import { hasIcon } from "comps/utils"; import React, { useCallback, useMemo } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; -import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { clickEvent, doubleClickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; import styled from "styled-components"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; -const TextEventOptions = [clickEvent] as const; +const TextEventOptions = [clickEvent, doubleClickEvent] as const; const TextWrapper = styled.div` cursor: pointer; @@ -50,9 +51,8 @@ interface SimpleTextEditViewProps { const SimpleTextContent = React.memo(({ value, prefixIcon, suffixIcon, onEvent }: SimpleTextContentProps) => { const handleClick = useCallback(() => { - if (onEvent) { - onEvent("click"); - } + console.log("This comp"); + onEvent && ComponentClickHandler({onEvent})() }, [onEvent]); return ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index 0df62e2f0..2a28f098c 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -14,6 +14,7 @@ import { CSSProperties } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); @@ -52,11 +53,7 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn { - props.onEvent("click"); - }, [props.onEvent]); - - const handleDoubleClick = useCallback((e: React.MouseEvent) => { - props.onEvent("doubleClick"); + ComponentClickHandler({onEvent: props.onEvent}) }, [props.onEvent]); const buttonStyle = useMemo(() => ({ @@ -75,7 +72,6 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn {/* prevent the button from disappearing */} {hasText ? props.text : (iconOnly ? null : " ")} diff --git a/client/packages/lowcoder/src/comps/comps/textComp.tsx b/client/packages/lowcoder/src/comps/comps/textComp.tsx index 73f776ac3..b41ab1b61 100644 --- a/client/packages/lowcoder/src/comps/comps/textComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/textComp.tsx @@ -25,6 +25,7 @@ import { NewChildren } from "../generators/uiCompBuilder"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "../generators/multi"; import { BoolControl } from "../controls/boolControl"; +import { ComponentClickHandler } from "../utils/componentClickHandler"; const EventOptions = [clickEvent, doubleClickEvent] as const; @@ -225,11 +226,9 @@ const TextPropertyView = React.memo((props: { const TextView = React.memo((props: ToViewReturn) => { const value = props.text.value; const handleClick = React.useCallback(() => { - props.onEvent("click"); - }, [props.onEvent]); - const handleDoubleClick = React.useCallback(() => { - props.onEvent("doubleClick"); + props.onEvent && ComponentClickHandler({onEvent: props.onEvent})() }, [props.onEvent]); + const containerStyle = useMemo(() => ({ justifyContent: props.horizontalAlignment, alignItems: props.autoHeight ? "center" : props.verticalAlignment, @@ -249,7 +248,6 @@ const TextView = React.memo((props: ToViewReturn) => { $styleConfig={props.style} style={containerStyle} onClick={handleClick} - onDoubleClick={handleDoubleClick} > {content} diff --git a/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx b/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx index e7743f0f8..ee5e16264 100644 --- a/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx @@ -50,6 +50,7 @@ import { convertTimeLineData } from "./timelineUtils"; import { default as Timeline } from "antd/es/timeline"; import { EditorContext } from "comps/editorState"; import { styled } from "styled-components"; +import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; const TimelineWrapper = styled.div<{ $style: TimeLineStyleType @@ -142,13 +143,7 @@ const TimelineComp = ( e.preventDefault(); dispatch(changeChildAction("clickedObject", value, false)); dispatch(changeChildAction("clickedIndex", index, false)); - onEvent("click"); - }} - onDoubleClick={(e) => { - e.preventDefault(); - dispatch(changeChildAction("clickedObject", value, false)); - dispatch(changeChildAction("clickedIndex", index, false)); - onEvent("doubleClick"); + ComponentClickHandler({onEvent})() }} // for responsiveness style={{ diff --git a/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx b/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx index f3a751eb6..b4b19d522 100644 --- a/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx @@ -819,4 +819,5 @@ export const CardEventHandlerControl = eventHandlerControl([ clickExtraEvent, focusEvent, blurEvent, + doubleClickEvent ] as const); \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx b/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx new file mode 100644 index 000000000..705321074 --- /dev/null +++ b/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx @@ -0,0 +1,31 @@ +import React from "react"; + +export enum ClickEventType { + CLICK = "click", + DOUBLE_CLICK = "doubleClick" +} + +interface Props { + onEvent: (event: ClickEventType) => void; +} + +const DOUBLE_CLICK_THRESHOLD = 300; // ms +let lastClickTime = 0; +let clickTimer: ReturnType; + +export const ComponentClickHandler = (props: Props) => { + return () => { + const now = Date.now() + clearTimeout(clickTimer) + + if((now - lastClickTime) < DOUBLE_CLICK_THRESHOLD){ + return props.onEvent(ClickEventType.DOUBLE_CLICK) + } else { + clickTimer = setTimeout(() => { + props.onEvent(ClickEventType.CLICK) + }, DOUBLE_CLICK_THRESHOLD) + } + + lastClickTime = now + } +} \ No newline at end of file From a27f132b40cdc5d2c7ed10568eb71957c0bb0af7 Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Thu, 12 Jun 2025 20:21:42 +0500 Subject: [PATCH 25/51] fix: - Added timeout - Removed Console logs --- .../comps/tableComp/column/columnTypeComps/simpleTextComp.tsx | 1 - .../lowcoder/src/comps/utils/componentClickHandler.tsx | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx index 3bb7aeb50..c346c22d9 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx @@ -51,7 +51,6 @@ interface SimpleTextEditViewProps { const SimpleTextContent = React.memo(({ value, prefixIcon, suffixIcon, onEvent }: SimpleTextContentProps) => { const handleClick = useCallback(() => { - console.log("This comp"); onEvent && ComponentClickHandler({onEvent})() }, [onEvent]); diff --git a/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx b/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx index 705321074..47fd335da 100644 --- a/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx +++ b/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx @@ -19,7 +19,8 @@ export const ComponentClickHandler = (props: Props) => { clearTimeout(clickTimer) if((now - lastClickTime) < DOUBLE_CLICK_THRESHOLD){ - return props.onEvent(ClickEventType.DOUBLE_CLICK) + clearTimeout(clickTimer) + props.onEvent(ClickEventType.DOUBLE_CLICK) } else { clickTimer = setTimeout(() => { props.onEvent(ClickEventType.CLICK) From 804449fe3e883400afd98b7e0168ec7ce7b8bb6d Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 12 Jun 2025 23:17:49 +0500 Subject: [PATCH 26/51] fix choose datasource dropdown in query panel value --- .../src/comps/queries/queryComp/queryPropertyView.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx index d78f7d6ab..fd4939da4 100644 --- a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx +++ b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx @@ -239,6 +239,11 @@ export const QueryGeneralPropertyView = (props: { comp.children.datasourceId.dispatchChangeValueAction(QUICK_REST_API_ID); } + if (datasourceType === 'js' && datasourceId === '') { + datasourceId = JS_CODE_ID; + comp.children.datasourceId.dispatchChangeValueAction(JS_CODE_ID); + } + const triggerOptions = useMemo(() => { if (datasourceType === "js" || datasourceType === "streamApi") { return JSTriggerTypeOptions; From 0d1e53e9b4b154ecf86f9457774530a74421ba1f Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 12 Jun 2025 23:18:56 +0500 Subject: [PATCH 27/51] revert table column's event handlers --- .../column/columnTypeComps/columnLinkComp.tsx | 19 ++++++++++++------- .../columnTypeComps/columnLinksComp.tsx | 8 ++++---- .../column/simpleColumnTypeComps.tsx | 12 +++++++++--- .../tableComp/column/tableColumnComp.tsx | 4 ++-- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx index 512329ee3..b9c150c66 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx @@ -19,6 +19,7 @@ const LinkEventOptions = [clickEvent] as const; const childrenMap = { text: StringControl, + onClick: ActionSelectorControlInContext, onEvent: eventHandlerControl(LinkEventOptions), disabled: BoolCodeControl, style: styleControl(TableColumnLinkStyle), @@ -37,12 +38,12 @@ const StyledLink = styled.a<{ $disabled: boolean }>` `; // Memoized link component -export const ColumnLink = React.memo(({ disabled, label, onEvent }: { disabled: boolean; label: string; onEvent?: (eventName: string) => void }) => { +export const ColumnLink = React.memo(({ disabled, label, onClick, onEvent }: { disabled: boolean; label: string; onClick?: () => void; onEvent?: (eventName: string) => void }) => { const handleClick = useCallback(() => { - if (!disabled && onEvent) { - onEvent("click"); - } - }, [disabled, onEvent]); + if (disabled) return; + onClick?.(); + // onEvent?.("click"); + }, [disabled, onClick, onEvent]); return ( { const value = props.changeValue ?? getBaseValue(props, dispatch); - return ; + return ; }, (nodeValue) => nodeValue.text.value, getBaseValue @@ -128,7 +129,11 @@ export const LinkComp = (function () { tooltip: ColumnValueTooltip, })} {disabledPropertyView(children)} - {children.onEvent.propertyView()} + {/* {children.onEvent.propertyView()} */} + {children.onClick.propertyView({ + label: trans("table.action"), + placement: "table", + })} )) .setStylePropertyViewFn((children) => ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx index 3d35aa31d..7b574eda1 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx @@ -63,7 +63,7 @@ const OptionItem = new MultiCompBuilder( })} {hiddenPropertyView(children)} {disabledPropertyView(children)} - {children.onEvent.propertyView()} + {/* {children.onEvent.propertyView()} */} ); }) @@ -76,9 +76,9 @@ const MenuItem = React.memo(({ option, index, onMainEvent }: { option: any; inde if (option.onClick) { option.onClick(); } - if (option.onEvent) { - option.onEvent("click"); - } + // if (option.onEvent) { + // option.onEvent("click"); + // } // Trigger the main component's event handler if (onMainEvent) { onMainEvent("click"); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index ba264c5e4..a7c79032a 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -38,6 +38,7 @@ const childrenMap = { text: StringControl, buttonType: dropdownControl(ButtonTypeOptions, "primary"), onEvent: eventHandlerControl(ButtonEventOptions), + onClick: ActionSelectorControlInContext, loading: BoolCodeControl, disabled: BoolCodeControl, prefixIcon: IconControl, @@ -52,8 +53,9 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn { - props.onEvent("click"); - }, [props.onEvent]); + props.onClick?.(); + // props.onEvent?.("click"); + }, [props.onClick, props.onEvent]); const buttonStyle = useMemo(() => ({ margin: 0, @@ -103,7 +105,11 @@ export const ButtonComp = (function () { })} {loadingPropertyView(children)} {disabledPropertyView(children)} - {children.onEvent.propertyView()} + {/* {children.onEvent.propertyView()} */} + {children.onClick.propertyView({ + label: trans("table.action"), + placement: "table", + })} )) .build(); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx index 7866cb813..bfadcdb1d 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx @@ -17,6 +17,7 @@ import { ConstructorToView, deferAction, fromRecord, + isDynamicSegment, multiChangeAction, withFunction, wrapChildAction, @@ -194,7 +195,6 @@ const ColumnPropertyView = React.memo(({ summaryRowIndex: number; }) => { const selectedColumn = comp.children.render.getSelectedComp(); - const columnType = useMemo(() => selectedColumn.getComp().children.compType.getView(), [selectedColumn] @@ -205,7 +205,7 @@ const ColumnPropertyView = React.memo(({ if (column.comp?.hasOwnProperty('src')) { return (column.comp as any).src; } else if (column.comp?.hasOwnProperty('text')) { - return (column.comp as any).text; + return isDynamicSegment((column.comp as any).text) ? '{{currentCell}}' : (column.comp as any).text; } return '{{currentCell}}'; }, [selectedColumn]); From 09f6c22a3b38333fa834934e288d3609bb2ad751 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 13 Jun 2025 15:11:01 +0500 Subject: [PATCH 28/51] fix data mapping dropdown value in table's column setting --- .../tableComp/column/tableColumnComp.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx index bfadcdb1d..938983ac9 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx @@ -17,7 +17,6 @@ import { ConstructorToView, deferAction, fromRecord, - isDynamicSegment, multiChangeAction, withFunction, wrapChildAction, @@ -199,21 +198,23 @@ const ColumnPropertyView = React.memo(({ selectedColumn.getComp().children.compType.getView(), [selectedColumn] ); - + + const initialColumns = useMemo(() => + selectedColumn.getParams()?.initialColumns as OptionType[] || [], + [selectedColumn] + ); + const columnValue = useMemo(() => { const column = selectedColumn.getComp().toJsonValue(); if (column.comp?.hasOwnProperty('src')) { return (column.comp as any).src; } else if (column.comp?.hasOwnProperty('text')) { - return isDynamicSegment((column.comp as any).text) ? '{{currentCell}}' : (column.comp as any).text; + const value = (column.comp as any).text; + const isDynamicValue = initialColumns.find((column) => column.value === value); + return !isDynamicValue ? '{{currentCell}}' : value; } return '{{currentCell}}'; - }, [selectedColumn]); - - const initialColumns = useMemo(() => - selectedColumn.getParams()?.initialColumns as OptionType[] || [], - [selectedColumn] - ); + }, [selectedColumn, initialColumns]); const summaryColumns = comp.children.summaryColumns.getView(); From c6f2d790e2bf5e5eaff7336055f6f23f9da8de0b Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 13 Jun 2025 16:35:27 +0500 Subject: [PATCH 29/51] fix memory leaks and convert click event wrapper to hook --- .../src/comps/comps/buttonComp/buttonComp.tsx | 6 ++- .../src/comps/utils/componentClickHandler.tsx | 47 ++++++++++++------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx b/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx index e7218dd70..1e44b5bbc 100644 --- a/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx @@ -29,7 +29,7 @@ import { AnimationStyle } from "@lowcoder-ee/comps/controls/styleControlConstant import { styleControl } from "@lowcoder-ee/comps/controls/styleControl"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; const FormLabel = styled(CommonBlueLabel)` font-size: 13px; @@ -182,6 +182,7 @@ const ButtonPropertyView = React.memo((props: { const ButtonView = React.memo((props: ToViewReturn) => { const editorState = useContext(EditorContext); const mountedRef = useRef(true); + const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent}); useEffect(() => { return () => { @@ -194,7 +195,8 @@ const ButtonView = React.memo((props: ToViewReturn) => { try { if (isDefault(props.type)) { - ComponentClickHandler({onEvent: props.onEvent})() + // ComponentClickHandler({onEvent: props.onEvent})() + handleClickEvent(); } else { submitForm(editorState, props.form); } diff --git a/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx b/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx index 47fd335da..e8f64cc5a 100644 --- a/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx +++ b/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback, useRef } from "react"; export enum ClickEventType { CLICK = "click", @@ -10,23 +10,38 @@ interface Props { } const DOUBLE_CLICK_THRESHOLD = 300; // ms -let lastClickTime = 0; -let clickTimer: ReturnType; -export const ComponentClickHandler = (props: Props) => { - return () => { - const now = Date.now() - clearTimeout(clickTimer) +export const useCompClickEventHandler = (props: Props) => { + const lastClickTimeRef = useRef(0); + const clickTimerRef = useRef>(); - if((now - lastClickTime) < DOUBLE_CLICK_THRESHOLD){ - clearTimeout(clickTimer) - props.onEvent(ClickEventType.DOUBLE_CLICK) + const handleClick = useCallback(() => { + const now = Date.now(); + + // Clear any existing timeout + if (clickTimerRef.current) { + clearTimeout(clickTimerRef.current); + } + + if ((now - lastClickTimeRef.current) < DOUBLE_CLICK_THRESHOLD) { + props.onEvent(ClickEventType.DOUBLE_CLICK); } else { - clickTimer = setTimeout(() => { - props.onEvent(ClickEventType.CLICK) - }, DOUBLE_CLICK_THRESHOLD) + clickTimerRef.current = setTimeout(() => { + props.onEvent(ClickEventType.CLICK); + }, DOUBLE_CLICK_THRESHOLD); } - lastClickTime = now - } -} \ No newline at end of file + lastClickTimeRef.current = now; + }, [props.onEvent]); + + // Cleanup on unmount + React.useEffect(() => { + return () => { + if (clickTimerRef.current) { + clearTimeout(clickTimerRef.current); + } + }; + }, []); + + return handleClick; +}; From 2798f3f866105bb1cce5535574dcbfe418c097d4 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 13 Jun 2025 18:42:56 +0500 Subject: [PATCH 30/51] revert back --- .../comps/tableComp/column/simpleColumnTypeComps.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index a7c79032a..3d5096cc8 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -13,7 +13,6 @@ import React, { useCallback, useEffect, useMemo } from "react"; import { CSSProperties } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; -import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); @@ -32,12 +31,9 @@ export const ButtonTypeOptions = [ }, ] as const; -const ButtonEventOptions = [clickEvent] as const; - const childrenMap = { text: StringControl, buttonType: dropdownControl(ButtonTypeOptions, "primary"), - onEvent: eventHandlerControl(ButtonEventOptions), onClick: ActionSelectorControlInContext, loading: BoolCodeControl, disabled: BoolCodeControl, @@ -54,8 +50,7 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn { props.onClick?.(); - // props.onEvent?.("click"); - }, [props.onClick, props.onEvent]); + }, [props.onClick]); const buttonStyle = useMemo(() => ({ margin: 0, @@ -105,7 +100,6 @@ export const ButtonComp = (function () { })} {loadingPropertyView(children)} {disabledPropertyView(children)} - {/* {children.onEvent.propertyView()} */} {children.onClick.propertyView({ label: trans("table.action"), placement: "table", From bd264935873d6844dbeb3aa81c0cff5fff1f83de Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 13 Jun 2025 20:07:26 +0500 Subject: [PATCH 31/51] fix localstorge values not reading on navigation to another app --- .../src/comps/hooks/localStorageComp.ts | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/client/packages/lowcoder/src/comps/hooks/localStorageComp.ts b/client/packages/lowcoder/src/comps/hooks/localStorageComp.ts index e3ce4633f..9a5b43411 100644 --- a/client/packages/lowcoder/src/comps/hooks/localStorageComp.ts +++ b/client/packages/lowcoder/src/comps/hooks/localStorageComp.ts @@ -3,7 +3,7 @@ import { isEmpty } from "lodash"; import { simpleMultiComp, stateComp, withViewFn } from "../generators"; import { NameConfig, withExposingConfigs } from "../generators/withExposing"; import { JSONObject } from "../../util/jsonTypes"; -import { useEffect } from "react"; +import { useEffect, useMemo, useCallback } from "react"; import isEqual from "fast-deep-equal"; import { trans } from "i18n"; import log from "loglevel"; @@ -13,28 +13,36 @@ const APP_STORE_NAMESPACE = "lowcoder_app_local_storage"; const LocalStorageCompBase = withViewFn( simpleMultiComp({ values: stateComp({}) }), (comp) => { - // add custom event listener to update values reactively - useEffect(() => { - const handler = () => { - try { - const raw = localStorage.getItem(APP_STORE_NAMESPACE) || "{}"; - const parsed = JSON.parse(raw); - comp.children.values.dispatchChangeValueAction(parsed); - } catch (e) { - log.error("Failed to parse localStorage:", e); - } - }; + const originStore = localStorage.getItem(APP_STORE_NAMESPACE) || "{}"; + + const parseStore = useMemo(() => { + try { + return JSON.parse(originStore); + } catch (e) { + log.error("application local storage invalid"); + return {}; + } + }, [originStore]); + + const handleStorageUpdate = useCallback(() => { + try { + comp.children.values.dispatchChangeValueAction(parseStore); + } catch (e) { + log.error("Failed to parse localStorage:", e); + } + }, [parseStore, comp.children.values]); + useEffect(() => { // Add listener on mount - window.addEventListener("lowcoder-localstorage-updated", handler); + window.addEventListener("lowcoder-localstorage-updated", handleStorageUpdate); // Run once on mount to initialize - handler(); + handleStorageUpdate(); return () => { - window.removeEventListener("lowcoder-localstorage-updated", handler); + window.removeEventListener("lowcoder-localstorage-updated", handleStorageUpdate); }; - }, []); + }, [handleStorageUpdate]); return null; } From b84dd48e858672a2bf9c2b1a335bec5a469a0209 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 13 Jun 2025 20:18:17 +0500 Subject: [PATCH 32/51] fix editor_mode_status and editor_panel_status values not updating in localstorage --- client/packages/lowcoder/src/pages/editor/editorView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder/src/pages/editor/editorView.tsx b/client/packages/lowcoder/src/pages/editor/editorView.tsx index c11d42e9c..2c7f0de92 100644 --- a/client/packages/lowcoder/src/pages/editor/editorView.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorView.tsx @@ -498,8 +498,6 @@ function EditorView(props: EditorViewProps) { return () => { window.removeEventListener(eventType, updateSize); - savePanelStatus(panelStatus); - saveEditorModeStatus(editorModeStatus); }; }, [panelStatus, editorModeStatus]); @@ -553,6 +551,8 @@ function EditorView(props: EditorViewProps) { setShowShortcutList(false); setMenuKey(SiderKey.State); setHeight(undefined); + savePanelStatus(panelStatus); + saveEditorModeStatus(editorModeStatus); }; }, []); From a87e00b781154cb486bdcec4619ec8af8d036adc Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 13 Jun 2025 21:49:42 +0500 Subject: [PATCH 33/51] [Fix]: Add backward compatibility for button, link and links --- .../column/columnTypeComps/columnLinkComp.tsx | 48 +++++++----- .../columnTypeComps/columnLinksComp.tsx | 75 ++++++++++--------- .../column/simpleColumnTypeComps.tsx | 65 +++++++++------- 3 files changed, 110 insertions(+), 78 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx index b9c150c66..3ad4bf275 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx @@ -37,12 +37,15 @@ const StyledLink = styled.a<{ $disabled: boolean }>` ${(props) => props.$disabled && disableCss}; `; -// Memoized link component +// Updated link component to handle both legacy and new event handlers export const ColumnLink = React.memo(({ disabled, label, onClick, onEvent }: { disabled: boolean; label: string; onClick?: () => void; onEvent?: (eventName: string) => void }) => { const handleClick = useCallback(() => { - if (disabled) return; - onClick?.(); - // onEvent?.("click"); + if (!disabled) { + // Trigger legacy onClick action for backward compatibility + onClick?.(); + // Trigger new event handlers + onEvent?.("click"); + } }, [disabled, onClick, onEvent]); return ( @@ -110,7 +113,7 @@ export const LinkComp = (function () { childrenMap, (props, dispatch) => { const value = props.changeValue ?? getBaseValue(props, dispatch); - return ; + return ; }, (nodeValue) => nodeValue.text.value, getBaseValue @@ -122,20 +125,27 @@ export const LinkComp = (function () { onChangeEnd={props.onChangeEnd} /> )) - .setPropertyViewFn((children) => ( - <> - {children.text.propertyView({ - label: trans("table.columnValue"), - tooltip: ColumnValueTooltip, - })} - {disabledPropertyView(children)} - {/* {children.onEvent.propertyView()} */} - {children.onClick.propertyView({ - label: trans("table.action"), - placement: "table", - })} - - )) + .setPropertyViewFn((children) => { + // Check if there's a legacy action configured + const hasLegacyAction = children.onClick.getView() && + typeof children.onClick.getView() === 'function' && + children.onClick.displayName() !== trans("eventHandler.incomplete"); + + return ( + <> + {children.text.propertyView({ + label: trans("table.columnValue"), + tooltip: ColumnValueTooltip, + })} + {disabledPropertyView(children)} + {children.onEvent.propertyView()} + {hasLegacyAction && children.onClick.propertyView({ + label: trans("table.action"), + placement: "table", + })} + + ); + }) .setStylePropertyViewFn((children) => ( <> {children.style.getPropertyView()} diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx index 7b574eda1..641c9adf9 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx @@ -40,65 +40,72 @@ const MenuWrapper = styled.div` const LinksEventOptions = [clickEvent] as const; +// Memoized menu item component +const MenuItem = React.memo(({ option, index, onMainEvent }: { option: any; index: number; onMainEvent?: (eventName: string) => void }) => { + const handleClick = useCallback(() => { + if (!option.disabled) { + // Trigger legacy onClick action for backward compatibility + if (option.onClick) { + option.onClick(); + } + // Trigger individual item event handlers + if (option.onEvent) { + option.onEvent("click"); + } + // Trigger the main column's event handler + if (onMainEvent) { + onMainEvent("click"); + } + } + }, [option.disabled, option.onClick, option.onEvent, onMainEvent]); + + return ( + + + + ); +}); + +MenuItem.displayName = 'MenuItem'; + // Update OptionItem to include event handlers const OptionItem = new MultiCompBuilder( { label: StringControl, onClick: ActionSelectorControlInContext, + onEvent: eventHandlerControl(LinksEventOptions), hidden: BoolCodeControl, disabled: BoolCodeControl, - onEvent: eventHandlerControl(LinksEventOptions), }, (props) => { return props; } ) .setPropertyViewFn((children) => { + // Check if there's a legacy action configured for this individual item + const hasLegacyAction = children.onClick.getView() && + typeof children.onClick.getView() === 'function' && + children.onClick.displayName() !== trans("eventHandler.incomplete"); + return ( <> {children.label.propertyView({ label: trans("label") })} - {children.onClick.propertyView({ + {hasLegacyAction && children.onClick.propertyView({ label: trans("table.action"), placement: "table", })} {hiddenPropertyView(children)} {disabledPropertyView(children)} - {/* {children.onEvent.propertyView()} */} + {children.onEvent.propertyView()} ); }) .build(); -// Memoized menu item component -const MenuItem = React.memo(({ option, index, onMainEvent }: { option: any; index: number; onMainEvent?: (eventName: string) => void }) => { - const handleClick = useCallback(() => { - if (!option.disabled) { - if (option.onClick) { - option.onClick(); - } - // if (option.onEvent) { - // option.onEvent("click"); - // } - // Trigger the main component's event handler - if (onMainEvent) { - onMainEvent("click"); - } - } - }, [option.disabled, option.onClick, option.onEvent, onMainEvent]); - - return ( - - - - ); -}); - -MenuItem.displayName = 'MenuItem'; - // Memoized menu component const LinksMenu = React.memo(({ options, onEvent }: { options: any[]; onEvent?: (eventName: string) => void }) => { const mountedRef = useRef(true); @@ -134,7 +141,7 @@ export const ColumnLinksComp = (function () { options: manualOptionsControl(OptionItem, { initOptions: [{ label: trans("table.option1") }], }), - onEvent: eventHandlerControl(LinksEventOptions), + onEvent: eventHandlerControl(LinksEventOptions), // Main column level event handlers }; return new ColumnTypeCompBuilder( childrenMap, diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index 3d5096cc8..924f1ae0c 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -13,6 +13,7 @@ import React, { useCallback, useEffect, useMemo } from "react"; import { CSSProperties } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); @@ -31,10 +32,13 @@ export const ButtonTypeOptions = [ }, ] as const; +const ButtonEventOptions = [clickEvent] as const; + const childrenMap = { text: StringControl, buttonType: dropdownControl(ButtonTypeOptions, "primary"), onClick: ActionSelectorControlInContext, + onEvent: eventHandlerControl(ButtonEventOptions), loading: BoolCodeControl, disabled: BoolCodeControl, prefixIcon: IconControl, @@ -49,8 +53,11 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn { + // Trigger legacy onClick action for backward compatibility props.onClick?.(); - }, [props.onClick]); + // Trigger new event handlers + props.onEvent?.("click"); + }, [props.onClick, props.onEvent]); const buttonStyle = useMemo(() => ({ margin: 0, @@ -82,29 +89,37 @@ export const ButtonComp = (function () { (props) => , (nodeValue) => nodeValue.text.value ) - .setPropertyViewFn((children) => ( - <> - {children.text.propertyView({ - label: trans("table.columnValue"), - tooltip: ColumnValueTooltip, - })} - {children.prefixIcon.propertyView({ - label: trans("button.prefixIcon"), - })} - {children.suffixIcon.propertyView({ - label: trans("button.suffixIcon"), - })} - {children.buttonType.propertyView({ - label: trans("table.type"), - radioButton: true, - })} - {loadingPropertyView(children)} - {disabledPropertyView(children)} - {children.onClick.propertyView({ - label: trans("table.action"), - placement: "table", - })} - - )) + .setPropertyViewFn((children) => { + // Check if there's a legacy action configured + const hasLegacyAction = children.onClick.getView() && + typeof children.onClick.getView() === 'function' && + children.onClick.displayName() !== trans("eventHandler.incomplete"); + + return ( + <> + {children.text.propertyView({ + label: trans("table.columnValue"), + tooltip: ColumnValueTooltip, + })} + {children.prefixIcon.propertyView({ + label: trans("button.prefixIcon"), + })} + {children.suffixIcon.propertyView({ + label: trans("button.suffixIcon"), + })} + {children.buttonType.propertyView({ + label: trans("table.type"), + radioButton: true, + })} + {loadingPropertyView(children)} + {disabledPropertyView(children)} + {children.onEvent.propertyView()} + {hasLegacyAction && children.onClick.propertyView({ + label: trans("table.action"), + placement: "table", + })} + + ); + }) .build(); })(); From fafab639ea906519b36954c190b52d23b7ba63a3 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 13 Jun 2025 23:10:47 +0500 Subject: [PATCH 34/51] fixed localstorage issues on accessing it after navigation to other app --- .../src/comps/hooks/localStorageComp.ts | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/client/packages/lowcoder/src/comps/hooks/localStorageComp.ts b/client/packages/lowcoder/src/comps/hooks/localStorageComp.ts index 9a5b43411..252ecaf1b 100644 --- a/client/packages/lowcoder/src/comps/hooks/localStorageComp.ts +++ b/client/packages/lowcoder/src/comps/hooks/localStorageComp.ts @@ -14,35 +14,39 @@ const LocalStorageCompBase = withViewFn( simpleMultiComp({ values: stateComp({}) }), (comp) => { const originStore = localStorage.getItem(APP_STORE_NAMESPACE) || "{}"; - - const parseStore = useMemo(() => { - try { - return JSON.parse(originStore); - } catch (e) { - log.error("application local storage invalid"); - return {}; - } - }, [originStore]); - const handleStorageUpdate = useCallback(() => { - try { + let parseStore = {}; + try { + parseStore = JSON.parse(originStore); + } catch (e) { + log.error("application local storage invalid"); + } + + useEffect(() => { + const value = comp.children.values.value; + if (!isEqual(value, parseStore)) { comp.children.values.dispatchChangeValueAction(parseStore); - } catch (e) { - log.error("Failed to parse localStorage:", e); } - }, [parseStore, comp.children.values]); + }, [parseStore]); useEffect(() => { - // Add listener on mount - window.addEventListener("lowcoder-localstorage-updated", handleStorageUpdate); + const handler = () => { + try { + const raw = localStorage.getItem(APP_STORE_NAMESPACE) || "{}"; + const parsed = JSON.parse(raw); + comp.children.values.dispatchChangeValueAction(parsed); + } catch (e) { + log.error("Failed to parse localStorage:", e); + } + }; - // Run once on mount to initialize - handleStorageUpdate(); + // Add listener on mount + window.addEventListener("lowcoder-localstorage-updated", handler); return () => { - window.removeEventListener("lowcoder-localstorage-updated", handleStorageUpdate); + window.removeEventListener("lowcoder-localstorage-updated", handler); }; - }, [handleStorageUpdate]); + }, []); return null; } From 6eb88427828efeb181f02a03a734378c04baf961 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 13 Jun 2025 23:47:54 +0500 Subject: [PATCH 35/51] added migration to handle old action handlers in table's button column type --- .../column/simpleColumnTypeComps.tsx | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index a7c79032a..4fb5b7a85 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -14,7 +14,21 @@ import { CSSProperties } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { migrateOldData } from "@lowcoder-ee/comps/generators/simpleGenerators"; +export const fixOldActionData = (oldData: any) => { + if (!oldData) return oldData; + if (Boolean(oldData.onClick)) { + return { + ...oldData, + onClick: [{ + name: "click", + handler: oldData.onClick, + }], + }; + } + return oldData; +} export const ColumnValueTooltip = trans("table.columnValueTooltip"); export const ButtonTypeOptions = [ @@ -38,7 +52,7 @@ const childrenMap = { text: StringControl, buttonType: dropdownControl(ButtonTypeOptions, "primary"), onEvent: eventHandlerControl(ButtonEventOptions), - onClick: ActionSelectorControlInContext, + onClick: eventHandlerControl(ButtonEventOptions), //ActionSelectorControlInContext, loading: BoolCodeControl, disabled: BoolCodeControl, prefixIcon: IconControl, @@ -53,8 +67,7 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn { - props.onClick?.(); - // props.onEvent?.("click"); + props.onClick?.("click"); }, [props.onClick, props.onEvent]); const buttonStyle = useMemo(() => ({ @@ -81,7 +94,7 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn , @@ -105,12 +118,10 @@ export const ButtonComp = (function () { })} {loadingPropertyView(children)} {disabledPropertyView(children)} - {/* {children.onEvent.propertyView()} */} - {children.onClick.propertyView({ - label: trans("table.action"), - placement: "table", - })} + {children.onClick.propertyView()} )) .build(); })(); + +export const ButtonComp = migrateOldData(ButtonCompTmp, fixOldActionData); From 4022c65084312e347012ddd903e8f785d8289274 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 13 Jun 2025 23:51:56 +0500 Subject: [PATCH 36/51] removed unused code --- .../comps/comps/tableComp/column/simpleColumnTypeComps.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index 4fb5b7a85..0abadf38f 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -51,8 +51,7 @@ const ButtonEventOptions = [clickEvent] as const; const childrenMap = { text: StringControl, buttonType: dropdownControl(ButtonTypeOptions, "primary"), - onEvent: eventHandlerControl(ButtonEventOptions), - onClick: eventHandlerControl(ButtonEventOptions), //ActionSelectorControlInContext, + onClick: eventHandlerControl(ButtonEventOptions), loading: BoolCodeControl, disabled: BoolCodeControl, prefixIcon: IconControl, @@ -68,7 +67,7 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn { props.onClick?.("click"); - }, [props.onClick, props.onEvent]); + }, [props.onClick]); const buttonStyle = useMemo(() => ({ margin: 0, From e36129922ffd92c29642b8037999f6e5776a5da4 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Sat, 14 Jun 2025 00:01:11 +0500 Subject: [PATCH 37/51] fix simple column type (btn) --- .../column/simpleColumnTypeComps.tsx | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index 924f1ae0c..0abadf38f 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -14,7 +14,21 @@ import { CSSProperties } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { migrateOldData } from "@lowcoder-ee/comps/generators/simpleGenerators"; +export const fixOldActionData = (oldData: any) => { + if (!oldData) return oldData; + if (Boolean(oldData.onClick)) { + return { + ...oldData, + onClick: [{ + name: "click", + handler: oldData.onClick, + }], + }; + } + return oldData; +} export const ColumnValueTooltip = trans("table.columnValueTooltip"); export const ButtonTypeOptions = [ @@ -37,8 +51,7 @@ const ButtonEventOptions = [clickEvent] as const; const childrenMap = { text: StringControl, buttonType: dropdownControl(ButtonTypeOptions, "primary"), - onClick: ActionSelectorControlInContext, - onEvent: eventHandlerControl(ButtonEventOptions), + onClick: eventHandlerControl(ButtonEventOptions), loading: BoolCodeControl, disabled: BoolCodeControl, prefixIcon: IconControl, @@ -53,11 +66,8 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn { - // Trigger legacy onClick action for backward compatibility - props.onClick?.(); - // Trigger new event handlers - props.onEvent?.("click"); - }, [props.onClick, props.onEvent]); + props.onClick?.("click"); + }, [props.onClick]); const buttonStyle = useMemo(() => ({ margin: 0, @@ -83,43 +93,34 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn , (nodeValue) => nodeValue.text.value ) - .setPropertyViewFn((children) => { - // Check if there's a legacy action configured - const hasLegacyAction = children.onClick.getView() && - typeof children.onClick.getView() === 'function' && - children.onClick.displayName() !== trans("eventHandler.incomplete"); - - return ( - <> - {children.text.propertyView({ - label: trans("table.columnValue"), - tooltip: ColumnValueTooltip, - })} - {children.prefixIcon.propertyView({ - label: trans("button.prefixIcon"), - })} - {children.suffixIcon.propertyView({ - label: trans("button.suffixIcon"), - })} - {children.buttonType.propertyView({ - label: trans("table.type"), - radioButton: true, - })} - {loadingPropertyView(children)} - {disabledPropertyView(children)} - {children.onEvent.propertyView()} - {hasLegacyAction && children.onClick.propertyView({ - label: trans("table.action"), - placement: "table", - })} - - ); - }) + .setPropertyViewFn((children) => ( + <> + {children.text.propertyView({ + label: trans("table.columnValue"), + tooltip: ColumnValueTooltip, + })} + {children.prefixIcon.propertyView({ + label: trans("button.prefixIcon"), + })} + {children.suffixIcon.propertyView({ + label: trans("button.suffixIcon"), + })} + {children.buttonType.propertyView({ + label: trans("table.type"), + radioButton: true, + })} + {loadingPropertyView(children)} + {disabledPropertyView(children)} + {children.onClick.propertyView()} + + )) .build(); })(); + +export const ButtonComp = migrateOldData(ButtonCompTmp, fixOldActionData); From 4a2f13bf5284be95129807d64df719fe3f00e5eb Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Sat, 14 Jun 2025 00:02:25 +0500 Subject: [PATCH 38/51] revert link column type --- .../column/columnTypeComps/columnLinkComp.tsx | 53 +++++++------------ 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx index 3ad4bf275..c82b7326a 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx @@ -11,16 +11,12 @@ import { disabledPropertyView } from "comps/utils/propertyUtils"; import styled, { css } from "styled-components"; import { styleControl } from "comps/controls/styleControl"; import { TableColumnLinkStyle } from "comps/controls/styleControlConstants"; -import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); -const LinkEventOptions = [clickEvent] as const; - const childrenMap = { text: StringControl, onClick: ActionSelectorControlInContext, - onEvent: eventHandlerControl(LinkEventOptions), disabled: BoolCodeControl, style: styleControl(TableColumnLinkStyle), }; @@ -37,16 +33,13 @@ const StyledLink = styled.a<{ $disabled: boolean }>` ${(props) => props.$disabled && disableCss}; `; -// Updated link component to handle both legacy and new event handlers -export const ColumnLink = React.memo(({ disabled, label, onClick, onEvent }: { disabled: boolean; label: string; onClick?: () => void; onEvent?: (eventName: string) => void }) => { +// Memoized link component +export const ColumnLink = React.memo(({ disabled, label, onClick }: { disabled: boolean; label: string; onClick?: () => void }) => { const handleClick = useCallback(() => { - if (!disabled) { - // Trigger legacy onClick action for backward compatibility - onClick?.(); - // Trigger new event handlers - onEvent?.("click"); + if (!disabled && onClick) { + onClick(); } - }, [disabled, onClick, onEvent]); + }, [disabled, onClick]); return ( { const value = props.changeValue ?? getBaseValue(props, dispatch); - return ; + return ; }, (nodeValue) => nodeValue.text.value, getBaseValue @@ -125,27 +118,19 @@ export const LinkComp = (function () { onChangeEnd={props.onChangeEnd} /> )) - .setPropertyViewFn((children) => { - // Check if there's a legacy action configured - const hasLegacyAction = children.onClick.getView() && - typeof children.onClick.getView() === 'function' && - children.onClick.displayName() !== trans("eventHandler.incomplete"); - - return ( - <> - {children.text.propertyView({ - label: trans("table.columnValue"), - tooltip: ColumnValueTooltip, - })} - {disabledPropertyView(children)} - {children.onEvent.propertyView()} - {hasLegacyAction && children.onClick.propertyView({ - label: trans("table.action"), - placement: "table", - })} - - ); - }) + .setPropertyViewFn((children) => ( + <> + {children.text.propertyView({ + label: trans("table.columnValue"), + tooltip: ColumnValueTooltip, + })} + {disabledPropertyView(children)} + {children.onClick.propertyView({ + label: trans("table.action"), + placement: "table", + })} + + )) .setStylePropertyViewFn((children) => ( <> {children.style.getPropertyView()} From 28d37435ce51b095493c98c73828d864017bde6a Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Sat, 14 Jun 2025 00:08:53 +0500 Subject: [PATCH 39/51] add backward compatibility for link type --- .../column/columnTypeComps/columnLinkComp.tsx | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx index c82b7326a..a82a760e7 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx @@ -4,19 +4,23 @@ import { ColumnTypeCompBuilder, ColumnTypeViewFn, } from "comps/comps/tableComp/column/columnTypeCompBuilder"; -import { ActionSelectorControlInContext } from "comps/controls/actionSelector/actionSelectorControl"; import { BoolCodeControl, StringControl } from "comps/controls/codeControl"; import { trans } from "i18n"; import { disabledPropertyView } from "comps/utils/propertyUtils"; import styled, { css } from "styled-components"; import { styleControl } from "comps/controls/styleControl"; import { TableColumnLinkStyle } from "comps/controls/styleControlConstants"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { migrateOldData } from "@lowcoder-ee/comps/generators/simpleGenerators"; +import { fixOldActionData } from "comps/comps/tableComp/column/simpleColumnTypeComps"; export const ColumnValueTooltip = trans("table.columnValueTooltip"); +const LinkEventOptions = [clickEvent] as const; + const childrenMap = { text: StringControl, - onClick: ActionSelectorControlInContext, + onClick: eventHandlerControl(LinkEventOptions), disabled: BoolCodeControl, style: styleControl(TableColumnLinkStyle), }; @@ -34,11 +38,10 @@ const StyledLink = styled.a<{ $disabled: boolean }>` `; // Memoized link component -export const ColumnLink = React.memo(({ disabled, label, onClick }: { disabled: boolean; label: string; onClick?: () => void }) => { +export const ColumnLink = React.memo(({ disabled, label, onClick }: { disabled: boolean; label: string; onClick?: (eventName: string) => void }) => { const handleClick = useCallback(() => { - if (!disabled && onClick) { - onClick(); - } + if (disabled) return; + onClick?.("click"); }, [disabled, onClick]); return ( @@ -101,7 +104,7 @@ LinkEdit.displayName = 'LinkEdit'; const getBaseValue: ColumnTypeViewFn = (props) => props.text; -export const LinkComp = (function () { +const LinkCompTmp = (function () { return new ColumnTypeCompBuilder( childrenMap, (props, dispatch) => { @@ -125,10 +128,7 @@ export const LinkComp = (function () { tooltip: ColumnValueTooltip, })} {disabledPropertyView(children)} - {children.onClick.propertyView({ - label: trans("table.action"), - placement: "table", - })} + {children.onClick.propertyView()} )) .setStylePropertyViewFn((children) => ( @@ -138,3 +138,5 @@ export const LinkComp = (function () { )) .build(); })(); + +export const LinkComp = migrateOldData(LinkCompTmp, fixOldActionData); From 304c2c24573b877b3b5ed64e714546d871e4f3e3 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Sat, 14 Jun 2025 00:09:52 +0500 Subject: [PATCH 40/51] revert links type --- .../columnTypeComps/columnLinksComp.tsx | 42 ++++--------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx index 641c9adf9..4ecd308dd 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx @@ -10,7 +10,6 @@ import { trans } from "i18n"; import styled from "styled-components"; import { ColumnLink } from "comps/comps/tableComp/column/columnTypeComps/columnLinkComp"; import { LightActiveTextColor, PrimaryColor } from "constants/style"; -import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; const MenuLinkWrapper = styled.div` > a { @@ -38,26 +37,13 @@ const MenuWrapper = styled.div` } `; -const LinksEventOptions = [clickEvent] as const; - // Memoized menu item component -const MenuItem = React.memo(({ option, index, onMainEvent }: { option: any; index: number; onMainEvent?: (eventName: string) => void }) => { +const MenuItem = React.memo(({ option, index }: { option: any; index: number }) => { const handleClick = useCallback(() => { - if (!option.disabled) { - // Trigger legacy onClick action for backward compatibility - if (option.onClick) { - option.onClick(); - } - // Trigger individual item event handlers - if (option.onEvent) { - option.onEvent("click"); - } - // Trigger the main column's event handler - if (onMainEvent) { - onMainEvent("click"); - } + if (!option.disabled && option.onClick) { + option.onClick(); } - }, [option.disabled, option.onClick, option.onEvent, onMainEvent]); + }, [option.disabled, option.onClick]); return ( @@ -72,12 +58,10 @@ const MenuItem = React.memo(({ option, index, onMainEvent }: { option: any; inde MenuItem.displayName = 'MenuItem'; -// Update OptionItem to include event handlers const OptionItem = new MultiCompBuilder( { label: StringControl, onClick: ActionSelectorControlInContext, - onEvent: eventHandlerControl(LinksEventOptions), hidden: BoolCodeControl, disabled: BoolCodeControl, }, @@ -86,28 +70,22 @@ const OptionItem = new MultiCompBuilder( } ) .setPropertyViewFn((children) => { - // Check if there's a legacy action configured for this individual item - const hasLegacyAction = children.onClick.getView() && - typeof children.onClick.getView() === 'function' && - children.onClick.displayName() !== trans("eventHandler.incomplete"); - return ( <> {children.label.propertyView({ label: trans("label") })} - {hasLegacyAction && children.onClick.propertyView({ + {children.onClick.propertyView({ label: trans("table.action"), placement: "table", })} {hiddenPropertyView(children)} {disabledPropertyView(children)} - {children.onEvent.propertyView()} ); }) .build(); // Memoized menu component -const LinksMenu = React.memo(({ options, onEvent }: { options: any[]; onEvent?: (eventName: string) => void }) => { +const LinksMenu = React.memo(({ options }: { options: any[] }) => { const mountedRef = useRef(true); // Cleanup on unmount @@ -122,9 +100,9 @@ const LinksMenu = React.memo(({ options, onEvent }: { options: any[]; onEvent?: .filter((o) => !o.hidden) .map((option, index) => ({ key: index, - label: + label: })), - [options, onEvent] + [options] ); return ( @@ -141,12 +119,11 @@ export const ColumnLinksComp = (function () { options: manualOptionsControl(OptionItem, { initOptions: [{ label: trans("table.option1") }], }), - onEvent: eventHandlerControl(LinksEventOptions), // Main column level event handlers }; return new ColumnTypeCompBuilder( childrenMap, (props) => { - return ; + return ; }, () => "" ) @@ -156,7 +133,6 @@ export const ColumnLinksComp = (function () { newOptionLabel: trans("table.option"), title: trans("table.optionList"), })} - {children.onEvent.propertyView()} )) .build(); From 62285ce59bb8a9df18f25e70d19106c4b375ff02 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Sat, 14 Jun 2025 00:14:39 +0500 Subject: [PATCH 41/51] add backward compatibility for links type --- .../columnTypeComps/columnLinksComp.tsx | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx index 4ecd308dd..e707eab43 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinksComp.tsx @@ -1,7 +1,6 @@ import React, { useState, useRef, useEffect, useCallback, useMemo } from "react"; import { default as Menu } from "antd/es/menu"; import { ColumnTypeCompBuilder } from "comps/comps/tableComp/column/columnTypeCompBuilder"; -import { ActionSelectorControlInContext } from "comps/controls/actionSelector/actionSelectorControl"; import { BoolCodeControl, StringControl } from "comps/controls/codeControl"; import { manualOptionsControl } from "comps/controls/optionsControl"; import { MultiCompBuilder } from "comps/generators"; @@ -10,6 +9,9 @@ import { trans } from "i18n"; import styled from "styled-components"; import { ColumnLink } from "comps/comps/tableComp/column/columnTypeComps/columnLinkComp"; import { LightActiveTextColor, PrimaryColor } from "constants/style"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { migrateOldData } from "@lowcoder-ee/comps/generators/simpleGenerators"; +import { fixOldActionData } from "comps/comps/tableComp/column/simpleColumnTypeComps"; const MenuLinkWrapper = styled.div` > a { @@ -37,11 +39,13 @@ const MenuWrapper = styled.div` } `; +const LinkEventOptions = [clickEvent] as const; + // Memoized menu item component const MenuItem = React.memo(({ option, index }: { option: any; index: number }) => { const handleClick = useCallback(() => { if (!option.disabled && option.onClick) { - option.onClick(); + option.onClick("click"); } }, [option.disabled, option.onClick]); @@ -58,10 +62,10 @@ const MenuItem = React.memo(({ option, index }: { option: any; index: number }) MenuItem.displayName = 'MenuItem'; -const OptionItem = new MultiCompBuilder( +const OptionItemTmp = new MultiCompBuilder( { label: StringControl, - onClick: ActionSelectorControlInContext, + onClick: eventHandlerControl(LinkEventOptions), hidden: BoolCodeControl, disabled: BoolCodeControl, }, @@ -73,17 +77,16 @@ const OptionItem = new MultiCompBuilder( return ( <> {children.label.propertyView({ label: trans("label") })} - {children.onClick.propertyView({ - label: trans("table.action"), - placement: "table", - })} {hiddenPropertyView(children)} {disabledPropertyView(children)} + {children.onClick.propertyView()} ); }) .build(); +const OptionItem = migrateOldData(OptionItemTmp, fixOldActionData); + // Memoized menu component const LinksMenu = React.memo(({ options }: { options: any[] }) => { const mountedRef = useRef(true); @@ -114,7 +117,7 @@ const LinksMenu = React.memo(({ options }: { options: any[] }) => { LinksMenu.displayName = 'LinksMenu'; -export const ColumnLinksComp = (function () { +const ColumnLinksCompTmp = (function () { const childrenMap = { options: manualOptionsControl(OptionItem, { initOptions: [{ label: trans("table.option1") }], @@ -137,3 +140,5 @@ export const ColumnLinksComp = (function () { )) .build(); })(); + +export const ColumnLinksComp = migrateOldData(ColumnLinksCompTmp, fixOldActionData); From df38a039fba78c0bd034890e474921bb372c2437 Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Mon, 16 Jun 2025 11:47:55 +0500 Subject: [PATCH 42/51] Optimizations - Added hook for click event handlers --- .../lowcoder/src/comps/comps/avatar.tsx | 6 ++- .../lowcoder/src/comps/comps/avatarGroup.tsx | 6 ++- .../src/comps/comps/buttonComp/buttonComp.tsx | 3 +- .../comps/buttonComp/floatButtonComp.tsx | 54 ++++++++++++++----- .../comps/comps/commentComp/commentComp.tsx | 8 +-- .../comps/comps/containerComp/cardComp.tsx | 17 ++++-- .../lowcoder/src/comps/comps/iconComp.tsx | 5 +- .../lowcoder/src/comps/comps/imageComp.tsx | 6 ++- .../comps/comps/meetingComp/controlButton.tsx | 7 ++- .../columnTypeComps/ColumnNumberComp.tsx | 6 ++- .../columnTypeComps/columnAvatarsComp.tsx | 6 ++- .../columnTypeComps/columnDropdownComp.tsx | 13 ++--- .../column/columnTypeComps/columnLinkComp.tsx | 18 +++---- .../columnTypeComps/columnLinksComp.tsx | 10 +--- .../column/columnTypeComps/simpleTextComp.tsx | 6 ++- .../column/simpleColumnTypeComps.tsx | 12 ++--- .../lowcoder/src/comps/comps/textComp.tsx | 6 ++- .../comps/comps/timelineComp/timelineComp.tsx | 6 ++- ...ndler.tsx => useCompClickEventHandler.tsx} | 0 19 files changed, 120 insertions(+), 75 deletions(-) rename client/packages/lowcoder/src/comps/utils/{componentClickHandler.tsx => useCompClickEventHandler.tsx} (100%) diff --git a/client/packages/lowcoder/src/comps/comps/avatar.tsx b/client/packages/lowcoder/src/comps/comps/avatar.tsx index c9d5b9602..aac9949a0 100644 --- a/client/packages/lowcoder/src/comps/comps/avatar.tsx +++ b/client/packages/lowcoder/src/comps/comps/avatar.tsx @@ -35,7 +35,7 @@ import { BadgeBasicSection, badgeChildren } from "./badgeComp/badgeConstants"; import { DropdownOptionControl } from "../controls/optionsControl"; import { ReactElement, useContext, useEffect } from "react"; import { CompNameContext, EditorContext } from "../editorState"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; const AvatarWrapper = styled(Avatar) ` @@ -143,6 +143,8 @@ const childrenMap = { const AvatarView = (props: RecordConstructorToView) => { const { shape, title, src, iconSize } = props; const comp = useContext(EditorContext).getUICompByName(useContext(CompNameContext)); + const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent}) + // const eventsCount = comp ? Object.keys(comp?.children.comp.children.onEvent.children).length : 0; const hasIcon = props.options.findIndex((option) => (option.prefixIcon as ReactElement)?.props.value) > -1; const items = props.options @@ -184,7 +186,7 @@ const AvatarView = (props: RecordConstructorToView) => { shape={shape} $style={props.avatarStyle} src={src.value} - onClick={ComponentClickHandler({onEvent: props.onEvent})} + onClick={() => handleClickEvent()} > {title.value} diff --git a/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx b/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx index 9181c5c21..f370a4ef9 100644 --- a/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx +++ b/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx @@ -19,7 +19,7 @@ import { optionsControl } from "../controls/optionsControl"; import { BoolControl } from "../controls/boolControl"; import { dropdownControl } from "../controls/dropdownControl"; import { JSONObject } from "util/jsonTypes"; -import { ComponentClickHandler } from "../utils/componentClickHandler"; +import { useCompClickEventHandler } from "../utils/useCompClickEventHandler"; const MacaroneList = [ '#fde68a', @@ -106,6 +106,8 @@ const childrenMap = { }; const AvatarGroupView = (props: RecordConstructorToView & { dispatch: (action: CompAction) => void; }) => { + const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent}) + return ( & { }} size={props.avatarSize} onClick={() => { - ComponentClickHandler({onEvent: props.onEvent})(); + handleClickEvent(); props.dispatch(changeChildAction("currentAvatar", item as JSONObject, false)); }} > diff --git a/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx b/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx index 1e44b5bbc..70a8de5d8 100644 --- a/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx @@ -29,7 +29,7 @@ import { AnimationStyle } from "@lowcoder-ee/comps/controls/styleControlConstant import { styleControl } from "@lowcoder-ee/comps/controls/styleControl"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; -import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; const FormLabel = styled(CommonBlueLabel)` font-size: 13px; @@ -195,7 +195,6 @@ const ButtonView = React.memo((props: ToViewReturn) => { try { if (isDefault(props.type)) { - // ComponentClickHandler({onEvent: props.onEvent})() handleClickEvent(); } else { submitForm(editorState, props.form); diff --git a/client/packages/lowcoder/src/comps/comps/buttonComp/floatButtonComp.tsx b/client/packages/lowcoder/src/comps/comps/buttonComp/floatButtonComp.tsx index 71fbbba20..358a1e6ff 100644 --- a/client/packages/lowcoder/src/comps/comps/buttonComp/floatButtonComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/buttonComp/floatButtonComp.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { RecordConstructorToView } from "lowcoder-core"; import { BoolControl } from "comps/controls/boolControl"; import { stringExposingStateControl } from "comps/controls/codeStateControl"; @@ -16,8 +17,7 @@ import { IconControl } from "comps/controls/iconControl"; import styled from "styled-components"; import { ButtonEventHandlerControl } from "comps/controls/eventHandlerControl"; import { manualOptionsControl } from "comps/controls/optionsControl"; -import { useContext, useEffect } from "react"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; const StyledFloatButton = styled(FloatButton)<{ $animationStyle: AnimationStyleType; @@ -99,21 +99,51 @@ const childrenMap = { dot: BoolControl, }; +const FloatButtonItem = React.memo(({ + button, + animationStyle, + badgeStyle, + buttonTheme, + shape, + dot +}: { + button: any; + animationStyle: AnimationStyleType; + badgeStyle: BadgeStyleType; + buttonTheme: 'primary' | 'default'; + shape: 'circle' | 'square'; + dot: boolean; +}) => { + const handleClickEvent = useCompClickEventHandler({ onEvent: button.onEvent }); + + return ( + + ); +}); + const FloatButtonView = (props: RecordConstructorToView) => { const renderButton = (button: any, onlyOne?: boolean) => { return !button?.hidden ? ( - ) - : '' + dot={props.dot} + /> + ) : ''; } return ( diff --git a/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx b/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx index 8e7ab8dfc..c20cb793b 100644 --- a/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx @@ -67,7 +67,7 @@ import dayjs from "dayjs"; // import "dayjs/locale/zh-cn"; import { getInitialsAndColorCode } from "util/stringUtils"; import { default as CloseOutlined } from "@ant-design/icons/CloseOutlined"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; dayjs.extend(relativeTime); // dayjs.locale("zh-cn"); @@ -136,6 +136,8 @@ const CommentCompBase = ( const [commentListData, setCommentListData] = useState([]); const [prefix, setPrefix] = useState("@"); const [context, setContext] = useState(""); + const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent}) + // Integrate the comment list with the names in the original mention list const mergeAllMentionList = (mentionList: any) => { setMentionList( @@ -177,7 +179,7 @@ const CommentCompBase = ( const generateCommentAvatar = (item: commentDataTYPE) => { return ( handleClickEvent()} // If there is an avatar, no background colour is set, and if displayName is not null, displayName is called using getInitialsAndColorCode style={{ backgroundColor: item?.user?.avatar @@ -294,7 +296,7 @@ const CommentCompBase = ( avatar={generateCommentAvatar(item)} title={
handleClickEvent()} > {item?.user?.name} (null); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); + const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent}) + const actionHandlers = props.actionOptions.map(item => ({ + ...item, + clickHandler: useCompClickEventHandler({onEvent: item.onEvent}) + })); + useEffect(() => { if (height && width) { onResize(); @@ -233,7 +239,7 @@ export const ContainerBaseComp = (function () { $cardType={props.cardType} onMouseEnter={() => props.onEvent('focus')} onMouseLeave={() => props.onEvent('blur')} - onClick={ComponentClickHandler({onEvent: props.onEvent})} + onClick={() => handleClickEvent()} > } actions={props.cardType == 'common' && props.showActionIcon ? - props.actionOptions.filter(item => !item.hidden).map(item => { + actionHandlers.filter(item => !item.hidden).map(item => { return ( { + e.stopPropagation() + item.clickHandler() + }} disabled={item.disabled} $style={props.style} > diff --git a/client/packages/lowcoder/src/comps/comps/iconComp.tsx b/client/packages/lowcoder/src/comps/comps/iconComp.tsx index 12a3da198..6e49b429a 100644 --- a/client/packages/lowcoder/src/comps/comps/iconComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/iconComp.tsx @@ -33,7 +33,7 @@ import { useContext } from "react"; import { EditorContext } from "comps/editorState"; import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl"; import { dropdownControl } from "../controls/dropdownControl"; -import { ComponentClickHandler } from "../utils/componentClickHandler"; +import { useCompClickEventHandler } from "../utils/useCompClickEventHandler"; const Container = styled.div<{ $sourceMode: string; @@ -96,6 +96,7 @@ const IconView = (props: RecordConstructorToView) => { const conRef = useRef(null); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); + const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent}) useEffect(() => { if (height && width) { @@ -136,7 +137,7 @@ const IconView = (props: RecordConstructorToView) => { $sourceMode={props.sourceMode} $animationStyle={props.animationStyle} style={style} - onClick={ComponentClickHandler({onEvent: props.onEvent})} + onClick={() => handleClickEvent()} > { props.sourceMode === 'standard' ? (props.icon || '') diff --git a/client/packages/lowcoder/src/comps/comps/imageComp.tsx b/client/packages/lowcoder/src/comps/comps/imageComp.tsx index e82d2bab2..80d2ba77b 100644 --- a/client/packages/lowcoder/src/comps/comps/imageComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/imageComp.tsx @@ -38,7 +38,7 @@ import { StringControl } from "../controls/codeControl"; import { PositionControl } from "comps/controls/dropdownControl"; import { dropdownControl } from "../controls/dropdownControl"; import { AssetType, IconscoutControl } from "../controls/iconscoutControl"; -import { ComponentClickHandler } from "../utils/componentClickHandler"; +import { useCompClickEventHandler } from "../utils/useCompClickEventHandler"; const Container = styled.div<{ $style: ImageStyleType | undefined, @@ -125,6 +125,8 @@ const ContainerImg = (props: RecordConstructorToView) => { const conRef = useRef(null); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); + const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent}) + const imgOnload = (img: HTMLImageElement) => { img.onload = function () { @@ -213,7 +215,7 @@ const ContainerImg = (props: RecordConstructorToView) => { draggable={false} preview={props.supportPreview ? {src: props.previewSrc || props.src.value } : false} fallback={DEFAULT_IMG_URL} - onClick={ComponentClickHandler({onEvent: props.onEvent})} + onClick={() => handleClickEvent()} />
diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx index 90da85e8c..0445c9403 100644 --- a/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx +++ b/client/packages/lowcoder/src/comps/comps/meetingComp/controlButton.tsx @@ -41,7 +41,7 @@ import { useResizeDetector } from "react-resize-detector"; import { useContext } from "react"; import { Tooltip } from "antd"; import { AssetType, IconscoutControl } from "@lowcoder-ee/comps/controls/iconscoutControl"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; const Container = styled.div<{ $style: any }>` height: 100%; @@ -213,6 +213,9 @@ let ButtonTmpComp = (function () { const imgRef = useRef(null); const conRef = useRef(null); + + const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent}) + useEffect(() => { if (height && width) { onResize(); @@ -286,7 +289,7 @@ let ButtonTmpComp = (function () { } onClick={() => isDefault(props.type) - ? ComponentClickHandler({onEvent: props.onEvent})() + ? handleClickEvent() : submitForm(editorState, props.form) } > diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx index af77fa472..619b42674 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx @@ -10,7 +10,7 @@ import styled from "styled-components"; import { IconControl } from "comps/controls/iconControl"; import { hasIcon } from "comps/utils"; import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; const InputNumberWrapper = styled.div` .ant-input-number { @@ -71,6 +71,8 @@ type NumberEditProps = { }; const ColumnNumberView = React.memo((props: NumberViewProps) => { + const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent ?? (() => {})}) + const formattedValue = useMemo(() => { let result = !props.float ? Math.floor(props.value) : props.value; if (props.float) { @@ -80,7 +82,7 @@ const ColumnNumberView = React.memo((props: NumberViewProps) => { }, [props.value, props.float, props.precision]); const handleClick = useCallback(() => { - props.onEvent && ComponentClickHandler({onEvent: props.onEvent})() + handleClickEvent() }, [props.onEvent]); return ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx index a8f864a77..f85863e01 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx @@ -17,7 +17,7 @@ import { optionsControl } from "comps/controls/optionsControl"; import { BoolControl } from "comps/controls/boolControl"; import { dropdownControl } from "comps/controls/dropdownControl"; import { JSONObject } from "util/jsonTypes"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; const MacaroneList = [ '#fde68a', @@ -100,6 +100,8 @@ const MemoizedAvatar = React.memo(({ onItemEvent?: (event: string) => void; }) => { const mountedRef = useRef(true); + const handleClickEvent = useCompClickEventHandler({onEvent}) + // Cleanup on unmount useEffect(() => { @@ -117,7 +119,7 @@ const MemoizedAvatar = React.memo(({ } // Then trigger main component event - ComponentClickHandler({onEvent})() + handleClickEvent() }, [onEvent, onItemEvent]); return ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx index cb426e296..7dd3bf242 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx @@ -15,8 +15,8 @@ import { ButtonStyle } from "comps/controls/styleControlConstants"; import { Button100 } from "comps/comps/buttonComp/buttonCompConstants"; import styled from "styled-components"; import { ButtonType } from "antd/es/button"; -import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; const StyledButton = styled(Button100)` display: flex; @@ -30,7 +30,7 @@ const StyledIconWrapper = styled(IconWrapper)` margin: 0; `; -const DropdownEventOptions = [clickEvent, doubleClickEvent] as const; +const DropdownEventOptions = [clickEvent] as const; const childrenMap = { buttonType: dropdownControl(ButtonTypeOptions, "primary"), @@ -44,8 +44,9 @@ const childrenMap = { const getBaseValue: ColumnTypeViewFn = (props) => props.label; // Memoized dropdown menu component -const DropdownMenu = React.memo(({ items, options, onEvent }: { items: any[]; options: any[]; onEvent?: (eventName: string) => void }) => { +const DropdownMenu = React.memo(({ items, options, onEvent }: { items: any[]; options: any[]; onEvent: (eventName: string) => void }) => { const mountedRef = useRef(true); + const handleClickEvent = useCompClickEventHandler({onEvent}) // Cleanup on unmount useEffect(() => { @@ -60,7 +61,7 @@ const DropdownMenu = React.memo(({ items, options, onEvent }: { items: any[]; op const itemIndex = options.findIndex(option => option.label === item?.label); item && options[itemIndex]?.onEvent("click"); // Also trigger the dropdown's main event handler - onEvent && ComponentClickHandler({onEvent})(); + handleClickEvent(); }, [items, options, onEvent]); const handleMouseDown = useCallback((e: React.MouseEvent) => { @@ -128,7 +129,7 @@ const DropdownView = React.memo((props: { const buttonStyle = useStyle(ButtonStyle); const menu = useMemo(() => ( - + {})} /> ), [items, props.options, props.onEvent]); return ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx index 3be2c5db9..e93b3082a 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnLinkComp.tsx @@ -11,7 +11,7 @@ import styled, { css } from "styled-components"; import { styleControl } from "comps/controls/styleControl"; import { TableColumnLinkStyle } from "comps/controls/styleControlConstants"; import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; import { migrateOldData } from "@lowcoder-ee/comps/generators/simpleGenerators"; import { fixOldActionData } from "comps/comps/tableComp/column/simpleColumnTypeComps"; @@ -39,19 +39,13 @@ const StyledLink = styled.a<{ $disabled: boolean }>` `; // Memoized link component -export const ColumnLink = React.memo(({ disabled, label, onClick }: { disabled: boolean; label: string; onClick?: (eventName: string) => void }) => { +export const ColumnLink = React.memo(({ disabled, label, onClick }: { disabled: boolean; label: string; onClick: (eventName: string) => void }) => { + const handleClickEvent = useCompClickEventHandler({onEvent: onClick}) const handleClick = useCallback(() => { - if (!disabled && onEvent) { - ComponentClickHandler({onEvent})(); + if (!disabled) { + handleClickEvent(); } - }, [disabled, onEvent]); - // if (disabled) return; - // onClick?.(); - // // onEvent?.("click"); - // }, [disabled, onClick, onEvent]); - // if (disabled) return; - // onClick?.("click"); - // }, [disabled, onClick]); + }, [disabled, onClick]); return ( { - const handleClick = useCallback(() => { - if (!option.disabled && option.onClick) { - option.onClick("click"); - } - }, [option.disabled, option.onClick]); - return ( ); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx index c346c22d9..80b8e8981 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx @@ -9,7 +9,7 @@ import React, { useCallback, useMemo } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; import { clickEvent, doubleClickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; import styled from "styled-components"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; const TextEventOptions = [clickEvent, doubleClickEvent] as const; @@ -50,8 +50,10 @@ interface SimpleTextEditViewProps { } const SimpleTextContent = React.memo(({ value, prefixIcon, suffixIcon, onEvent }: SimpleTextContentProps) => { + const handleClickEvent = useCompClickEventHandler({onEvent: onEvent ?? (() => {})}) + const handleClick = useCallback(() => { - onEvent && ComponentClickHandler({onEvent})() + handleClickEvent() }, [onEvent]); return ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index 2da1fbe7e..8ec51c6a1 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -14,8 +14,8 @@ import { CSSProperties } from "react"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "@lowcoder-ee/comps/generators/multi"; import { clickEvent, eventHandlerControl, doubleClickEvent } from "comps/controls/eventHandlerControl"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; import { migrateOldData } from "@lowcoder-ee/comps/generators/simpleGenerators"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; export const fixOldActionData = (oldData: any) => { if (!oldData) return oldData; @@ -65,15 +65,11 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn { - ComponentClickHandler({onEvent: props.onEvent}) - }, [props.onEvent]); - // props.onClick?.(); - // // props.onEvent?.("click"); - // }, [props.onClick, props.onEvent]); - // props.onClick?.("click"); - // }, [props.onClick]); + handleClickEvent() + }, [handleClickEvent]); const buttonStyle = useMemo(() => ({ margin: 0, diff --git a/client/packages/lowcoder/src/comps/comps/textComp.tsx b/client/packages/lowcoder/src/comps/comps/textComp.tsx index b41ab1b61..41b8ee09e 100644 --- a/client/packages/lowcoder/src/comps/comps/textComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/textComp.tsx @@ -25,7 +25,7 @@ import { NewChildren } from "../generators/uiCompBuilder"; import { RecordConstructorToComp } from "lowcoder-core"; import { ToViewReturn } from "../generators/multi"; import { BoolControl } from "../controls/boolControl"; -import { ComponentClickHandler } from "../utils/componentClickHandler"; +import { useCompClickEventHandler } from "../utils/useCompClickEventHandler"; const EventOptions = [clickEvent, doubleClickEvent] as const; @@ -225,8 +225,10 @@ const TextPropertyView = React.memo((props: { const TextView = React.memo((props: ToViewReturn) => { const value = props.text.value; + const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent}) + const handleClick = React.useCallback(() => { - props.onEvent && ComponentClickHandler({onEvent: props.onEvent})() + handleClickEvent() }, [props.onEvent]); const containerStyle = useMemo(() => ({ diff --git a/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx b/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx index ee5e16264..06e1ff1a4 100644 --- a/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/timelineComp/timelineComp.tsx @@ -50,7 +50,7 @@ import { convertTimeLineData } from "./timelineUtils"; import { default as Timeline } from "antd/es/timeline"; import { EditorContext } from "comps/editorState"; import { styled } from "styled-components"; -import { ComponentClickHandler } from "@lowcoder-ee/comps/utils/componentClickHandler"; +import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler"; const TimelineWrapper = styled.div<{ $style: TimeLineStyleType @@ -113,6 +113,8 @@ const TimelineComp = ( ) => { const { value, dispatch, style, mode, reverse, onEvent } = props; const [icons, setIcons] = useState([]); + const handleClickEvent = useCompClickEventHandler({onEvent}) + useEffect(() => { const loadIcons = async () => { const iconComponents = await Promise.all( @@ -143,7 +145,7 @@ const TimelineComp = ( e.preventDefault(); dispatch(changeChildAction("clickedObject", value, false)); dispatch(changeChildAction("clickedIndex", index, false)); - ComponentClickHandler({onEvent})() + handleClickEvent() }} // for responsiveness style={{ diff --git a/client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx b/client/packages/lowcoder/src/comps/utils/useCompClickEventHandler.tsx similarity index 100% rename from client/packages/lowcoder/src/comps/utils/componentClickHandler.tsx rename to client/packages/lowcoder/src/comps/utils/useCompClickEventHandler.tsx From b454a7ff53ffb5b621bfcc357649497a8e090b1b Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Mon, 16 Jun 2025 14:36:09 +0500 Subject: [PATCH 43/51] - Requested Changes --- client/packages/lowcoder/src/comps/comps/avatar.tsx | 2 +- .../lowcoder/src/comps/comps/commentComp/commentComp.tsx | 4 ++-- .../lowcoder/src/comps/comps/containerComp/cardComp.tsx | 2 +- client/packages/lowcoder/src/comps/comps/iconComp.tsx | 2 +- client/packages/lowcoder/src/comps/comps/imageComp.tsx | 2 +- .../column/columnTypeComps/columnAvatarsComp.tsx | 2 +- .../column/columnTypeComps/columnDropdownComp.tsx | 2 +- .../tableComp/column/columnTypeComps/columnSelectComp.tsx | 8 ++------ .../tableComp/column/columnTypeComps/simpleTextComp.tsx | 2 +- client/packages/lowcoder/src/comps/comps/textComp.tsx | 2 +- 10 files changed, 12 insertions(+), 16 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/avatar.tsx b/client/packages/lowcoder/src/comps/comps/avatar.tsx index aac9949a0..94e24d59a 100644 --- a/client/packages/lowcoder/src/comps/comps/avatar.tsx +++ b/client/packages/lowcoder/src/comps/comps/avatar.tsx @@ -186,7 +186,7 @@ const AvatarView = (props: RecordConstructorToView) => { shape={shape} $style={props.avatarStyle} src={src.value} - onClick={() => handleClickEvent()} + onClick={handleClickEvent} > {title.value} diff --git a/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx b/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx index c20cb793b..f3b14959c 100644 --- a/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/commentComp/commentComp.tsx @@ -179,7 +179,7 @@ const CommentCompBase = ( const generateCommentAvatar = (item: commentDataTYPE) => { return ( handleClickEvent()} + onClick={handleClickEvent} // If there is an avatar, no background colour is set, and if displayName is not null, displayName is called using getInitialsAndColorCode style={{ backgroundColor: item?.user?.avatar @@ -296,7 +296,7 @@ const CommentCompBase = ( avatar={generateCommentAvatar(item)} title={
handleClickEvent()} + onClick={handleClickEvent} > {item?.user?.name} props.onEvent('focus')} onMouseLeave={() => props.onEvent('blur')} - onClick={() => handleClickEvent()} + onClick={handleClickEvent} > ) => { $sourceMode={props.sourceMode} $animationStyle={props.animationStyle} style={style} - onClick={() => handleClickEvent()} + onClick={handleClickEvent} > { props.sourceMode === 'standard' ? (props.icon || '') diff --git a/client/packages/lowcoder/src/comps/comps/imageComp.tsx b/client/packages/lowcoder/src/comps/comps/imageComp.tsx index 80d2ba77b..8bc246a2b 100644 --- a/client/packages/lowcoder/src/comps/comps/imageComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/imageComp.tsx @@ -215,7 +215,7 @@ const ContainerImg = (props: RecordConstructorToView) => { draggable={false} preview={props.supportPreview ? {src: props.previewSrc || props.src.value } : false} fallback={DEFAULT_IMG_URL} - onClick={() => handleClickEvent()} + onClick={handleClickEvent} />
diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx index f85863e01..f02ee1994 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnAvatarsComp.tsx @@ -120,7 +120,7 @@ const MemoizedAvatar = React.memo(({ // Then trigger main component event handleClickEvent() - }, [onEvent, onItemEvent]); + }, [onItemEvent, handleClickEvent]); return ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx index 7dd3bf242..b78601a5f 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnDropdownComp.tsx @@ -62,7 +62,7 @@ const DropdownMenu = React.memo(({ items, options, onEvent }: { items: any[]; op item && options[itemIndex]?.onEvent("click"); // Also trigger the dropdown's main event handler handleClickEvent(); - }, [items, options, onEvent]); + }, [items, options, handleClickEvent]); const handleMouseDown = useCallback((e: React.MouseEvent) => { e.stopPropagation(); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx index 5c532836a..b54be8799 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnSelectComp.tsx @@ -6,7 +6,6 @@ import { IconControl } from "comps/controls/iconControl"; import { MultiCompBuilder } from "comps/generators"; import { optionsControl } from "comps/controls/optionsControl"; import { disabledPropertyView, hiddenPropertyView } from "comps/utils/propertyUtils"; - import { trans } from "i18n"; import { ColumnTypeCompBuilder, ColumnTypeViewFn } from "../columnTypeCompBuilder"; import { ColumnValueTooltip } from "../simpleColumnTypeComps"; @@ -146,14 +145,11 @@ const SelectEdit = React.memo((props: SelectEditProps) => { if (!mountedRef.current) return; props.onChange(val); setCurrentValue(val); + // Trigger the specific option's event handler const selectedOption = props.options.find(option => option.value === val); if (selectedOption?.onEvent) { - if (selectedOption.onEvent.isBind("click")) { - selectedOption.onEvent("click"); - } else if (selectedOption.onEvent.isBind("doubleClick")) { - selectedOption.onEvent("doubleClick"); - } + selectedOption.onEvent("click"); } // Also trigger the main component's event handler diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx index 80b8e8981..dcdffe390 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/simpleTextComp.tsx @@ -54,7 +54,7 @@ const SimpleTextContent = React.memo(({ value, prefixIcon, suffixIcon, onEvent } const handleClick = useCallback(() => { handleClickEvent() - }, [onEvent]); + }, [handleClickEvent]); return ( diff --git a/client/packages/lowcoder/src/comps/comps/textComp.tsx b/client/packages/lowcoder/src/comps/comps/textComp.tsx index 41b8ee09e..dcc5ccdb2 100644 --- a/client/packages/lowcoder/src/comps/comps/textComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/textComp.tsx @@ -229,7 +229,7 @@ const TextView = React.memo((props: ToViewReturn) => { const handleClick = React.useCallback(() => { handleClickEvent() - }, [props.onEvent]); + }, [handleClickEvent]); const containerStyle = useMemo(() => ({ justifyContent: props.horizontalAlignment, From 6d4cd531a9a7513681a34790250967d5c66b446f Mon Sep 17 00:00:00 2001 From: Thomasr Date: Mon, 16 Jun 2025 18:17:22 -0400 Subject: [PATCH 44/51] fixed orgmembers with searchMemberName and searchGroupId --- .../api/usermanagement/OrgApiService.java | 2 + .../api/usermanagement/OrgApiServiceImpl.java | 91 ++++++++++++++++++- .../OrganizationController.java | 11 +++ .../usermanagement/OrganizationEndpoints.java | 7 ++ 4 files changed, 110 insertions(+), 1 deletion(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiService.java index 2901aeb0d..c87732d35 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiService.java @@ -53,5 +53,7 @@ public interface OrgApiService { Mono getOrganizationConfigs(String orgId); Mono getApiUsageCount(String orgId, Boolean lastMonthOnly); + + Mono getOrganizationMembersForSearch(String orgId, String searchMemberName, String searchGroupId, Integer pageNum, Integer pageSize); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java index 5853bfcf2..2a5b0d0c3 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java @@ -40,6 +40,9 @@ import org.springframework.http.codec.multipart.Part; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; +import reactor.core.publisher.Flux; +import org.lowcoder.domain.group.service.GroupMemberService; +import org.lowcoder.domain.group.model.GroupMember; import java.util.*; import java.util.stream.Collectors; @@ -49,6 +52,8 @@ import static org.lowcoder.sdk.util.ExceptionUtils.deferredError; import static org.lowcoder.sdk.util.ExceptionUtils.ofError; import static org.lowcoder.sdk.util.StreamUtils.collectSet; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; @Slf4j @Service @@ -72,9 +77,10 @@ public class OrgApiServiceImpl implements OrgApiService { private GroupService groupService; @Autowired private AuthenticationService authenticationService; - @Autowired private ServerLogService serverLogService; + @Autowired + private GroupMemberService groupMemberService; @Override public Mono getOrganizationMembers(String orgId, int page, int count) { @@ -84,6 +90,78 @@ public Mono getOrganizationMembers(String orgId, int page, in .then(getOrgMemberListView(orgId, page, count)); } +// Update getOrgMemberListViewForSearch to filter by group membership +private Mono getOrgMemberListViewForSearch(String orgId, String searchMemberName, String searchGroupId, Integer page, Integer pageSize) { + return orgMemberService.getOrganizationMembers(orgId) + .collectList() + .flatMap(orgMembers -> { + List userIds = orgMembers.stream() + .map(OrgMember::getUserId) + .collect(Collectors.toList()); + Mono> users = userService.getByIds(userIds); + + // If searchGroupId is provided, fetch group members + Mono> groupUserIdsMono = StringUtils.isBlank(searchGroupId) + ? Mono.just(Collections.emptySet()) + : groupMemberService.getGroupMembers(searchGroupId) + .map(list -> list.stream() + .map(GroupMember::getUserId) + .collect(Collectors.toSet())); + + return Mono.zip(users, groupUserIdsMono) + .map(tuple -> { + Map userMap = tuple.getT1(); + Set groupUserIds = tuple.getT2(); + + var list = orgMembers.stream() + .map(orgMember -> { + User user = userMap.get(orgMember.getUserId()); + if (user == null) { + log.warn("user {} not exist and will be removed from the result.", orgMember.getUserId()); + return null; + } + return buildOrgMemberView(user, orgMember); + }) + .filter(Objects::nonNull) + .filter(orgMemberView -> { + // Filter by name + boolean matchesName = StringUtils.isBlank(searchMemberName) || + StringUtils.containsIgnoreCase(orgMemberView.getName(), searchMemberName); + + // Filter by group + boolean matchesGroup = StringUtils.isBlank(searchGroupId) || + groupUserIds.contains(orgMemberView.getUserId()); + + return matchesName && matchesGroup; + }) + .collect(Collectors.toList()); + var pageTotal = list.size(); + list = list.subList((page - 1) * pageSize, pageSize == 0 ? pageTotal : Math.min(page * pageSize, pageTotal)); + return Pair.of(list, pageTotal); + }); + }) + .zipWith(sessionUserService.getVisitorOrgMemberCache()) + .map(tuple -> { + List memberViews = tuple.getT1().getLeft(); + var pageTotal = tuple.getT1().getRight(); + OrgMember orgMember = tuple.getT2(); + return OrgMemberListView.builder() + .members(memberViews) + .total(pageTotal) + .pageNum(page) + .pageSize(pageSize) + .visitorRole(orgMember.getRole().getValue()) + .build(); + }); + } + @Override + public Mono getOrganizationMembersForSearch(String orgId, String searchMemberName, String searchGroupId, Integer page, Integer pageSize) { + return sessionUserService.getVisitorId() + .flatMap(visitorId -> orgMemberService.getOrgMember(orgId, visitorId)) + .switchIfEmpty(deferredError(BizError.NOT_AUTHORIZED, "NOT_AUTHORIZED")) + .then(getOrgMemberListViewForSearch(orgId, searchMemberName, searchGroupId, page, pageSize)); + } + private Mono getOrgMemberListView(String orgId, int page, int count) { return orgMemberService.getOrganizationMembers(orgId) .collectList() @@ -136,6 +214,17 @@ protected OrgMemberView build(User user, OrgMember orgMember) { .rawUserInfos(findRawUserInfos(user, orgId)) .build(); } + protected OrgMemberView buildOrgMemberView(User user, OrgMember orgMember) { + String orgId = orgMember.getOrgId(); + return OrgMemberView.builder() + .name(user.getName()) + .userId(user.getId()) + .role(orgMember.getRole().getValue()) + .avatarUrl(user.getAvatarUrl()) + .joinTime(orgMember.getJoinTime()) + .rawUserInfos(findRawUserInfos(user, orgId)) + .build(); + } protected Map> findRawUserInfos(User user, String orgId) { return SetUtils.emptyIfNull(user.getConnections()) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java index 55221cd71..f73758127 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java @@ -12,6 +12,7 @@ import org.lowcoder.api.usermanagement.view.UpdateRoleRequest; import org.lowcoder.api.util.BusinessEventPublisher; import org.lowcoder.api.util.GidService; +import org.lowcoder.domain.organization.model.OrgMember; import org.lowcoder.domain.organization.model.Organization; import org.lowcoder.domain.organization.model.Organization.OrganizationCommonSettings; import org.lowcoder.domain.organization.service.OrgMemberService; @@ -117,6 +118,16 @@ public Mono> getOrgMembers(@PathVariable String orgApiService.getOrganizationMembers(id, pageNum, pageSize) .map(ResponseView::success)); } + @Override + public Mono> getOrgMembersForSearch(@PathVariable String orgId, + @PathVariable String searchMemberName, + @PathVariable String searchGroupId, + @RequestParam(required = false, defaultValue = "1") int pageNum, + @RequestParam(required = false, defaultValue = "1000") int pageSize) { + return gidService.convertOrganizationIdToObjectId(orgId).flatMap(id -> + orgApiService.getOrganizationMembersForSearch(id, searchMemberName, searchGroupId, pageNum, pageSize) + .map(ResponseView::success)); + } @Override public Mono> updateRoleForMember(@RequestBody UpdateRoleRequest updateRoleRequest, diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java index 86ed6888b..6fee2a511 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java @@ -98,6 +98,13 @@ public Mono> getOrgMembers(@PathVariable String @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "1000") int pageSize); + @GetMapping("/{orgId}/{searchMemberName}/{searchGroupId}/members") + public Mono> getOrgMembersForSearch(@PathVariable String orgId, + @PathVariable String searchMemberName, + @PathVariable String searchGroupId, + @RequestParam(required = false, defaultValue = "1") int pageNum, + @RequestParam(required = false, defaultValue = "1000") int pageSize); + @Operation( tags = TAG_ORGANIZATION_MEMBERS, operationId = "updateOrganizationMemberRole", From 40619e9855e6abb408e19d39b135d46ddf31f07d Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Tue, 17 Jun 2025 15:43:30 +0500 Subject: [PATCH 45/51] fix hidden container results into white spaces --- .../lowcoder/src/layout/compSelectionWrapper.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/client/packages/lowcoder/src/layout/compSelectionWrapper.tsx b/client/packages/lowcoder/src/layout/compSelectionWrapper.tsx index 0e6a228d3..739e87723 100644 --- a/client/packages/lowcoder/src/layout/compSelectionWrapper.tsx +++ b/client/packages/lowcoder/src/layout/compSelectionWrapper.tsx @@ -242,18 +242,8 @@ const ResizableChildren = React.memo((props: { children: JSX.Element | React.ReactNode; }) => { const { ref: innerRef } = useResizeDetector({ - skipOnMount: ( - props.compType === 'responsiveLayout' - || props.compType === 'columnLayout' - || props.compType === 'pageLayout' - || props.compType === 'splitLayout' - || props.compType === 'floatTextContainer' - || props.compType === 'tabbedContainer' - || props.compType === 'collapsibleContainer' - || props.compType === 'container' - ), refreshMode: "debounce", - refreshRate: 0, + refreshRate: 10, onResize: ({width, height}: ResizePayload) => props.onInnerResize(width ?? undefined, height ?? undefined), observerOptions: { box: "border-box" } }); From 68b951382bba688f1ae2046adae39e53c2ed1a48 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Tue, 17 Jun 2025 17:16:43 +0500 Subject: [PATCH 46/51] remove console errors --- .../comps/comps/tabs/tabbedContainerComp.tsx | 2 +- .../services/environments.service.ts | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tabs/tabbedContainerComp.tsx b/client/packages/lowcoder/src/comps/comps/tabs/tabbedContainerComp.tsx index 2d930fbf4..73fa06c0d 100644 --- a/client/packages/lowcoder/src/comps/comps/tabs/tabbedContainerComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tabs/tabbedContainerComp.tsx @@ -245,7 +245,7 @@ const TabbedContainer = (props: TabbedContainerProps) => { label, key: tab.key, forceRender: !destroyInactiveTab, - destroyInactiveTabPane: destroyInactiveTab, + destroyInactiveTab: destroyInactiveTab, children: ( diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts index 762bb4774..3d4504942 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts @@ -95,8 +95,8 @@ export async function getEnvironments(): Promise { return response.data.data || []; } catch (error) { const errorMessage = - error instanceof Error ? error.message : trans("environments.services_environments_failedToFetchEnvironments"); - messageInstance.error(errorMessage); + error && error instanceof Error ? error.message : trans("environments.services_environments_failedToFetchEnvironments"); + console.error(errorMessage); throw error; } } @@ -150,7 +150,7 @@ export async function getEnvironmentById(id: string): Promise { } catch (error) { const errorMessage = error instanceof Error ? error.message : trans("environments.services_environments_failedToFetchEnvironment"); - messageInstance.error(errorMessage); + console.error(errorMessage); throw error; } } @@ -223,7 +223,7 @@ export async function getEnvironmentWorkspaces( // Handle and transform error const errorMessage = error instanceof Error ? error.message : trans("environments.services_environments_failedToFetchWorkspaces"); - messageInstance.error(errorMessage); + console.error(errorMessage); throw error; } } @@ -273,7 +273,7 @@ export async function getEnvironmentUserGroups( } catch (error) { // Handle and transform error const errorMessage = error instanceof Error ? error.message : trans("environments.services_environments_failedToFetchUserGroups"); - messageInstance.error(errorMessage); + console.error(errorMessage); throw error; } } @@ -383,7 +383,7 @@ export async function getWorkspaceApps( } catch (error) { // Handle and transform error const errorMessage = error instanceof Error ? error.message : trans("environments.services_environments_failedToFetchWorkspaceApps"); - messageInstance.error(errorMessage); + console.error(errorMessage); throw error; } } @@ -441,7 +441,7 @@ export async function getWorkspaceDataSources( } catch (error) { // Handle and transform error const errorMessage = error instanceof Error ? error.message : trans("environments.services_environments_failedToFetchWorkspaceDataSources"); - messageInstance.error(errorMessage); + console.error(errorMessage); throw error; } } @@ -523,7 +523,7 @@ export async function getWorkspaceQueries( } catch (error) { // Handle and transform error const errorMessage = error instanceof Error ? error.message : trans("environments.services_environments_failedToFetchWorkspaceQueries"); - messageInstance.error(errorMessage); + console.error(errorMessage); throw error; } } @@ -575,7 +575,7 @@ export async function getEnvironmentsWithLicenseStatus(): Promise } catch (error) { const errorMessage = error instanceof Error ? error.message : trans("environments.services_environments_failedToFetchEnvironments"); - messageInstance.error(errorMessage); + console.error(errorMessage); throw error; } } @@ -618,7 +618,7 @@ export async function getEnvironmentDeploymentId( } catch (error) { // Handle and transform error const errorMessage = error instanceof Error ? error.message : trans("environments.services_environments_failedToFetchDeploymentId"); - messageInstance.error(errorMessage); + console.error(errorMessage); throw error; } } \ No newline at end of file From d509dc73b7aa899823ff5d4b381b46e9aa5e7629 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Tue, 17 Jun 2025 05:25:27 -0400 Subject: [PATCH 47/51] Fixed pagination for myorg endpoint. --- .../api/usermanagement/UserController.java | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java index 3592f0a86..362b68863 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java @@ -30,6 +30,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.List; + import static org.lowcoder.sdk.exception.BizError.INVALID_USER_STATUS; import static org.lowcoder.sdk.util.ExceptionUtils.ofError; @@ -70,30 +72,25 @@ public Mono> getUserProfile(ServerWebExchange exchange) { @Override public Mono> getUserOrgs(ServerWebExchange exchange, - @RequestParam(required = false) String orgName, - @RequestParam(required = false, defaultValue = "1") Integer pageNum, - @RequestParam(required = false, defaultValue = "10") Integer pageSize) { + @RequestParam(required = false) String orgName, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "10") Integer pageSize) { return sessionUserService.getVisitor() .flatMap(user -> { - // Get all active organizations for the user Flux orgMemberFlux = orgMemberService.getAllActiveOrgs(user.getId()); - - // If orgName filter is provided, filter organizations by name - if (StringUtils.isNotBlank(orgName)) { - return orgMemberFlux - .flatMap(orgMember -> organizationService.getById(orgMember.getOrgId())) - .filter(org -> StringUtils.containsIgnoreCase(org.getName(), orgName)) - .map(OrgView::new) - .collectList() - .map(orgs -> PageResponseView.success(orgs, pageNum, pageSize, orgs.size())); - } - - // If no filter, return all organizations - return orgMemberFlux + + Flux orgViewFlux = orgMemberFlux .flatMap(orgMember -> organizationService.getById(orgMember.getOrgId())) - .map(OrgView::new) - .collectList() - .map(orgs -> PageResponseView.success(orgs, pageNum, pageSize, orgs.size())); + .filter(org -> StringUtils.isBlank(orgName) || StringUtils.containsIgnoreCase(org.getName(), orgName)) + .map(OrgView::new); + + return orgViewFlux.collectList().map(orgs -> { + int total = orgs.size(); + int fromIndex = Math.max((pageNum - 1) * pageSize, 0); + int toIndex = Math.min(fromIndex + pageSize, total); + List pagedOrgs = fromIndex < toIndex ? orgs.subList(fromIndex, toIndex) : List.of(); + return PageResponseView.success(pagedOrgs, pageNum, pageSize, total); + }); }) .map(ResponseView::success); } From a10c20b2d4eebd5381d21fd08dce19808e3081d8 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Tue, 17 Jun 2025 11:49:56 -0400 Subject: [PATCH 48/51] Fixed pagination for myorg endpoint. --- .../repository/OrganizationRepository.java | 5 +++ .../service/OrganizationService.java | 4 +++ .../service/OrganizationServiceImpl.java | 32 +++++++++++++++++++ .../api/usermanagement/UserController.java | 26 +++++++-------- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/repository/OrganizationRepository.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/repository/OrganizationRepository.java index 7fceace3e..d6606fde2 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/repository/OrganizationRepository.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/repository/OrganizationRepository.java @@ -6,6 +6,8 @@ import org.lowcoder.domain.organization.model.OrganizationState; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import org.springframework.stereotype.Repository; +import org.springframework.data.domain.Pageable; +import java.util.List; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -31,4 +33,7 @@ public interface OrganizationRepository extends ReactiveMongoRepository findByOrganizationDomainIsNotNull(); Mono existsBySlug(String slug); + + Flux findByIdInAndNameContainingIgnoreCase(List ids, String name, Pageable pageable); + Mono countByIdInAndNameContainingIgnoreCase(List ids, String name); } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationService.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationService.java index 6b375d4d2..fa9b0cd5e 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationService.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationService.java @@ -8,6 +8,7 @@ import org.lowcoder.infra.annotation.NonEmptyMono; import org.lowcoder.infra.annotation.PossibleEmptyMono; import org.springframework.http.codec.multipart.Part; +import org.springframework.data.domain.Pageable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -52,4 +53,7 @@ public interface OrganizationService { Mono updateCommonSettings(String orgId, String key, Object value); Mono updateSlug(String organizationId, String newSlug); + + Flux findUserOrgs(String userId, String orgName, Pageable pageable); + Mono countUserOrgs(String userId, String orgName); } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationServiceImpl.java index 781ffe257..39c26d990 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationServiceImpl.java @@ -18,6 +18,8 @@ import org.lowcoder.domain.user.repository.UserRepository; import org.lowcoder.domain.util.SlugUtils; import org.lowcoder.infra.annotation.PossibleEmptyMono; +import org.lowcoder.infra.birelation.BiRelationService; +import org.lowcoder.infra.birelation.BiRelation; import org.lowcoder.infra.mongo.MongoUpsertHelper; import org.lowcoder.sdk.config.CommonConfig; import org.lowcoder.sdk.config.dynamic.Conf; @@ -31,6 +33,7 @@ import org.springframework.data.mongodb.core.query.Update; import org.springframework.http.codec.multipart.Part; import org.springframework.stereotype.Service; +import org.springframework.data.domain.Pageable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -41,6 +44,7 @@ import static org.lowcoder.domain.organization.model.OrganizationState.DELETED; import static org.lowcoder.domain.util.QueryDslUtils.fieldName; import static org.lowcoder.sdk.exception.BizError.UNABLE_TO_FIND_VALID_ORG; +import static org.lowcoder.infra.birelation.BiRelationBizType.ORG_MEMBER; import static org.lowcoder.sdk.util.ExceptionUtils.deferredError; import static org.lowcoder.sdk.util.ExceptionUtils.ofError; import static org.lowcoder.sdk.util.LocaleUtils.getLocale; @@ -62,6 +66,7 @@ public class OrganizationServiceImpl implements OrganizationService { private final ApplicationContext applicationContext; private final CommonConfig commonConfig; private final ConfigCenter configCenter; + private final BiRelationService biRelationService; @PostConstruct private void init() @@ -315,4 +320,31 @@ public Mono updateSlug(String organizationId, String newSlug) { }); }); } + + @Override + public Flux findUserOrgs(String userId, String orgName, Pageable pageable) { + return biRelationService.getByTargetId(ORG_MEMBER, userId) + .map(BiRelation::getSourceId) + .collectList() + .flatMapMany(orgIds -> { + if (orgIds.isEmpty()) { + return Flux.empty(); + } + return repository.findByIdInAndNameContainingIgnoreCase(orgIds, orgName, pageable); + }); + } + + @Override + public Mono countUserOrgs(String userId, String orgName) { + String filter = orgName == null ? "" : orgName; + return biRelationService.getByTargetId(ORG_MEMBER, userId) + .map(BiRelation::getSourceId) + .collectList() + .flatMap(orgIds -> { + if (orgIds.isEmpty()) { + return Mono.just(0L); + } + return repository.countByIdInAndNameContainingIgnoreCase(orgIds, filter); + }); + } } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java index 362b68863..f3485477e 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java @@ -27,6 +27,9 @@ import org.springframework.http.codec.multipart.Part; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ServerWebExchange; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.PageRequest; + import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -77,20 +80,15 @@ public Mono> getUserOrgs(ServerWebExchange exchange, @RequestParam(required = false, defaultValue = "10") Integer pageSize) { return sessionUserService.getVisitor() .flatMap(user -> { - Flux orgMemberFlux = orgMemberService.getAllActiveOrgs(user.getId()); - - Flux orgViewFlux = orgMemberFlux - .flatMap(orgMember -> organizationService.getById(orgMember.getOrgId())) - .filter(org -> StringUtils.isBlank(orgName) || StringUtils.containsIgnoreCase(org.getName(), orgName)) - .map(OrgView::new); - - return orgViewFlux.collectList().map(orgs -> { - int total = orgs.size(); - int fromIndex = Math.max((pageNum - 1) * pageSize, 0); - int toIndex = Math.min(fromIndex + pageSize, total); - List pagedOrgs = fromIndex < toIndex ? orgs.subList(fromIndex, toIndex) : List.of(); - return PageResponseView.success(pagedOrgs, pageNum, pageSize, total); - }); + Pageable pageable = PageRequest.of(pageNum - 1, pageSize); + String filter = orgName == null ? "" : orgName; + return organizationService.findUserOrgs(user.getId(), filter, pageable) + .map(OrgView::new) + .collectList() + .zipWith(organizationService.countUserOrgs(user.getId(), filter)) + .map(tuple -> PageResponseView.success( + tuple.getT1(), pageNum, pageSize, tuple.getT2().intValue() + )); }) .map(ResponseView::success); } From 3a7ace3fdab829ae967f0bcd334e116cff499813 Mon Sep 17 00:00:00 2001 From: FalkWolsky Date: Tue, 17 Jun 2025 19:59:55 +0200 Subject: [PATCH 49/51] Update Version Numbers --- client/VERSION | 2 +- client/package.json | 2 +- client/packages/lowcoder-comps/package.json | 2 +- client/packages/lowcoder-sdk/package.json | 2 +- client/packages/lowcoder/package.json | 2 +- server/api-service/pom.xml | 2 +- server/node-service/package.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/VERSION b/client/VERSION index 9aa34646d..fbafd6b60 100644 --- a/client/VERSION +++ b/client/VERSION @@ -1 +1 @@ -2.7.0 \ No newline at end of file +2.7.2 \ No newline at end of file diff --git a/client/package.json b/client/package.json index 1d539f23b..12f93a4aa 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-frontend", - "version": "2.7.0", + "version": "2.7.2", "type": "module", "private": true, "workspaces": [ diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index 2819fd79c..613c8ee3d 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-comps", - "version": "2.7.1", + "version": "2.7.2", "type": "module", "license": "MIT", "dependencies": { diff --git a/client/packages/lowcoder-sdk/package.json b/client/packages/lowcoder-sdk/package.json index e901e98aa..7cc5d5ea4 100644 --- a/client/packages/lowcoder-sdk/package.json +++ b/client/packages/lowcoder-sdk/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-sdk", - "version": "2.7.0", + "version": "2.7.2", "type": "module", "files": [ "src", diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json index f25be12c3..e838c870d 100644 --- a/client/packages/lowcoder/package.json +++ b/client/packages/lowcoder/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder", - "version": "2.7.0", + "version": "2.7.2", "private": true, "type": "module", "main": "src/index.sdk.ts", diff --git a/server/api-service/pom.xml b/server/api-service/pom.xml index 50e8c157f..972c23619 100644 --- a/server/api-service/pom.xml +++ b/server/api-service/pom.xml @@ -12,7 +12,7 @@ - 2.7.0 + 2.7.2 17 ${java.version} ${java.version} diff --git a/server/node-service/package.json b/server/node-service/package.json index 50ba90832..db8736ac0 100644 --- a/server/node-service/package.json +++ b/server/node-service/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-node-server", - "version": "2.7.0", + "version": "2.7.2", "private": true, "engines": { "node": "^14.18.0 || >=16.0.0" From 49b9126acdf8357c9906a67cf843ba944b24eb84 Mon Sep 17 00:00:00 2001 From: FalkWolsky Date: Tue, 17 Jun 2025 22:33:07 +0200 Subject: [PATCH 50/51] Adding Better News, Enterprise Form and Translations --- .../lowcoder-design/src/icons/index.tsx | 2 +- client/packages/lowcoder/src/api/newsApi.ts | 2 +- .../packages/lowcoder/src/i18n/locales/de.ts | 4 +- .../packages/lowcoder/src/i18n/locales/en.ts | 4 +- .../packages/lowcoder/src/i18n/locales/es.ts | 2 + .../packages/lowcoder/src/i18n/locales/it.ts | 2 + .../packages/lowcoder/src/i18n/locales/pt.ts | 2 + .../packages/lowcoder/src/i18n/locales/ru.ts | 2 + .../packages/lowcoder/src/i18n/locales/zh.ts | 2 + .../src/pages/ApplicationV2/NewsLayout.tsx | 75 ++++++++++++++++--- .../src/pages/ApplicationV2/index.tsx | 15 ++++ .../src/pages/setting/appUsage/index.tsx | 7 +- .../src/pages/setting/audit/index.tsx | 9 +-- .../src/pages/setting/branding/index.tsx | 7 +- .../src/pages/setting/environments/index.tsx | 5 +- .../src/pages/setting/hubspotModal.tsx | 35 +++++---- .../src/pages/setting/settingHome.tsx | 24 +++++- .../setting/subscriptions/productCard.tsx | 5 +- 18 files changed, 150 insertions(+), 54 deletions(-) diff --git a/client/packages/lowcoder-design/src/icons/index.tsx b/client/packages/lowcoder-design/src/icons/index.tsx index 94453db48..b033d52e9 100644 --- a/client/packages/lowcoder-design/src/icons/index.tsx +++ b/client/packages/lowcoder-design/src/icons/index.tsx @@ -255,7 +255,7 @@ export { ReactComponent as RecyclerIcon } from "./remix/delete-bin-line.svg"; export { ReactComponent as MarketplaceIcon } from "./v1/icon-application-marketplace.svg"; export { ReactComponent as FavoritesIcon } from "./v1/icon-application-favorites.svg"; export { ReactComponent as HomeSettingIcon } from "./remix/settings-4-line.svg"; -export { ReactComponent as EnterpriseIcon } from "./remix/earth-line.svg"; +export { ReactComponent as EnterpriseIcon } from "./remix/shield-star-line.svg"; export { ReactComponent as VerticalIcon } from "./remix/vertical.svg"; export { ReactComponent as HorizontalIcon } from "./remix/horizontal.svg"; diff --git a/client/packages/lowcoder/src/api/newsApi.ts b/client/packages/lowcoder/src/api/newsApi.ts index 9da920260..2d8c822e2 100644 --- a/client/packages/lowcoder/src/api/newsApi.ts +++ b/client/packages/lowcoder/src/api/newsApi.ts @@ -132,7 +132,7 @@ export const getHubspotContent = async () => { }; try { const result = await NewsApi.secureRequest(apiBody); - return result?.data[0]?.hubspot?.length > 0 ? result.data[0].hubspot as any[] : []; + return result?.data[0]?.results?.length > 0 ? result.data[0].results as any[] : []; } catch (error) { console.error("Error getting news:", error); throw error; diff --git a/client/packages/lowcoder/src/i18n/locales/de.ts b/client/packages/lowcoder/src/i18n/locales/de.ts index 17d38343d..4df6ce472 100644 --- a/client/packages/lowcoder/src/i18n/locales/de.ts +++ b/client/packages/lowcoder/src/i18n/locales/de.ts @@ -2458,8 +2458,10 @@ export const de = { "usageLogDevices": "Gerät/OS Aufschlüsselung", "usageLogBrowsers": "Browser/Layout Engine Aufschlüsselung", "premiumFeaturesNotice": "Alle Premium Features sind in der Enterprise Edition von Lowcoder verfügbar.", +"readMoreNotice" : "Erfahren Sie mehr über die Enterprise Edition und wie Sie sie ganz einfach installieren können.", +"readMoreButton": "Details zur Enterprise Edition", "requestLicense": "Lizenzen für die Enterprise Edition anfordern", -"requestLicensesBtton": "Request Enterprise Access", +"requestLicensesBtton": "Enterprise Edition anfragen", "AuditLogsTitle": "Audit Logs", "AuditLogsIntroTitle": "Leistungsstarker Einblick in die Aktivitäten Ihres Arbeitsbereichs", "AuditLogsIntro1": "Mithilfe von Audit-Protokollen können Administratoren genau verfolgen, was auf der gesamten Lowcoder-Plattform passiert. Von Benutzeranmeldungen bis hin zu App-Änderungen wird jede relevante Aktion erfasst und gespeichert.", diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index c88dde8eb..de24d5b64 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -2449,8 +2449,10 @@ export const en = { "usageLogDevices" : "Device/OS Breakdown", "usageLogBrowsers" : "Browser/Layout Engine Breakdown", "premiumFeaturesNotice" : "All Premium Features are avilable in the Enterprise Edition of Lowcoder.", + "readMoreNotice" : "Learn more about the Enterprise Edition and how to install it easily.", + "readMoreButton" : "Enterprise Edition Details", "requestLicense" : "Request Enterprise Edition Licenses", - "requestLicensesBtton" : "Request Enterprise Access", + "requestLicensesBtton" : "Unlock Enterprise Features", "AuditLogsTitle": "Audit Logs", "AuditLogsIntroTitle": "Powerful visibility into your workspace activity", "AuditLogsIntro1": "Audit Logs enable administrators to track exactly what happens across the entire Lowcoder platform. From user sign-ins to app modifications, every relevant action is captured and stored.", diff --git a/client/packages/lowcoder/src/i18n/locales/es.ts b/client/packages/lowcoder/src/i18n/locales/es.ts index 606293093..44623f20e 100644 --- a/client/packages/lowcoder/src/i18n/locales/es.ts +++ b/client/packages/lowcoder/src/i18n/locales/es.ts @@ -2458,6 +2458,8 @@ export const es = { "usageLogDevices": "Desglose de dispositivos/OS", "usageLogBrowsers": "Desglose del navegador/motor de diseño", "premiumFeaturesNotice": "Todas las características Premium están disponibles en la Edición Enterprise de Lowcoder.", +"readMoreNotice" : "Conozca más sobre la edición Enterprise y cómo instalarla fácilmente.", +"readMoreButton": "Detalles de la edición Enterprise", "requestLicense": "Solicitar licencias de Enterprise Edition", "requestLicensesBtton": "Solicitar acceso para empresas", "AuditLogsTitle": "Registros de auditoría", diff --git a/client/packages/lowcoder/src/i18n/locales/it.ts b/client/packages/lowcoder/src/i18n/locales/it.ts index bb2833901..c6bdbd723 100644 --- a/client/packages/lowcoder/src/i18n/locales/it.ts +++ b/client/packages/lowcoder/src/i18n/locales/it.ts @@ -2458,6 +2458,8 @@ export const it = { "usageLogDevices": "Ripartizione dispositivi/OS", "usageLogBrowsers": "Browser/Motore di layout", "premiumFeaturesNotice": "Tutte le funzioni Premium sono disponibili nell'edizione Enterprise di Lowcoder.", +"readMoreNotice" : "Scopri di più sulla versione Enterprise e su come installarla facilmente.", +"readMoreButton": "Dettagli dell'edizione Enterprise", "requestLicense": "Richiesta di licenze Enterprise Edition", "requestLicensesBtton": "Richiesta di accesso aziendale", "AuditLogsTitle": "Registri di controllo", diff --git a/client/packages/lowcoder/src/i18n/locales/pt.ts b/client/packages/lowcoder/src/i18n/locales/pt.ts index a4d094bb7..c51200227 100644 --- a/client/packages/lowcoder/src/i18n/locales/pt.ts +++ b/client/packages/lowcoder/src/i18n/locales/pt.ts @@ -2458,6 +2458,8 @@ export const pt = { "usageLogDevices": "Discriminação por dispositivo/SO", "usageLogBrowsers": "Navegador/motor de apresentação", "premiumFeaturesNotice": "Todas as funcionalidades Premium estão disponíveis na Enterprise Edition do Lowcoder.", +"readMoreNotice" : "Saiba mais sobre a edição Enterprise e como instalá-la com facilidade.", +"readMoreButton": "Detalhes da edição Enterprise", "requestLicense": "Solicitar licenças da Enterprise Edition", "requestLicensesBtton": "Pedir acesso à empresa", "AuditLogsTitle": "Registos de auditoria", diff --git a/client/packages/lowcoder/src/i18n/locales/ru.ts b/client/packages/lowcoder/src/i18n/locales/ru.ts index 806eea60e..5ce0435c3 100644 --- a/client/packages/lowcoder/src/i18n/locales/ru.ts +++ b/client/packages/lowcoder/src/i18n/locales/ru.ts @@ -2458,6 +2458,8 @@ export const ru = { "usageLogDevices": "Разбивка по устройствам/ОС", "usageLogBrowsers": "Разбивка браузера/программного обеспечения", "premiumFeaturesNotice": "Все премиум-функции доступны в корпоративной версии Lowcoder.", +"readMoreNotice" : "Узнайте больше о версии Enterprise и о том, как легко её установить.", +"readMoreButton": "Подробности о версии Enterprise", "requestLicense": "Запрос лицензий Enterprise Edition", "requestLicensesBtton": "Запрос доступа к предприятию", "AuditLogsTitle": "Журналы аудита", diff --git a/client/packages/lowcoder/src/i18n/locales/zh.ts b/client/packages/lowcoder/src/i18n/locales/zh.ts index 797ed9354..69555a8d1 100644 --- a/client/packages/lowcoder/src/i18n/locales/zh.ts +++ b/client/packages/lowcoder/src/i18n/locales/zh.ts @@ -2459,6 +2459,8 @@ export const zh = { "usageLogDevices": "设备/操作系统明细", "usageLogBrowsers": "浏览器/布局引擎细分", "premiumFeaturesNotice": "Lowcoder 企业版可提供所有高级功能。", + "readMoreNotice" : "了解有关企业版的更多信息,以及如何轻松安装它。", + "readMoreButton": "企业版详情", "requestLicense": "申请企业版许可证", "requestLicensesBtton": "申请企业访问权限", "AuditLogsTitle": "审计日志", diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/NewsLayout.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/NewsLayout.tsx index c9db2e1fe..0075e23b8 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/NewsLayout.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/NewsLayout.tsx @@ -98,8 +98,6 @@ export function NewsLayout() { .catch(err => console.error("Failed to load news:", err)); }, []); - console.log(youTubeData); - return ( @@ -110,6 +108,69 @@ export function NewsLayout() {

Lowcoder {trans("home.news")}

+ + 📝 Latest Blog Posts + + {hubspotData?.map((item: { htmlTitle: any; publishDate: any; postSummary: any; url: any; featuredImage: any; metaDescription: any; }, idx: any) => { + const { + htmlTitle, + publishDate, + postSummary, + url, + featuredImage, + metaDescription, + } = item; + + const summaryHtml = postSummary || metaDescription || ""; + const coverImage = featuredImage || "https://placehold.co/600x400?text=Lowcoder+Blog"; + + // Strip HTML to plain text + const stripHtml = (html: string): string => { + const div = document.createElement("div"); + div.innerHTML = html; + return div.textContent || div.innerText || ""; + }; + + const plainSummary = stripHtml(summaryHtml); + + return ( + + + + + } + > + + {htmlTitle} + + } + description={ + <> + + {new Date(publishDate).toLocaleDateString()} + + + {plainSummary} + + + } + /> + + + ); + })} + + + 📺 Latest YouTube Videos @@ -173,17 +234,7 @@ export function NewsLayout() { - 📝 Latest Blog Posts - - {hubspotData.length === 0 && ( - - No blog posts available at the moment. - - )} - - - diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx index 4dd184f2e..4faaf6a3f 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx @@ -11,6 +11,7 @@ import { NEWS_URL, ORG_HOME_URL, SUBSCRIPTION_SETTING, + ENVIRONMENT_SETTING, } from "constants/routesURL"; import { getUser, isFetchingUser } from "redux/selectors/usersSelectors"; import { useDispatch, useSelector } from "react-redux"; @@ -231,6 +232,20 @@ export default function ApplicationHome() { } ] }, + { + items: [ + { + text: {trans("environments.detail_enterpriseEdition")}, + routePath: ENVIRONMENT_SETTING, + routeComp: Setting, + routePathExact: false, + icon: ({ selected, ...otherProps }) => , + mobileVisible: true, + visible: () => !isLicenseActive, + style: { color: "#ff6f3c" }, + } + ] + }, { items: [ diff --git a/client/packages/lowcoder/src/pages/setting/appUsage/index.tsx b/client/packages/lowcoder/src/pages/setting/appUsage/index.tsx index c8e1e81e8..d17d72f41 100644 --- a/client/packages/lowcoder/src/pages/setting/appUsage/index.tsx +++ b/client/packages/lowcoder/src/pages/setting/appUsage/index.tsx @@ -55,6 +55,7 @@ const AppUsageDoc = () => { const user = useSelector(getUser); const deploymentId = useSelector(getDeploymentId); const dispatch = useDispatch(); +const isLowCoderDomain = window.location.hostname === 'app.lowcoder.cloud'; const apiUsage = useSelector(getOrgApiUsage); useEffect(() => { @@ -122,12 +123,6 @@ const apiUsage = useSelector(getOrgApiUsage); - - -

{deploymentId}

-
-
- {trans("enterprise.PricingIntro")} diff --git a/client/packages/lowcoder/src/pages/setting/audit/index.tsx b/client/packages/lowcoder/src/pages/setting/audit/index.tsx index 552b19117..edbc3840a 100644 --- a/client/packages/lowcoder/src/pages/setting/audit/index.tsx +++ b/client/packages/lowcoder/src/pages/setting/audit/index.tsx @@ -54,7 +54,8 @@ const Audit = () => { const user = useSelector(getUser); const deploymentId = useSelector(getDeploymentId); const dispatch = useDispatch(); - + const isLowCoderDomain = window.location.hostname === 'app.lowcoder.cloud'; + const apiUsage = useSelector(getOrgApiUsage); useEffect(() => { dispatch(fetchAPIUsageAction(user.currentOrgId)); @@ -162,12 +163,6 @@ const Audit = () => { - - -

{deploymentId}

-
-
- {trans("enterprise.PricingIntro")} diff --git a/client/packages/lowcoder/src/pages/setting/branding/index.tsx b/client/packages/lowcoder/src/pages/setting/branding/index.tsx index 410842f4c..9c1d34fcb 100644 --- a/client/packages/lowcoder/src/pages/setting/branding/index.tsx +++ b/client/packages/lowcoder/src/pages/setting/branding/index.tsx @@ -45,6 +45,7 @@ const BrandingPromo = () => { const user = useSelector(getUser); const deploymentId = useSelector(getDeploymentId); const dispatch = useDispatch(); + const isLowCoderDomain = window.location.hostname === 'app.lowcoder.cloud'; const apiUsage = useSelector(getOrgApiUsage); useEffect(() => { @@ -152,12 +153,6 @@ const BrandingPromo = () => { {trans("enterprise.BrandingWhatsNewIntro")} - - - -

{deploymentId}

-
-
diff --git a/client/packages/lowcoder/src/pages/setting/environments/index.tsx b/client/packages/lowcoder/src/pages/setting/environments/index.tsx index 1b7778708..967b8d70a 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/index.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/index.tsx @@ -44,6 +44,7 @@ const EnvironmentsPromo = () => { const [modalOpen, setModalOpen] = useState(false); const user = useSelector(getUser); const deploymentId = useSelector(getDeploymentId); + const isLowCoderDomain = window.location.hostname === 'app.lowcoder.cloud'; const dispatch = useDispatch(); @@ -129,11 +130,11 @@ const EnvironmentsPromo = () => { - + {!isLowCoderDomain &&

{deploymentId}

-
+
} diff --git a/client/packages/lowcoder/src/pages/setting/hubspotModal.tsx b/client/packages/lowcoder/src/pages/setting/hubspotModal.tsx index 354ede1bb..c517570d0 100644 --- a/client/packages/lowcoder/src/pages/setting/hubspotModal.tsx +++ b/client/packages/lowcoder/src/pages/setting/hubspotModal.tsx @@ -41,6 +41,9 @@ interface Props { } export function HubspotModal({ open, onClose, orgId, deploymentIds }: Props) { + + const isLowCoderDomain = typeof window !== "undefined" && window.location.hostname === "app.lowcoder.cloud"; + useEffect(() => { if (!open) return; @@ -96,21 +99,25 @@ export function HubspotModal({ open, onClose, orgId, deploymentIds }: Props) { {orgId} - - Deployment IDs: - + + { !isLowCoderDomain && + <> + + Deployment IDs: + + + {deploymentIds.length === 0 ? ( + No deployments found. + ) : ( + deploymentIds.map((id, idx) => ( + + {id} + + )) + )} + + } - - {deploymentIds.length === 0 ? ( - No deployments found. - ) : ( - deploymentIds.map((id, idx) => ( - - {id} - - )) - )} - diff --git a/client/packages/lowcoder/src/pages/setting/settingHome.tsx b/client/packages/lowcoder/src/pages/setting/settingHome.tsx index 0017e598f..548a72cd7 100644 --- a/client/packages/lowcoder/src/pages/setting/settingHome.tsx +++ b/client/packages/lowcoder/src/pages/setting/settingHome.tsx @@ -165,9 +165,31 @@ export function SettingHome() {
{trans("enterprise.premiumFeaturesNotice")}
- + +
+ {trans("enterprise.readMoreNotice")} +
+ + + )} diff --git a/client/packages/lowcoder/src/pages/setting/subscriptions/productCard.tsx b/client/packages/lowcoder/src/pages/setting/subscriptions/productCard.tsx index e2ae339ed..f892755e2 100644 --- a/client/packages/lowcoder/src/pages/setting/subscriptions/productCard.tsx +++ b/client/packages/lowcoder/src/pages/setting/subscriptions/productCard.tsx @@ -5,6 +5,7 @@ import { Card, Button } from 'antd'; import { SettingOutlined, CheckCircleOutlined, LoadingOutlined, InfoCircleOutlined } from '@ant-design/icons'; import { buildSubscriptionSettingsLink, buildSubscriptionInfoLink } from "constants/routesURL"; import history from "util/history"; +import { trans } from "i18n"; const ProductCardContainer = styled(Card)` width: 300px; @@ -87,8 +88,8 @@ export const ProductCard: React.FC = ({ ) : ( !activeSubscription && ( checkoutLinkDataLoaded ? ( - ) : ( From 713f1f7a4eedea7c6555f53b9bcd0072163793ed Mon Sep 17 00:00:00 2001 From: FalkWolsky Date: Tue, 17 Jun 2025 22:35:13 +0200 Subject: [PATCH 51/51] Preparation for Release v2.7.2 --- client/packages/lowcoder-sdk-webpack-bundle/package.json | 2 +- deploy/helm/Chart.yaml | 2 +- .../lowcoder-server/src/main/resources/application.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/packages/lowcoder-sdk-webpack-bundle/package.json b/client/packages/lowcoder-sdk-webpack-bundle/package.json index b9266b7bd..267fb8fb3 100644 --- a/client/packages/lowcoder-sdk-webpack-bundle/package.json +++ b/client/packages/lowcoder-sdk-webpack-bundle/package.json @@ -1,7 +1,7 @@ { "name": "lowcoder-sdk-webpack-bundle", "description": "", - "version": "2.7.0", + "version": "2.7.2", "main": "index.jsx", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", diff --git a/deploy/helm/Chart.yaml b/deploy/helm/Chart.yaml index 7b3bf927d..41fb54f3d 100644 --- a/deploy/helm/Chart.yaml +++ b/deploy/helm/Chart.yaml @@ -7,7 +7,7 @@ type: application version: 2.7.0 # Lowcoder version -appVersion: "2.7.0" +appVersion: "2.7.2" # Dependencies needed for Lowcoder deployment dependencies: diff --git a/server/api-service/lowcoder-server/src/main/resources/application.yaml b/server/api-service/lowcoder-server/src/main/resources/application.yaml index 254eca6e8..2cb8c22f9 100644 --- a/server/api-service/lowcoder-server/src/main/resources/application.yaml +++ b/server/api-service/lowcoder-server/src/main/resources/application.yaml @@ -64,7 +64,7 @@ common: domain: default-value: lowcoder.org cloud: false - version: 2.7.0 + version: 2.7.2 apiVersion: 1.2 block-hound-enable: false encrypt: pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy