From e751d1ec33f351664a8221e81f0620196321fe5c Mon Sep 17 00:00:00 2001 From: Falk Wolsky Date: Mon, 2 Jun 2025 23:49:08 +0200 Subject: [PATCH 01/55] Update netlify.toml --- client/netlify.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/netlify.toml b/client/netlify.toml index 1cb2010f3e..4d549283cf 100644 --- a/client/netlify.toml +++ b/client/netlify.toml @@ -2,3 +2,5 @@ from = "/*" to = "/" status = 200 +[[plugins]] + package = "@netlify/plugin-cache" From 77135def900182d5f0b0d661ca7fab51cac9c2c9 Mon Sep 17 00:00:00 2001 From: FalkWolsky Date: Tue, 3 Jun 2025 00:07:13 +0200 Subject: [PATCH 02/55] Update Netlify Config to support local build --- .gitignore | 3 +++ client/netlify.toml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index d2a57a1fe6..f015f90569 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ server/api-service/lowcoder-server/src/main/resources/application-local-dev.yaml translations/locales/node_modules/ server/api-service/lowcoder-server/src/main/resources/application-local-dev-ee.yaml node_modules + +# Local Netlify folder +.netlify diff --git a/client/netlify.toml b/client/netlify.toml index 1cb2010f3e..7c03691673 100644 --- a/client/netlify.toml +++ b/client/netlify.toml @@ -2,3 +2,7 @@ from = "/*" to = "/" status = 200 +[build] + base = "client" + command = "yarn workspace lowcoder build" + publish = "client/packages/lowcoder/build" \ No newline at end of file From 72abc0b39fcc98d6744e3151c91ad8b2750cdecd Mon Sep 17 00:00:00 2001 From: FalkWolsky Date: Tue, 3 Jun 2025 00:27:43 +0200 Subject: [PATCH 03/55] Update Netlify Build Process in Readme --- client/README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/client/README.md b/client/README.md index 2c848ec18f..b7c9918ad6 100644 --- a/client/README.md +++ b/client/README.md @@ -116,4 +116,73 @@ When you finish developing and testing the plugin, you can publish it into the n yarn build --publish ``` -You can check a code demo here: [Code Demo on Github](https://github.com/lowcoder-org/lowcoder/tree/main/client/packages/lowcoder-plugin-demo) \ No newline at end of file +You can check a code demo here: [Code Demo on Github](https://github.com/lowcoder-org/lowcoder/tree/main/client/packages/lowcoder-plugin-demo) + +# Deployment of the Lowcoder Frontend to Netlify (Local Build Flow) + +## ⚙️ Prerequisites + +* Node.js & Yarn installed +* Netlify CLI installed: + +```bash +npm install -g netlify-cli +``` + +* Netlify CLI authenticated: + +```bash +netlify login +``` + +* The project is linked to the correct Netlify site: + +```bash +cd client +netlify link +``` + +--- + +## 🛠 Setup `netlify.toml` (only once) + +Inside the `client/` folder, create or update `netlify.toml`: + +```toml +[build] + base = "client" + command = "yarn workspace lowcoder build" + publish = "client/packages/lowcoder/build" +``` + +This ensures Netlify uses the correct build and publish paths when building locally. + +--- + +## 🚀 Deployment Steps + +1️⃣ Navigate into the `client` folder: + +```bash +cd client +``` + +2️⃣ Run local build (with Netlify environment variables injected): + +```bash +netlify build +``` + +3️⃣ Deploy to production: + +```bash +netlify deploy --prod --dir=packages/lowcoder/build +``` + +--- + +## 🔧 Notes + +* This local build flow fully honors the environment variables configured in Netlify. +* No build happens on Netlify servers — only the deploy step runs on Netlify. +* This approach avoids Netlify’s build memory limits. \ No newline at end of file From 9464ae361c41c08af4b872ad05299788c2e44fbf Mon Sep 17 00:00:00 2001 From: Thomasr Date: Tue, 3 Jun 2025 03:31:02 -0400 Subject: [PATCH 04/55] Resolved issue where plugins were not being detected. --- .../lowcoder/domain/plugin/client/DatasourcePluginClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java index cae767e6f5..aba0cacc09 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java @@ -138,7 +138,7 @@ public Mono executeQuery(String pluginName, Object queryDs String json = OBJECT_MAPPER.writeValueAsString(body); boolean encryptionEnabled = !(commonConfig.getJsExecutor().getPassword().isEmpty() || commonConfig.getJsExecutor().getSalt().isEmpty()); - String payload; + Object payload; WebClient.RequestBodySpec requestSpec = WEB_CLIENT .post() .uri(nodeServerHelper.createUri(RUN_PLUGIN_QUERY)) @@ -148,7 +148,7 @@ public Mono executeQuery(String pluginName, Object queryDs payload = encryptionService.encryptStringForNodeServer(json); requestSpec = requestSpec.header("X-Encrypted", "true"); } else { - payload = json; + payload = body; } return requestSpec From f84b565f20191797f50a9546d1b31dba46a49391 Mon Sep 17 00:00:00 2001 From: Kamal Qureshi Date: Tue, 3 Jun 2025 19:50:01 +0500 Subject: [PATCH 05/55] 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 cbdab8a0ff..bc1eaaf656 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 f0b83c4f53..0705a745b6 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 06/55] [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 d71ad03cb3..9055413de1 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 c82b7326a3..512329ee36 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 4ecd308ddd..b36f2acfcd 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 de76a4dd87..6162abea76 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 3d5096cc89..ba264c5e4f 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 07/55] 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 60e292d0dc..80e8e03403 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 47db799b84..f40f18c73d 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 08/55] 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 c2ab8801b2..fc25e03e75 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 09/55] [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 cf9e6d7929..4f083cc186 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 10/55] 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 0ce837560c..c9c8229ed4 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 e32c32c09f..4ddb7463d9 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 0628515d6b..5b69d23f0b 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 11/55] 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 36d1d7ce99..a345379877 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 b3dbe77c92..7866cb8134 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 fee2da5233..721f645657 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 12/55] [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 0f4f1e15f6..b7092b67b9 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 e32c32c09f..a0c58f1214 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 13/55] 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 c51a6858f9..2293236d65 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 2977ad4b98..5c98ddb89d 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 14/55] 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 4ddb7463d9..9e927331d2 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 15/55] 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 ee02ce0abf..d8520a9d54 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 6cd8d99fd6..3592f0a865 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 2de3af919f..955bb70bfa 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 16/55] 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 a8211777db..f7e2eaa677 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 a88b9debdd..c379004bc1 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 17/55] 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 7d5d26e131..fa53d2f506 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 58a7871aa3..bd7a5d0ffe 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 18/55] [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 f221b547db..78bba93807 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 c34b6dfbb9..a62704ff6d 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 b062f8fc46..d3d2041016 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 b36f2acfcd..3d35aa31d3 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 e8fcd9a4b2..17ad78efd3 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 6162abea76..ee15dda648 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 b7092b67b9..3bdbbed9dc 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 a345379877..aba5052526 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 a0c58f1214..0105c76854 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 19/55] 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 bbd39f73e8..f07de98cae 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 4cc2567c64..8f35bd4f4d 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 4fb21b69f5..d79fa542d2 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 ec4190bc6e..b0bd4d3dd6 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 c34b6dfbb9..11141281fa 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 9055413de1..d7efb3afff 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 512329ee36..183d87889b 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 b36f2acfcd..89ed76672f 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 6162abea76..a751b033ab 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 ba264c5e4f..0df62e2f0b 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 93b3d79ae0..73f776ac37 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 db45ba023b..e7743f0f8e 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 d8c26d7ad8..289a212eac 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 20/55] 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 11f5bd9535..7c13cdc578 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 21/55] 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 8512772fba..64e1847ab6 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 22/55] 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 c379004bc1..c88dde8eb8 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 23/55] 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 289a212eac..f3a751eb6e 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 24/55] 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 80e8e03403..037516d91b 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 25/55] 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 bd7a5d0ffe..56e4584c26 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 26/55] 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 f7e2eaa677..2b33bf3766 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 27/55] 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 99bee383e6..55168b1515 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 28/55] 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 f07de98cae..c9d5b9602b 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 8f35bd4f4d..9181c5c215 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 6f657c1e84..e7218dd70d 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 223650ef48..71fbbba204 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 d79fa542d2..8e7ab8dfc6 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 e50bd002ce..12a3da1985 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 b0bd4d3dd6..e82d2bab25 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 313358815a..90da85e8cb 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 78bba93807..af77fa4726 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 a33100154a..a8f864a775 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 d7efb3afff..cb426e2964 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 183d87889b..e285898d73 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 e62db2c746..ed5190c544 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 aba5052526..3bb7aeb503 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 0df62e2f0b..2a28f098cd 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 73f776ac37..b41ab1b61d 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 e7743f0f8e..ee5e162649 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 f3a751eb6e..b4b19d5228 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 0000000000..705321074d --- /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 29/55] 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 3bb7aeb503..c346c22d90 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 705321074d..47fd335da7 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 30/55] 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 d78f7d6ab9..fd4939da47 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 31/55] 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 512329ee36..b9c150c663 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 3d35aa31d3..7b574eda16 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 ba264c5e4f..a7c79032a6 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 7866cb8134..bfadcdb1dc 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 32/55] 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 bfadcdb1dc..938983ac9e 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 33/55] 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 e7218dd70d..1e44b5bbc1 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 47fd335da7..e8f64cc5a4 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 34/55] 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 a7c79032a6..3d5096cc89 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 35/55] 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 e3ce4633f1..9a5b43411d 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 36/55] 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 c11d42e9cb..2c7f0de92e 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 37/55] [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 b9c150c663..3ad4bf275f 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 7b574eda16..641c9adf90 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 3d5096cc89..924f1ae0c7 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 38/55] 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 9a5b43411d..252ecaf1b3 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 39/55] 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 a7c79032a6..4fb5b7a85f 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 40/55] 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 4fb5b7a85f..0abadf38f2 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 41/55] 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 924f1ae0c7..0abadf38f2 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 42/55] 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 3ad4bf275f..c82b7326a3 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 43/55] 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 c82b7326a3..a82a760e7f 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 44/55] 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 641c9adf90..4ecd308ddd 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 45/55] 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 4ecd308ddd..e707eab432 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 46/55] 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 c9d5b9602b..aac9949a0a 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 9181c5c215..f370a4ef99 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 1e44b5bbc1..70a8de5d83 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 71fbbba204..358a1e6ff2 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 8e7ab8dfc6..c20cb793ba 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 12a3da1985..6e49b429ae 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 e82d2bab25..80d2ba77b0 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 90da85e8cb..0445c94039 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 af77fa4726..619b42674f 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 a8f864a775..f85863e01b 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 cb426e2964..7dd3bf2424 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 3be2c5db95..e93b3082a6 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 c346c22d90..80b8e89811 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 2da1fbe7e9..8ec51c6a1a 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 b41ab1b61d..41b8ee09e3 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 ee5e162649..06e1ff1a4e 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 47/55] - 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 aac9949a0a..94e24d59a4 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 c20cb793ba..f3b14959c9 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 80d2ba77b0..8bc246a2b1 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 f85863e01b..f02ee19943 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 7dd3bf2424..b78601a5fa 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 5c532836a7..b54be87997 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 80b8e89811..dcdffe3907 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 41b8ee09e3..dcc5ccdb2b 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 48/55] 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 2901aeb0dc..c87732d35c 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 5853bfcf25..2a5b0d0c30 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 55221cd71b..f73758127d 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 86ed6888b2..6fee2a511f 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 49/55] 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 0e6a228d39..739e87723f 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 50/55] 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 2d930fbf44..73fa06c0da 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 762bb47743..3d4504942c 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 51/55] 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 3592f0a865..362b688635 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 52/55] 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 7fceace3e9..d6606fde20 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 6b375d4d2d..fa9b0cd5e2 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 781ffe257d..39c26d9906 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 362b688635..f3485477e3 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 53/55] 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 9aa34646dc..fbafd6b600 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 1d539f23bc..12f93a4aa7 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 2819fd79ce..613c8ee3db 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 e901e98aac..7cc5d5ea49 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 f25be12c32..e838c870d8 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 50e8c157ff..972c236198 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 50ba90832e..db8736ac0b 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 54/55] 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 94453db48b..b033d52e92 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 9da9202609..2d8c822e2c 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 17d38343d0..4df6ce4720 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 c88dde8eb8..de24d5b64a 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 6062930932..44623f20ee 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 bb2833901e..c6bdbd7236 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 a4d094bb76..c512002273 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 806eea60ee..5ce0435c35 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 797ed9354f..69555a8d1d 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 c9db2e1fe1..0075e23b88 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 4dd184f2eb..4faaf6a3fb 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 c8e1e81e80..d17d72f417 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 552b191175..edbc3840a5 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 410842f4c9..9c1d34fcba 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 1b77787084..967b8d70a1 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 354ede1bbd..c517570d04 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 0017e598fc..548a72cd71 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 e2ae339ed3..f892755e29 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 55/55] 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 b9266b7bd5..267fb8fb36 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 7b3bf927d0..41fb54f3d8 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 254eca6e87..2cb8c22f93 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