diff --git a/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx b/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx index d70f911c8..85a85d0c0 100644 --- a/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx @@ -26,6 +26,7 @@ import { CompAction, CompActionTypes, ConstructorToView, + CustomAction, evalNodeOrMinor, updateNodesV2Action, } from "lowcoder-core"; @@ -42,8 +43,14 @@ import { toStringNumberArray, } from "util/convertUtils"; import { JSONObject, JSONValue } from "util/jsonTypes"; -import { getPromiseAfterDispatch } from "../../util/promiseUtils"; -import { EvalParamType, ParamConfig, ParamType } from "./actionSelector/executeCompTypes"; +import { getPromiseAfterDispatch, handlePromiseAfterResult } from "../../util/promiseUtils"; +import { + EvalParamType, + ExecuteAction, + MethodConfig, + ParamConfig, + ParamType, +} from "./actionSelector/executeCompTypes"; export const __TMP_STATE_FIELD_NAME = "__TMP_STATE_FIELD_NAME"; @@ -173,19 +180,76 @@ export function withLinkedDefaultValue< ): TCtor { class LinkedDefaultValueComp extends (Base as any) { reduce(action: CompAction): this { - const ret = super.reduce(action) as this; - if ( - action.type === CompActionTypes.UPDATE_NODES_V2 && - (ret.children as any)[defaultValueKey] !== (this.children as any)[defaultValueKey] - ) { - const defaultValue = (ret.children as any)[defaultValueKey].getView().value; - const valueChild = (ret.children as any)[valueKey]; - const newValue = transformDefaultValue ? transformDefaultValue(defaultValue, ret) : defaultValue; - if (!_.isEqual(newValue, valueChild.getView().value)) { - return (ret as any).setChild(valueKey, valueChild.reduce(changeChildAction("value", newValue, false))); - } + const updated = super.reduce(action) as this; + + const defaultValueFieldBefore = (this.children as any)[defaultValueKey]; + const defaultValueFieldAfter = (updated.children as any)[defaultValueKey]; + if (defaultValueFieldAfter === defaultValueFieldBefore) { + return updated; + } + + const defaultBefore = defaultValueFieldBefore.getView().value; + const defaultAfter = defaultValueFieldAfter.getView().value; + if (_.isEqual(defaultBefore, defaultAfter)) { + return updated; + } + + const currentValueField = (updated.children as any)[valueKey]; + + let newValue = defaultAfter; + if (transformDefaultValue) { + newValue = transformDefaultValue(newValue, updated); + } + if (_.isEqual(newValue, currentValueField.getView().value)) { + return updated; + } + + const setValueMessage = changeChildAction("value", newValue, false); + + if (action.type === CompActionTypes.UPDATE_NODES_V2) { + setTimeout(currentValueField.dispatch, 0, setValueMessage); + return updated; } - return ret; + + const updatedCurrentValueField = currentValueField.reduce(setValueMessage); + return (updated as any).setChild(valueKey, updatedCurrentValueField); + } + + // Someone called a method on this component, e.g. switch1.resetValue(). + // "call" holds the method's name and its arguments. + // Returns true if this function took care of the call. + executeMethod(call: CustomAction): boolean { + const calledMethodName = call.value.methodName; + + // What the component shows right now (e.g. the switch is on or off). + const currentValueField = (this.children as any)[valueKey]; + + // What was entered as "Default value" in the property panel. + const defaultValueField = (this.children as any)[defaultValueKey]; + + // The current value field offers three methods: set…, clear… and reset… + // (e.g. setValue, clearValue, resetValue). Find the name of the reset one. + const currentValueMethods: MethodConfig[] = currentValueField.getMethodConfig(); + const resetMethod = currentValueMethods.find((method) => method.name.startsWith("reset")); + + if (calledMethodName !== resetMethod?.name) { + // Any other method (setValue, clearValue, focus, ...): handle it as before. + return super.executeMethod(call); + } + + // The current value field's own reset would read a default of its own, + // which is always empty. Take the one from the property panel instead. + let defaultValue = defaultValueField.getView().value; + if (transformDefaultValue) { + defaultValue = transformDefaultValue(defaultValue, this); + } + + // Same as calling setValue(defaultValue). + const doneWhenChanged = currentValueField.change(defaultValue); + + // Lets the caller wait for the change, e.g. "await switch1.resetValue()". + handlePromiseAfterResult(call, doneWhenChanged); + return true; } } @@ -305,7 +369,7 @@ export const jsonObjectStateControl = (defaultValue?: JSONObject) => export const arrayObjectStateControl = (defaultValue?: JSONObject[]) => withTmpState(jsonArrayControl(defaultValue), {}); - + export const jsonValueStateControl = (defaultValue?: JSONValue) => withTmpState(jsonValueControl(defaultValue), ""); diff --git a/client/packages/lowcoder/src/comps/generators/withMultiContext.tsx b/client/packages/lowcoder/src/comps/generators/withMultiContext.tsx index 40c2d231a..a0ba7dba8 100644 --- a/client/packages/lowcoder/src/comps/generators/withMultiContext.tsx +++ b/client/packages/lowcoder/src/comps/generators/withMultiContext.tsx @@ -137,8 +137,8 @@ export function withMultiContext(VariantComp comp = this.getOriginalComp(); } else { comp = this.getOriginalComp() - .setParams(params) - .changeDispatch(wrapDispatch(wrapDispatch(this.dispatch, MAP_KEY), key)); + .changeDispatch(wrapDispatch(wrapDispatch(this.dispatch, MAP_KEY), key)) + .setParams(params); } } return comp; @@ -184,8 +184,8 @@ export function withMultiContext(VariantComp if (params) { const childComp = comp .getOriginalComp() - .setParams(params) - .changeDispatch(wrapDispatch(wrapDispatch(comp.dispatch, MAP_KEY), key)); + .changeDispatch(wrapDispatch(wrapDispatch(comp.dispatch, MAP_KEY), key)) + .setParams(params); const newChildComp = childComp.reduce(childAction); if (childComp !== newChildComp) { diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index f0ff6f607..4fbbd1abe 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -112,7 +112,7 @@ CMD [ "/bin/sh", "/lowcoder/node-service/entrypoint.sh" ] ## ## Standalone image is built from server/proxy-service/Dockerfile ## -FROM node:22.23-slim AS build-proxy-service +FROM node:22.23.4-slim AS build-proxy-service # Copy only the build inputs, so local build/ and node_modules folders are never picked up COPY server/proxy-service/package.json server/proxy-service/yarn.lock server/proxy-service/tsconfig.json /lowcoder/proxy-service/app/ @@ -137,7 +137,7 @@ RUN chmod +x /lowcoder/proxy-service/*.sh ## ## Standalone image is built from deploy/docker/agora-token-service/Dockerfile ## -FROM node:22.23-slim AS build-agora-token-service +FROM node:22.23.4-slim AS build-agora-token-service WORKDIR /lowcoder/agora-token-service/app COPY deploy/docker/agora-token-service/package.json ./ @@ -157,7 +157,7 @@ RUN chmod +x /lowcoder/agora-token-service/*.sh ## HOCUSPOCUS_SERVER_VERSION in sync with it. ## @hocuspocus/server 4.x requires node >= 22. ## -FROM node:22.23-slim AS build-hocuspocus +FROM node:22.23.4-slim AS build-hocuspocus ARG HOCUSPOCUS_SERVER_VERSION=4.3.0 @@ -176,7 +176,7 @@ RUN chmod +x /lowcoder/hocuspocus/*.sh ## ## Build lowcoder client application ## -FROM node:22.23-slim AS build-client +FROM node:22.23.4-slim AS build-client # curl is required for yarn build to succeed, because it calls it while building client RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates @@ -217,7 +217,7 @@ RUN yarn build ## To create a separate image out of it, build it with: ## DOCKER_BUILDKIT=1 docker build -f deploy/docker/Dockerfile -t lowcoderorg/lowcoder-ce-frontend --target lowcoder-ce-frontend . ## -FROM nginx:1.27.1 AS lowcoder-ce-frontend +FROM nginx:1.30.6 AS lowcoder-ce-frontend LABEL maintainer="lowcoder" # Change default nginx user into lowcoder user and remove default nginx config @@ -258,7 +258,7 @@ EXPOSE 3443 ## ## Build lowcoder client (Enterprise) application ## -FROM node:22.23-slim AS build-client-ee +FROM node:22.23.4-slim AS build-client-ee # curl is required for yarn build to succeed, because it calls it while building client RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates @@ -299,7 +299,7 @@ RUN yarn build ## To create a separate image out of it, build it with: ## DOCKER_BUILDKIT=1 docker build -f deploy/docker/Dockerfile -t lowcoderorg/lowcoder-ee-frontend --target lowcoder-ee-frontend . ## -FROM nginx:1.27.1 AS lowcoder-enterprise-frontend +FROM nginx:1.30.6 AS lowcoder-enterprise-frontend LABEL maintainer="lowcoder" # Change default nginx user into lowcoder user and remove default nginx config @@ -357,12 +357,12 @@ RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg - && curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg \ && echo "deb [signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] https://repo.mongodb.org/apt/ubuntu `lsb_release -cs`/mongodb-org/7.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-7.0.list \ && curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu `lsb_release -cs` nginx" | tee /etc/apt/sources.list.d/nginx.list + && echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" | tee /etc/apt/sources.list.d/nginx.list # Install required packages RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends -y \ - nginx=1.27.1-1~jammy \ + nginx=1.30.6-1~jammy \ mongodb-org \ redis \ supervisor \ @@ -376,8 +376,8 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-ins # Use configuration setup from official nginx image RUN rm -rf /etc/nginx/nginx.conf -COPY --from=nginx:1.27.1 /docker-entrypoint.d /docker-entrypoint.d -COPY --from=nginx:1.27.1 /docker-entrypoint.sh /docker-entrypoint.sh +COPY --from=nginx:1.30.6 /docker-entrypoint.d /docker-entrypoint.d +COPY --from=nginx:1.30.6 /docker-entrypoint.sh /docker-entrypoint.sh # Add lowcoder user RUN usermod --login lowcoder --uid 9001 nginx \ @@ -450,4 +450,3 @@ EXPOSE 8081 ENTRYPOINT [ "/bin/sh" , "/lowcoder/entrypoint.sh" ] CMD ["/usr/bin/supervisord", "-n" , "-c" , "/lowcoder/etc/supervisord.conf"] - diff --git a/deploy/docker/agora-token-service/Dockerfile b/deploy/docker/agora-token-service/Dockerfile index d148630de..cfb04030b 100644 --- a/deploy/docker/agora-token-service/Dockerfile +++ b/deploy/docker/agora-token-service/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine +FROM node:22.23.4-alpine WORKDIR /app diff --git a/deploy/docker/hocuspocus/Dockerfile b/deploy/docker/hocuspocus/Dockerfile index 2412a2cdb..a4f862b18 100644 --- a/deploy/docker/hocuspocus/Dockerfile +++ b/deploy/docker/hocuspocus/Dockerfile @@ -1,6 +1,6 @@ # Hocuspocus (Yjs WebSocket) server for lowcoder ChatBox. # Build context: client/packages/lowcoder (see Dockerfile.dockerignore next to this file) -FROM node:26-alpine +FROM node:26.12.1-alpine ARG HOCUSPOCUS_SERVER_VERSION=4.3.0 diff --git a/server/proxy-service/Dockerfile b/server/proxy-service/Dockerfile index e728bed6e..19594ab26 100644 --- a/server/proxy-service/Dockerfile +++ b/server/proxy-service/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine +FROM node:22.23.4-alpine WORKDIR /lowcoder/proxy-service