diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml index d075f1fdce..be06cf1a4b 100644 --- a/.github/workflows/docker-images.yml +++ b/.github/workflows/docker-images.yml @@ -80,18 +80,21 @@ jobs: # Image names ALLINONE_IMAGE_NAMES=lowcoderorg/lowcoder-ce:${IMAGE_TAG} FRONTEND_IMAGE_NAMES=lowcoderorg/lowcoder-ce-frontend:${IMAGE_TAG} + FRONTEND_EE_IMAGE_NAMES=lowcoderorg/lowcoder-enterprise-frontend:${IMAGE_TAG} APISERVICE_IMAGE_NAMES=lowcoderorg/lowcoder-ce-api-service:${IMAGE_TAG} NODESERVICE_IMAGE_NAMES=lowcoderorg/lowcoder-ce-node-service:${IMAGE_TAG} if [[ "${IS_LATEST}" == "true" ]]; then ALLINONE_IMAGE_NAMES="lowcoderorg/lowcoder-ce:latest,${ALLINONE_IMAGE_NAMES}" FRONTEND_IMAGE_NAMES="lowcoderorg/lowcoder-ce-frontend:latest,${FRONTEND_IMAGE_NAMES}" + FRONTEND_EE_IMAGE_NAMES="lowcoderorg/lowcoder-enterprise-frontend:latest,${FRONTEND_EE_IMAGE_NAMES}" APISERVICE_IMAGE_NAMES="lowcoderorg/lowcoder-ce-api-service:latest,${APISERVICE_IMAGE_NAMES}" NODESERVICE_IMAGE_NAMES="lowcoderorg/lowcoder-ce-node-service:latest,${NODESERVICE_IMAGE_NAMES}" fi; echo "ALLINONE_IMAGE_NAMES=${ALLINONE_IMAGE_NAMES}" >> "${GITHUB_ENV}" echo "FRONTEND_IMAGE_NAMES=${FRONTEND_IMAGE_NAMES}" >> "${GITHUB_ENV}" + echo "FRONTEND_EE_IMAGE_NAMES=${FRONTEND_EE_IMAGE_NAMES}" >> "${GITHUB_ENV}" echo "APISERVICE_IMAGE_NAMES=${APISERVICE_IMAGE_NAMES}" >> "${GITHUB_ENV}" echo "NODESERVICE_IMAGE_NAMES=${NODESERVICE_IMAGE_NAMES}" >> "${GITHUB_ENV}" @@ -146,6 +149,24 @@ jobs: push: true tags: ${{ env.FRONTEND_IMAGE_NAMES }} + - name: Build and push the enterprise edition frontend image + if: ${{ env.BUILD_FRONTEND == 'true' }} + uses: docker/build-push-action@v6 + env: + NODE_ENV: production + with: + file: ./deploy/docker/Dockerfile + target: lowcoder-enterprise-frontend + build-args: | + REACT_APP_ENV=production + REACT_APP_EDITION=enterprise + REACT_APP_COMMIT_ID="dev #${{ env.SHORT_SHA }}" + platforms: | + linux/amd64 + linux/arm64 + push: true + tags: ${{ env.FRONTEND_EE_IMAGE_NAMES }} + - name: Build and push the node service image if: ${{ env.BUILD_NODESERVICE == 'true' }} uses: docker/build-push-action@v6 diff --git a/client/VERSION b/client/VERSION index fbafd6b600..460b6fd404 100644 --- a/client/VERSION +++ b/client/VERSION @@ -1 +1 @@ -2.7.2 \ No newline at end of file +2.7.5 \ No newline at end of file diff --git a/client/package.json b/client/package.json index 12f93a4aa7..4b58716c6f 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-frontend", - "version": "2.7.2", + "version": "2.7.5", "type": "module", "private": true, "workspaces": [ @@ -15,6 +15,7 @@ "start:ee": "REACT_APP_EDITION=enterprise yarn workspace lowcoder start", "translate": "node --loader ts-node/esm ./scripts/translate.js", "build": "yarn node ./scripts/build.js", + "build:ee": "REACT_APP_EDITION=enterprise yarn node ./scripts/build.js", "test": "jest && yarn workspace lowcoder-comps test", "prepare": "yarn workspace lowcoder prepare", "build:core": "yarn workspace lowcoder-core build", diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index 613c8ee3db..9dbd281533 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.2", + "version": "2.7.5", "type": "module", "license": "MIT", "dependencies": { diff --git a/client/packages/lowcoder-comps/src/comps/barChartComp/barChartComp.tsx b/client/packages/lowcoder-comps/src/comps/barChartComp/barChartComp.tsx index df7fc06232..7b64f0f6c5 100644 --- a/client/packages/lowcoder-comps/src/comps/barChartComp/barChartComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/barChartComp/barChartComp.tsx @@ -61,6 +61,8 @@ BarChartTmpComp = withViewFn(BarChartTmpComp, (comp) => { const [chartSize, setChartSize] = useState(); const firstResize = useRef(true); const theme = useContext(ThemeContext); + const [chartKey, setChartKey] = useState(0); + const prevRaceMode = useRef(); const defaultChartTheme = { color: chartColorPalette, backgroundColor: "#fff", @@ -73,6 +75,16 @@ BarChartTmpComp = withViewFn(BarChartTmpComp, (comp) => { log.error('theme chart error: ', error); } + // Detect race mode changes and force chart recreation + const currentRaceMode = comp.children.chartConfig?.children?.comp?.children?.race?.getView(); + useEffect(() => { + if (prevRaceMode.current !== undefined && prevRaceMode.current !== currentRaceMode) { + // Force chart recreation when race mode changes + setChartKey(prev => prev + 1); + } + prevRaceMode.current = currentRaceMode; + }, [currentRaceMode]); + const triggerClickEvent = async (dispatch: any, action: CompAction) => { await getPromiseAfterDispatch( dispatch, @@ -176,10 +188,11 @@ BarChartTmpComp = withViewFn(BarChartTmpComp, (comp) => { return (
(echartsCompRef.current = e)} style={{ height: "100%" }} - notMerge - lazyUpdate + notMerge={!currentRaceMode} + lazyUpdate={!currentRaceMode} opts={{ locale: getEchartsLocale() }} option={option} mode={mode} diff --git a/client/packages/lowcoder-comps/src/comps/barChartComp/barChartUtils.ts b/client/packages/lowcoder-comps/src/comps/barChartComp/barChartUtils.ts index 72abe79f77..84b4ea05c6 100644 --- a/client/packages/lowcoder-comps/src/comps/barChartComp/barChartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/barChartComp/barChartUtils.ts @@ -201,6 +201,15 @@ export function getEchartsConfig( animationEasing: 'linear', animationEasingUpdate: 'linear', } + } else { + // Ensure proper animation settings when race is disabled + config = { + ...config, + animationDuration: 1000, + animationDurationUpdate: 1000, + animationEasing: 'cubicOut', + animationEasingUpdate: 'cubicOut', + } } if (props.data.length <= 0) { // no data @@ -333,6 +342,21 @@ export function getEchartsConfig( animationDurationUpdate: 300 }, } + } else { + // Reset axis animations when race is disabled + config = { + ...config, + xAxis: { + ...config.xAxis, + animationDuration: undefined, + animationDurationUpdate: undefined + }, + yAxis: { + ...config.yAxis, + animationDuration: undefined, + animationDurationUpdate: undefined + }, + } } } // console.log("Echarts transformedData and config", transformedData, config); diff --git a/client/packages/lowcoder-comps/src/comps/basicChartComp/chartConfigs/barChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/basicChartComp/chartConfigs/barChartConfig.tsx index dd7a369934..d1450007a6 100644 --- a/client/packages/lowcoder-comps/src/comps/basicChartComp/chartConfigs/barChartConfig.tsx +++ b/client/packages/lowcoder-comps/src/comps/basicChartComp/chartConfigs/barChartConfig.tsx @@ -53,7 +53,7 @@ export const BarChartConfig = (function () { type: "bar", subtype: props.type, realtimeSort: props.race, - seriesLayoutBy: props.race?'column':undefined, + seriesLayoutBy: props.race?'column':'row', label: { show: props.showLabel, position: "top", diff --git a/client/packages/lowcoder-comps/src/comps/calendarComp/calendarComp.tsx b/client/packages/lowcoder-comps/src/comps/calendarComp/calendarComp.tsx index 43ddfbaf30..9bf7a4a6c7 100644 --- a/client/packages/lowcoder-comps/src/comps/calendarComp/calendarComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/calendarComp/calendarComp.tsx @@ -268,6 +268,7 @@ let CalendarBasicComp = (function () { const ref = createRef(); const editEvent = useRef(); const initData = useRef(false); + const clickTimeout = useRef(null); const [form] = Form.useForm(); const [left, setLeft] = useState(undefined); const [licensed, setLicensed] = useState(props.licenseKey !== ""); @@ -370,6 +371,15 @@ let CalendarBasicComp = (function () { initData.current = true; } }, [JSON.stringify(initialEvents), comp?.children?.comp?.children?.initialData]); + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (clickTimeout.current) { + clearTimeout(clickTimeout.current); + } + }; + }, []); const resources = useMemo(() => props.resources.value, [props.resources.value]); @@ -850,22 +860,30 @@ let CalendarBasicComp = (function () { handleEventDataChange, ]); + const handleSingleClick = useCallback(() => { + // Prevent double click from triggering the event + // Use a timeout to debounce rapid clicks + if (clickTimeout.current) { + clearTimeout(clickTimeout.current); + clickTimeout.current = null; + return; // This was a double click, don't trigger + } + + clickTimeout.current = setTimeout(() => { + props.onEvent('click'); + clickTimeout.current = null; + }, 150); // Small delay to catch double clicks + }, [props.onEvent]); + const handleDbClick = useCallback(() => { - const event = props.updatedEventsData.find( - (item: EventType) => item.id === editEvent.current?.id - ) as EventType; if (!props.editable || !editEvent.current) { return; } - if (event) { - showModal(event, true); + if (onEventVal && onEventVal.some((e: any) => e.name === 'doubleClick')) { + // Check if 'doubleClick' is included in the array + props.onEvent('doubleClick'); } else { - if (onEventVal && onEventVal.some((e: any) => e.name === 'doubleClick')) { - // Check if 'doubleClick' is included in the array - props.onEvent('doubleClick'); - } else { - showModal(editEvent.current as EventType, false); - } + showModal(editEvent.current as EventType, false); } }, [ editEvent, @@ -974,6 +992,9 @@ let CalendarBasicComp = (function () { allDaySlot={props.showAllDay} eventContent={renderEventContent} select={(info) => handleCreate(info)} + dateClick={() => { + handleSingleClick(); + }} eventClick={(info) => { const event = events.find( (item: EventInput) => item.id === info.event.id @@ -982,6 +1003,7 @@ let CalendarBasicComp = (function () { setTimeout(() => { editEvent.current = undefined; }, 500); + handleSingleClick(); }} moreLinkClick={(info) => { let left = 0; diff --git a/client/packages/lowcoder-core/lib/index.js b/client/packages/lowcoder-core/lib/index.js index f972c7ba4b..719756c00b 100644 --- a/client/packages/lowcoder-core/lib/index.js +++ b/client/packages/lowcoder-core/lib/index.js @@ -1692,11 +1692,12 @@ class CodeNode extends AbstractNode { // if query is dependent on itself, mark as ready if (pathsArr?.[0] === options?.queryName) return; + // TODO: check if this is needed after removing lazy load // wait for lazy loaded comps to load before executing query on page load - if (value && !Object.keys(value).length && paths.size) { - isFetching = true; - ready = false; - } + // if (value && !Object.keys(value).length && paths.size) { + // isFetching = true; + // ready = false; + // } if (_.has(value, IS_FETCHING_FIELD)) { isFetching = isFetching || value.isFetching === true; } diff --git a/client/packages/lowcoder-core/src/eval/codeNode.tsx b/client/packages/lowcoder-core/src/eval/codeNode.tsx index 2b67e7bbfa..e2e69fab1b 100644 --- a/client/packages/lowcoder-core/src/eval/codeNode.tsx +++ b/client/packages/lowcoder-core/src/eval/codeNode.tsx @@ -176,11 +176,12 @@ export class CodeNode extends AbstractNode> { // if query is dependent on itself, mark as ready if (pathsArr?.[0] === options?.queryName) return; + // TODO: check if this is needed after removing lazy load // wait for lazy loaded comps to load before executing query on page load - if (value && !Object.keys(value).length && paths.size) { - isFetching = true; - ready = false; - } + // if (value && !Object.keys(value).length && paths.size) { + // isFetching = true; + // ready = false; + // } if (_.has(value, IS_FETCHING_FIELD)) { isFetching = isFetching || value.isFetching === true; } diff --git a/client/packages/lowcoder-design/src/components/colorSelect/colorUtils.ts b/client/packages/lowcoder-design/src/components/colorSelect/colorUtils.ts index 94621fcb16..d0f0e7d6f6 100644 --- a/client/packages/lowcoder-design/src/components/colorSelect/colorUtils.ts +++ b/client/packages/lowcoder-design/src/components/colorSelect/colorUtils.ts @@ -75,6 +75,26 @@ const isValidColor = (str?: string) => { return colord(str).isValid(); }; +const isTransparentColor = (color?: string) => { + if (!color) return true; + + // Check for common transparent values + if (color === 'transparent' || color === '') return true; + + // Check if it's a valid color with alpha = 0 + try { + const colorObj = colord(color); + if (colorObj.isValid()) { + return colorObj.alpha() === 0; + } + } catch (e) { + // If colord can't parse it, consider it transparent + return true; + } + + return false; +}; + export const isDarkColor = (colorStr: string) => { return brightnessCompare(colorStr, 0.75); }; @@ -122,4 +142,4 @@ export const darkenColor = (colorStr: string, intensity: number) => { return color.darken(intensity).toHex().toUpperCase(); }; -export { toRGBA, toHex, alphaOfRgba, isValidColor, isValidGradient }; +export { toRGBA, toHex, alphaOfRgba, isValidColor, isValidGradient, isTransparentColor }; diff --git a/client/packages/lowcoder-design/src/components/customSelect.tsx b/client/packages/lowcoder-design/src/components/customSelect.tsx index 2f13f0db8e..72864178ad 100644 --- a/client/packages/lowcoder-design/src/components/customSelect.tsx +++ b/client/packages/lowcoder-design/src/components/customSelect.tsx @@ -20,7 +20,8 @@ const SelectWrapper = styled.div<{ $border?: boolean }>` padding: ${(props) => (props.$border ? "0px" : "0 0 0 12px")}; height: 100%; align-items: center; - margin-right: 8px; + margin-right: 10px; + padding-right: 5px; background-color: #fff; .ant-select-selection-item { @@ -46,9 +47,9 @@ const SelectWrapper = styled.div<{ $border?: boolean }>` } .ant-select-arrow { - width: 20px; - height: 20px; - right: 8px; + width: 17px; + height: 17px; + right: 10px; top: 0; bottom: 0; margin: auto; diff --git a/client/packages/lowcoder-design/src/icons/index.tsx b/client/packages/lowcoder-design/src/icons/index.tsx index b033d52e92..9c00866feb 100644 --- a/client/packages/lowcoder-design/src/icons/index.tsx +++ b/client/packages/lowcoder-design/src/icons/index.tsx @@ -355,6 +355,7 @@ export { ReactComponent as VideoCameraStreamCompIconSmall } from "./v2/camera-st export { ReactComponent as VideoScreenshareCompIconSmall } from "./v2/screen-share-stream-s.svg"; // new export { ReactComponent as SignatureCompIconSmall } from "./v2/signature-s.svg"; export { ReactComponent as StepCompIconSmall } from "./v2/steps-s.svg"; +export { ReactComponent as TagsCompIconSmall } from "./v2/tags-s.svg" export { ReactComponent as CandlestickChartCompIconSmall } from "./v2/candlestick-chart-s.svg"; // new @@ -468,6 +469,7 @@ export { ReactComponent as SignatureCompIcon } from "./v2/signature-m.svg"; export { ReactComponent as GanttCompIcon } from "./v2/gantt-chart-m.svg"; export { ReactComponent as KanbanCompIconSmall } from "./v2/kanban-s.svg"; export { ReactComponent as KanbanCompIcon } from "./v2/kanban-m.svg"; +export { ReactComponent as TagsCompIcon } from "./v2/tags-l.svg"; export { ReactComponent as CandlestickChartCompIcon } from "./v2/candlestick-chart-m.svg"; export { ReactComponent as FunnelChartCompIcon } from "./v2/funnel-chart-m.svg"; diff --git a/client/packages/lowcoder-design/src/icons/v2/tags-l.svg b/client/packages/lowcoder-design/src/icons/v2/tags-l.svg new file mode 100644 index 0000000000..cd1d0368c3 --- /dev/null +++ b/client/packages/lowcoder-design/src/icons/v2/tags-l.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/client/packages/lowcoder-design/src/icons/v2/tags-s.svg b/client/packages/lowcoder-design/src/icons/v2/tags-s.svg new file mode 100644 index 0000000000..d45fcb0aa8 --- /dev/null +++ b/client/packages/lowcoder-design/src/icons/v2/tags-s.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/client/packages/lowcoder-sdk-webpack-bundle/package.json b/client/packages/lowcoder-sdk-webpack-bundle/package.json index 267fb8fb36..e83e77363d 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.2", + "version": "2.7.5", "main": "index.jsx", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", diff --git a/client/packages/lowcoder-sdk/package.json b/client/packages/lowcoder-sdk/package.json index 7cc5d5ea49..522d92b258 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.2", + "version": "2.7.5", "type": "module", "files": [ "src", diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json index e838c870d8..418d72a392 100644 --- a/client/packages/lowcoder/package.json +++ b/client/packages/lowcoder/package.json @@ -1,12 +1,13 @@ { "name": "lowcoder", - "version": "2.7.2", + "version": "2.7.5", "private": true, "type": "module", "main": "src/index.sdk.ts", "types": "src/index.sdk.ts", "dependencies": { "@ant-design/icons": "^5.3.0", + "@bany/curl-to-json": "^1.2.8", "@codemirror/autocomplete": "^6.11.1", "@codemirror/commands": "^6.3.2", "@codemirror/lang-css": "^6.2.1", diff --git a/client/packages/lowcoder/src/api/applicationApi.ts b/client/packages/lowcoder/src/api/applicationApi.ts index 2411b50d80..8ed818b371 100644 --- a/client/packages/lowcoder/src/api/applicationApi.ts +++ b/client/packages/lowcoder/src/api/applicationApi.ts @@ -99,6 +99,7 @@ class ApplicationApi extends Api { static publicToMarketplaceURL = (applicationId: string) => `/applications/${applicationId}/public-to-marketplace`; static getMarketplaceAppURL = (applicationId: string) => `/applications/${applicationId}/view_marketplace`; static setAppEditingStateURL = (applicationId: string) => `/applications/editState/${applicationId}`; + static getAvailableGroupsMembersURL = (applicationId: string) => `/applications/${applicationId}/groups-members/available`; static serverSettingsURL = () => `/serverSettings`; static fetchHomeData(request: HomeDataPayload): AxiosPromise { @@ -217,6 +218,10 @@ class ApplicationApi extends Api { }); } + static getAvailableGroupsMembers(applicationId: string, search: string): AxiosPromise { + return Api.get(ApplicationApi.getAvailableGroupsMembersURL(applicationId), {search}) + } + /** * set app as public */ diff --git a/client/packages/lowcoder/src/api/commonSettingApi.ts b/client/packages/lowcoder/src/api/commonSettingApi.ts index b2f746454b..226617d96c 100644 --- a/client/packages/lowcoder/src/api/commonSettingApi.ts +++ b/client/packages/lowcoder/src/api/commonSettingApi.ts @@ -123,7 +123,7 @@ export function isThemeColorKey(key: string) { case "padding": case "gridColumns": case "textSize": - case "lineHeight": + case "lineHeight": return true; } return false; diff --git a/client/packages/lowcoder/src/api/datasourceApi.ts b/client/packages/lowcoder/src/api/datasourceApi.ts index 1be29e6469..278015a124 100644 --- a/client/packages/lowcoder/src/api/datasourceApi.ts +++ b/client/packages/lowcoder/src/api/datasourceApi.ts @@ -187,6 +187,10 @@ export class DatasourceApi extends Api { return Api.get(DatasourceApi.url + `/listByOrg?orgId=${orgId}`, {...res}); } + static getDatasourceById(id: string): AxiosPromise> { + return Api.get(`${DatasourceApi.url}/${id}`); + } + static createDatasource( datasourceConfig: Partial ): AxiosPromise> { diff --git a/client/packages/lowcoder/src/api/orgApi.ts b/client/packages/lowcoder/src/api/orgApi.ts index 588a20df51..379234b32e 100644 --- a/client/packages/lowcoder/src/api/orgApi.ts +++ b/client/packages/lowcoder/src/api/orgApi.ts @@ -62,6 +62,7 @@ export class OrgApi extends Api { static updateOrgURL = (orgId: string) => `/organizations/${orgId}/update`; static fetchUsage = (orgId: string) => `/organizations/${orgId}/api-usage`; static fetchOrgsByEmailURL = (email: string) => `organizations/byuser/${email}`; + static fetchGroupPotentialMembersURL = (groupId: string) => `/groups/${groupId}/potential-members`; static createGroup(request: { name: string }): AxiosPromise> { return Api.post(OrgApi.createGroupURL, request); @@ -110,6 +111,10 @@ export class OrgApi extends Api { return Api.get(OrgApi.fetchGroupUsersURL(groupId)); } + static fetchGroupPotentialMembers(searchName: string, groupId: string): AxiosPromise { + return Api.get(OrgApi.fetchGroupPotentialMembersURL(groupId), {searchName}) + } + static fetchGroupUsersPagination(request: fetchGroupUserRequestType): AxiosPromise { const {groupId, ...res} = request; return Api.get(OrgApi.fetchGroupUsersURL(groupId), {...res}); diff --git a/client/packages/lowcoder/src/api/userApi.ts b/client/packages/lowcoder/src/api/userApi.ts index c80d4b19dd..a65a72338c 100644 --- a/client/packages/lowcoder/src/api/userApi.ts +++ b/client/packages/lowcoder/src/api/userApi.ts @@ -1,6 +1,6 @@ import Api from "api/api"; import { AxiosPromise } from "axios"; -import { OrgAndRole } from "constants/orgConstants"; +import { Org, OrgAndRole } from "constants/orgConstants"; import { BaseUserInfo, CurrentUser } from "constants/userConstants"; import { MarkUserStatusPayload, UpdateUserPayload } from "redux/reduxActions/userActions"; import { ApiResponse, GenericApiResponse } from "./apiResponses"; @@ -60,10 +60,28 @@ export interface FetchApiKeysResponse extends ApiResponse { export type GetCurrentUserResponse = GenericApiResponse; +export interface GetMyOrgsResponse extends ApiResponse { + data: { + data: Array<{ + isCurrentOrg: boolean; + orgView: { + orgId: string; + orgName: string; + createdAt?: number; + updatedAt?: number; + }; + }>; + pageNum: number; + pageSize: number; + total: number; + }; +} + class UserApi extends Api { static thirdPartyLoginURL = "/auth/tp/login"; static thirdPartyBindURL = "/auth/tp/bind"; static usersURL = "/users"; + static myOrgsURL = "/users/myorg"; static sendVerifyCodeURL = "/auth/otp/send"; static logoutURL = "/auth/logout"; static userURL = "/users/me"; @@ -127,6 +145,19 @@ class UserApi extends Api { static getCurrentUser(): AxiosPromise { return Api.get(UserApi.currentUserURL); } + static getMyOrgs( + pageNum: number = 1, + pageSize: number = 20, + orgName?: string + ): AxiosPromise { + const params = new URLSearchParams({ + pageNum: pageNum.toString(), + pageSize: pageSize.toString(), + ...(orgName && { orgName }) + }); + + return Api.get(`${UserApi.myOrgsURL}?${params}`); + } static getRawCurrentUser(): AxiosPromise { return Api.get(UserApi.rawCurrentUserURL); diff --git a/client/packages/lowcoder/src/app.tsx b/client/packages/lowcoder/src/app.tsx index 1fb49720d4..a4857882ee 100644 --- a/client/packages/lowcoder/src/app.tsx +++ b/client/packages/lowcoder/src/app.tsx @@ -60,7 +60,6 @@ import GlobalInstances from 'components/GlobalInstances'; import { fetchHomeData, fetchServerSettingsAction } from "./redux/reduxActions/applicationActions"; import { getNpmPackageMeta } from "./comps/utils/remote"; import { packageMetaReadyAction, setLowcoderCompsLoading } from "./redux/reduxActions/npmPluginActions"; -import { fetchBrandingSetting } from "./redux/reduxActions/enterpriseActions"; import { EnterpriseProvider } from "./util/context/EnterpriseContext"; import { SimpleSubscriptionContextProvider } from "./util/context/SimpleSubscriptionContext"; import { getBrandingSetting } from "./redux/selectors/enterpriseSelectors"; @@ -137,7 +136,6 @@ type AppIndexProps = { defaultHomePage: string | null | undefined; fetchHomeDataFinished: boolean; fetchConfig: (orgId?: string) => void; - fetchBrandingSetting: (orgId?: string) => void; fetchHomeData: (currentUserAnonymous?: boolean | undefined) => void; fetchLowcoderCompVersions: () => void; getCurrentUser: () => void; @@ -167,7 +165,6 @@ class AppIndex extends React.Component { if (!this.props.currentUserAnonymous) { this.props.fetchHomeData(this.props.currentUserAnonymous); this.props.fetchLowcoderCompVersions(); - this.props.fetchBrandingSetting(this.props.currentOrgId); } } } @@ -521,7 +518,6 @@ const mapDispatchToProps = (dispatch: any) => ({ fetchHomeData: (currentUserAnonymous: boolean | undefined) => { dispatch(fetchHomeData({})); }, - fetchBrandingSetting: (orgId?: string) => dispatch(fetchBrandingSetting({ orgId, fallbackToGlobal: true })), fetchLowcoderCompVersions: async () => { try { dispatch(setLowcoderCompsLoading(true)); diff --git a/client/packages/lowcoder/src/base/codeEditor/extensions.tsx b/client/packages/lowcoder/src/base/codeEditor/extensions.tsx index 3682cf4e04..cd36cb9c28 100644 --- a/client/packages/lowcoder/src/base/codeEditor/extensions.tsx +++ b/client/packages/lowcoder/src/base/codeEditor/extensions.tsx @@ -27,7 +27,7 @@ import { foldKeymap, indentOnInput, } from "@codemirror/language"; -import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands"; +import { defaultKeymap, history, historyKeymap, insertTab, indentLess, indentMore } from "@codemirror/commands"; import { highlightSelectionMatches, searchKeymap } from "@codemirror/search"; import { Diagnostic, linter, lintKeymap } from "@codemirror/lint"; import { type EditorState, Prec } from "@codemirror/state"; @@ -282,7 +282,20 @@ export function useFocusExtension(onFocus?: (focused: boolean) => void): [Extens } function indentWithTabExtension(open?: boolean) { - return open ?? true ? keymap.of([indentWithTab]) : []; + if (!(open ?? true)) return []; + return keymap.of([ + { + key: "Tab", + run: (view: EditorView) => { + const { main } = view.state.selection; + if (!main.empty && main.from !== main.to) { + return indentMore(view); + } + return insertTab(view); + }, + }, + { key: "Shift-Tab", run: indentLess }, + ]); } export function lineNoExtension(showLineNumber?: boolean) { @@ -493,26 +506,26 @@ export function useExtensions(props: CodeEditorProps) { basicSetup, defaultTheme, highlightJsExt, - autocompletionExtension, focusExtension, lineNoExt, languageExt, onChangeExt, placeholderExt, indentWithTabExt, + autocompletionExtension, tooltipExt, lintExt, iconExt, ], [ highlightJsExt, - autocompletionExtension, focusExtension, lineNoExt, languageExt, onChangeExt, placeholderExt, indentWithTabExt, + autocompletionExtension, tooltipExt, lintExt, iconExt, diff --git a/client/packages/lowcoder/src/components/CurlImport.tsx b/client/packages/lowcoder/src/components/CurlImport.tsx new file mode 100644 index 0000000000..a15d54bbf3 --- /dev/null +++ b/client/packages/lowcoder/src/components/CurlImport.tsx @@ -0,0 +1,97 @@ +import React, { useState } from "react"; +import { Modal, Input, Button, message } from "antd"; +import { trans } from "i18n"; +import parseCurl from "@bany/curl-to-json"; +const { TextArea } = Input; +interface CurlImportModalProps { + open: boolean; + onCancel: () => void; + onSuccess: (parsedData: any) => void; +} + +export function CurlImportModal(props: CurlImportModalProps) { + const { open, onCancel, onSuccess } = props; + const [curlCommand, setCurlCommand] = useState(""); + const [loading, setLoading] = useState(false); + + const handleImport = async () => { + if (!curlCommand.trim()) { + message.error("Please enter a cURL command"); + return; + } + + setLoading(true); + try { + // Parse the cURL command using the correct import + const parsedData = parseCurl(curlCommand); + + + + // Log the result for now as requested + // console.log("Parsed cURL data:", parsedData); + + // Call success callback with parsed data + onSuccess(parsedData); + + // Reset form and close modal + setCurlCommand(""); + onCancel(); + + message.success("cURL command imported successfully!"); + } catch (error: any) { + console.error("Error parsing cURL command:", error); + message.error(`Failed to parse cURL command: ${error.message}`); + } finally { + setLoading(false); + } + }; + + const handleCancel = () => { + setCurlCommand(""); + onCancel(); + }; + + return ( + + Cancel + , + , + ]} + width={600} + > +
+
+ Paste cURL Command Here +
+
+
+ Examples: +
+
+ GET: curl -X GET https://jsonplaceholder.typicode.com/posts/1 +
+
+ POST: curl -X POST https://jsonplaceholder.typicode.com/posts -H "Content-Type: application/json" -d '{"title":"foo","body":"bar","userId":1}' +
+
+ Users: curl -X GET https://jsonplaceholder.typicode.com/users +
+
+