diff --git a/client/README.md b/client/README.md index 3f504a8ee..2c848ec18 100644 --- a/client/README.md +++ b/client/README.md @@ -31,26 +31,89 @@ docker run -d --name lowcoder-dev -p 3000:3000 -v "$PWD/stacks:/lowcoder-stacks" ### Start develop -1. Check out the source code. -2. Change to client dir in the repository root via cd client. + +1. Check out source code. +2. Change to **/client** dir in the source dir. ```bash cd client ``` - -4. Run yarn to install dependencies: . +3. Run yarn to install dependencies. ```bash yarn install ``` -5. Start dev server: `LOWCODER_API_SERVICE_URL=http://localhost:3000 yarn start`. -6. After the dev server starts successfully, it will be automatically opened in the default browser. +4. Start dev server: + +```bash +LOWCODER_API_SERVICE_URL=http://localhost:3000 yarn start +``` + +5. After dev server starts successfully, it will be automatically opened in the default browser. ### Before submitting a pull request In addition, before submitting a pull request, please make sure the following is done: 1. If you’ve fixed a bug or added code that should be tested and add unit test suite. -2. Run `yarn test` and ensure all test suites pass. -3. If you add new dependency, use yarn workspace lowcoder some-package to make sure yarn.lock is also updated. +2. Run test and ensure all test suites pass. + +```bash +yarn test +``` + +3. If you add new dependency, use the yarn worspace tool to make sure yarn.lock is also updated. + +```bash +yarn workspace lowcoder +``` + +### Developing and publishung UI components for Lowcoder + +1. Initialization + +Project initiation + +```bash +yarn create Lowcoder-plugin +``` + +Go to the project root + +```bash +cd my-plugin +``` + +Start the development environment + +```bash +yarn start +``` + +After executing yarn start, the browser is automatically opened and you enter the component development environment. +Please find more information in our [docs](https://docs.lowcoder.cloud/lowcoder-documentation/lowcoder-extension/develop-ui-components-for-apps) + +2. Export components + +To export all the components, use src/index.ts, for example: + +```bash +import HelloWorldComp from "./HelloWorldComp"; + +export default { + hello_world: HelloWorldComp, +}; +``` + +import HelloWorldComp from "./HelloWorldComp"; + +3. Publish plugins + +When you finish developing and testing the plugin, you can publish it into the npm registry. Login in to the npm registry locally, and then execute the following command: + +```bash +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 diff --git a/client/VERSION b/client/VERSION index 50aea0e7a..c346e7a04 100644 --- a/client/VERSION +++ b/client/VERSION @@ -1 +1 @@ -2.1.0 \ No newline at end of file +2.1.4 \ No newline at end of file diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index a64dfd632..5d7ba98f3 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -7,7 +7,7 @@ "@fullcalendar/core": "^6.1.6", "@fullcalendar/daygrid": "^6.1.6", "@fullcalendar/interaction": "^6.1.6", - "@fullcalendar/list": "^6.1.6", + "@fullcalendar/list": "^6.1.9", "@fullcalendar/moment": "^6.1.6", "@fullcalendar/react": "^6.1.6", "@fullcalendar/timegrid": "^6.1.6", diff --git a/client/packages/lowcoder/index.html b/client/packages/lowcoder/index.html index 10f704aa1..2a45e2639 100644 --- a/client/packages/lowcoder/index.html +++ b/client/packages/lowcoder/index.html @@ -95,6 +95,6 @@
- <%- browserCheckScript %> + diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json index b9fd727cf..3f9dc3541 100644 --- a/client/packages/lowcoder/package.json +++ b/client/packages/lowcoder/package.json @@ -116,7 +116,7 @@ }, "devDependencies": { "@types/core-js": "^2.5.5", - "@types/intl": "^1.2.0", + "@types/intl": "^1.2.1", "@types/papaparse": "^5.3.5", "@types/regenerator-runtime": "^0.13.1", "@types/uuid": "^8.3.4", diff --git a/client/packages/lowcoder/src/comps/comps/layout/mobileTabLayout.tsx b/client/packages/lowcoder/src/comps/comps/layout/mobileTabLayout.tsx index f220fea9e..38c1a8063 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/mobileTabLayout.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/mobileTabLayout.tsx @@ -86,7 +86,9 @@ function TabBarView(props: TabBarProps) { activeKey={props.selectedKey} > {props.tabs.map((tab) => { - return ; + return ( + + ); })} @@ -126,9 +128,18 @@ let MobileTabLayoutTmp = (function () { const childrenMap = { tabs: manualOptionsControl(TabOptionComp, { initOptions: [ - { label: trans("optionsControl.optionI", { i: 1 }), icon: "/icon:solid/1" }, - { label: trans("optionsControl.optionI", { i: 2 }), icon: "/icon:solid/2" }, - { label: trans("optionsControl.optionI", { i: 3 }), icon: "/icon:solid/3" }, + { + label: trans("optionsControl.optionI", { i: 1 }), + icon: "/icon:solid/1", + }, + { + label: trans("optionsControl.optionI", { i: 2 }), + icon: "/icon:solid/2", + }, + { + label: trans("optionsControl.optionI", { i: 3 }), + icon: "/icon:solid/3", + }, ], }), }; @@ -138,7 +149,9 @@ let MobileTabLayoutTmp = (function () { .setPropertyViewFn((children) => { return ( <> -
{children.tabs.propertyView({})}
+
+ {children.tabs.propertyView({})} +
); }) @@ -168,7 +181,9 @@ MobileTabLayoutTmp = withViewFn(MobileTabLayoutTmp, (comp) => { tabs={tabViews.map((tab, index) => ({ key: index, title: tab.children.label.getView(), - icon: tab.children.icon.toJsonValue() ? tab.children.icon.getView() : undefined, + icon: tab.children.icon.toJsonValue() + ? tab.children.icon.getView() + : undefined, }))} selectedKey={tabIndex + ""} onChange={(key) => setTabIndex(Number(key))} diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/meetingControlerUtils.tsx b/client/packages/lowcoder/src/comps/comps/meetingComp/meetingControlerUtils.tsx new file mode 100644 index 000000000..81b11f622 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/meetingComp/meetingControlerUtils.tsx @@ -0,0 +1,36 @@ +import { CheckBox, controlItem, Switch, SwitchWrapper } from "lowcoder-design"; +import { ReactNode } from "react"; +import { ControlParams, SimpleComp } from "@lowcoder-ee/index.sdk"; + +export class BoolShareVideoControl extends SimpleComp { + readonly IGNORABLE_DEFAULT_VALUE = false; + protected getDefaultValue(): boolean { + return false; + } + + getPropertyView(): ReactNode { + return ( + this.dispatchChangeValueAction(x)} + /> + ); + } + + propertyView(params: ControlParams & { type?: "switch" | "checkbox" }) { + return controlItem( + { filterText: params.label }, + + {params.type === "checkbox" ? ( + this.dispatchChangeValueAction(x.target.checked)} + /> + ) : ( + this.getPropertyView() + )} + + ); + } +} diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingControllerComp.tsx b/client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingControllerComp.tsx index c700d5961..13b6b86e1 100644 --- a/client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingControllerComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingControllerComp.tsx @@ -10,9 +10,9 @@ import { BoolControl } from "comps/controls/boolControl"; import { StringControl } from "comps/controls/codeControl"; import { booleanExposingStateControl, + BooleanStateControl, jsonObjectExposingStateControl, - numberExposingStateControl, - stringExposingStateControl, + stringStateControl, } from "comps/controls/codeStateControl"; import { PositionControl } from "comps/controls/dropdownControl"; import { @@ -53,7 +53,7 @@ import AgoraRTC, { import { JSONValue } from "@lowcoder-ee/index.sdk"; import { getData } from "../listViewComp/listViewUtils"; -import AgoraRTM, { RtmChannel, RtmClient, RtmMessage } from "agora-rtm-sdk"; +import AgoraRTM, { RtmChannel, RtmClient } from "agora-rtm-sdk"; const EventOptions = [closeEvent] as const; @@ -103,20 +103,27 @@ export const client: IAgoraRTCClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8", }); +AgoraRTC.setLogLevel(3); + let audioTrack: IMicrophoneAudioTrack; let videoTrack: ICameraVideoTrack; let screenShareStream: ILocalVideoTrack; let userId: UID | null | undefined; let rtmChannelResponse: RtmChannel; let rtmClient: RtmClient; +const agoraTokenUrl = `https://sandbox.wiggolive.com/token/rtc`; const generateToken = async ( appId: any, certificate: any, channelName: any ) => { - const agoraTokenUrl = `https://api.agora.io/v1/token?channelName=test&uid=${userId}&appID=${appId}&appCertificate=${certificate}`; - await axios.post(agoraTokenUrl); + let response = await axios.post(agoraTokenUrl, { + appId, + certificate, + channelName, + }); + return response.data; }; const turnOnCamera = async (flag?: boolean) => { @@ -180,14 +187,12 @@ const publishVideo = async ( height: any, certifiCateKey: string ) => { - // console.log( - // "generateToken", - // await generateToken(appId, certifiCateKey, channel) - // ); - - // return; + let token = null; + if (certifiCateKey) { + token = await generateToken(appId, certifiCateKey, channel); + } await turnOnCamera(true); - await client.join(appId, channel, null, userId); + await client.join(appId, channel, token, userId); await client.publish(videoTrack); await rtmInit(appId, userId, channel); @@ -197,31 +202,17 @@ const publishVideo = async ( const videoSettings = mediaStreamTrack.getSettings(); const videoWidth = videoSettings.width; const videoHeight = videoSettings.height; - height.videoWidth.change(videoWidth); - height.videoHeight.change(videoHeight); + // height.videoWidth.change(videoWidth); + // height.videoHeight.change(videoHeight); } }; const sendMessageRtm = (message: any) => { - rtmChannelResponse - .sendMessage({ text: JSON.stringify(message) }) - .then(() => { - console.log("message sent " + JSON.stringify(message)); - }) - .catch((e: any) => { - console.log("error", e); - }); + rtmChannelResponse.sendMessage({ text: JSON.stringify(message) }); }; const sendPeerMessageRtm = (message: any, toId: string) => { - rtmClient - .sendMessageToPeer({ text: JSON.stringify(message) }, toId) - .then(() => { - console.log("message sent " + JSON.stringify(message)); - }) - .catch((e: any) => { - console.log("error", e); - }); + rtmClient.sendMessageToPeer({ text: JSON.stringify(message) }, toId); }; const rtmInit = async (appId: any, uid: any, channel: any) => { @@ -231,30 +222,13 @@ const rtmInit = async (appId: any, uid: any, channel: any) => { }; await rtmClient.login(options); - rtmClient.on("ConnectionStateChanged", function (state, reason) { - console.log("State changed To: " + state + " Reason: " + reason); - }); - rtmChannelResponse = rtmClient.createChannel(channel); - await rtmChannelResponse.join().then(async () => { - console.log( - "You have successfully joined channel " + rtmChannelResponse.channelId - ); - }); - - // Display channel member stats - rtmChannelResponse.on("MemberJoined", function (memberId) { - console.log(memberId + " joined the channel"); - }); - // Display channel member stats - rtmChannelResponse.on("MemberLeft", function (memberId) { - console.log(memberId + " left the channel"); - }); + await rtmChannelResponse.join(); }; export const meetingControllerChildren = { - visible: booleanExposingStateControl("visible"), + visible: withDefault(BooleanStateControl, "visible"), onEvent: eventHandlerControl(EventOptions), width: StringControl, height: StringControl, @@ -263,19 +237,17 @@ export const meetingControllerChildren = { placement: PositionControl, maskClosable: withDefault(BoolControl, true), showMask: withDefault(BoolControl, true), - audioControl: booleanExposingStateControl("false"), - videoControl: booleanExposingStateControl("true"), - endCall: booleanExposingStateControl("false"), - sharing: booleanExposingStateControl("false"), - videoSettings: jsonObjectExposingStateControl(""), - videoWidth: numberExposingStateControl("videoWidth", 200), - videoHeight: numberExposingStateControl("videoHeight", 200), + meetingActive: withDefault(BooleanStateControl, "false"), + audioControl: withDefault(BooleanStateControl, "false"), + videoControl: withDefault(BooleanStateControl, "true"), + endCall: withDefault(BooleanStateControl, "false"), + sharing: withDefault(BooleanStateControl, "false"), appId: withDefault(StringControl, trans("meeting.appid")), participants: stateComp([]), usersScreenShared: stateComp([]), localUser: jsonObjectExposingStateControl(""), - meetingName: stringExposingStateControl("meetingName"), - certifiCateKey: stringExposingStateControl(""), + meetingName: stringStateControl("meetingName"), + certifiCateKey: stringStateControl(""), messages: stateComp([]), }; let MTComp = (function () { @@ -300,7 +272,12 @@ let MTComp = (function () { [dispatch, isTopBom] ); const [userIds, setUserIds] = useState([]); + const [updateVolume, setUpdateVolume] = useState({ + update: false, + userid: null, + }); const [rtmMessages, setRtmMessages] = useState([]); + const [localUserSpeaking, setLocalUserSpeaking] = useState(false); useEffect(() => { dispatch( @@ -308,6 +285,25 @@ let MTComp = (function () { ); }, [userIds]); + useEffect(() => { + if (updateVolume.userid) { + let prevUsers: [] = props.participants as []; + + const updatedItems = prevUsers.map((userInfo: any) => { + if ( + userInfo.user === updateVolume.userid && + userInfo.speaking != updateVolume.update + ) { + return { ...userInfo, speaking: updateVolume.update }; + } + return userInfo; + }); + dispatch( + changeChildAction("participants", getData(updatedItems).data, false) + ); + } + }, [updateVolume]); + useEffect(() => { if (props.endCall.value) { let newUsers = userIds.filter((item: any) => item.user !== userId); @@ -325,16 +321,36 @@ let MTComp = (function () { } }, [rtmMessages]); + useEffect(() => { + if (localUserSpeaking === true) { + let localObject = { + user: userId + "", + audiostatus: props.audioControl.value, + streamingVideo: props.videoControl.value, + speaking: localUserSpeaking, + }; + props.localUser.onChange(localObject); + } + }, [localUserSpeaking]); + + useEffect(() => { + if (props.localUser.value) { + let newUsers = userIds.filter((item: any) => item.user !== userId); + if (newUsers.length == 0) return; + newUsers = props.localUser.value; + let updatedUsers = [...userIds, newUsers]; + dispatch( + changeChildAction("participants", getData(updatedUsers).data, false) + ); + } + }, [props.localUser.value]); + useEffect(() => { if (rtmChannelResponse) { rtmClient.on("MessageFromPeer", function (message, peerId) { - console.log( - "Message from: " + peerId + " Message: " + message.text - ); setRtmMessages(message.text); }); rtmChannelResponse.on("ChannelMessage", function (message, memberId) { - console.log("Message received from: " + memberId, message.text); setRtmMessages(message.text); dispatch( changeChildAction("messages", getData(rtmMessages).data, false) @@ -344,28 +360,48 @@ let MTComp = (function () { }, [rtmChannelResponse]); useEffect(() => { - client.on("user-joined", (user: IAgoraRTCRemoteUser) => { - let userData = { - user: user.uid, - host: false, - audiostatus: user.hasVideo, - }; - if (userIds.length == 0) { - userData.host = true; - } else { - userData.host = false; - } - setUserIds((userIds: any) => [...userIds, userData]); - }); - client.on("user-left", (user: IAgoraRTCRemoteUser, reason: any) => { - let newUsers = userIds.filter((item: any) => item.user !== user.uid); - let hostExists = newUsers.filter((f: any) => f.host === true); - if (hostExists.length == 0 && newUsers.length > 0) { - newUsers[0].host = true; - hostChanged(newUsers); - } - setUserIds(newUsers); - }); + if (client) { + client.enableAudioVolumeIndicator(); + client.on("user-joined", (user: IAgoraRTCRemoteUser) => { + let userData = { + user: user.uid, + host: false, + audiostatus: user.hasVideo, + }; + + if (userIds.length == 0) { + userData.host = true; + } else { + userData.host = false; + } + setUserIds((userIds: any) => [...userIds, userData]); + }); + client.on("user-left", (user: IAgoraRTCRemoteUser, reason: any) => { + let newUsers = userIds.filter( + (item: any) => item.user !== user.uid + ); + let hostExists = newUsers.filter((f: any) => f.host === true); + if (hostExists.length == 0 && newUsers.length > 0) { + newUsers[0].host = true; + hostChanged(newUsers); + } + setUserIds(newUsers); + }); + client.on("volume-indicator", (volumeInfos: any) => { + if (volumeInfos.length == 0) return; + volumeInfos.map((volumeInfo: any) => { + const speaking = volumeInfo.level >= 30; + if ( + volumeInfo.uid == userId && + props.localUser.value.speaking != speaking + ) { + setLocalUserSpeaking(speaking); + } else { + setUpdateVolume({ update: speaking, userid: volumeInfo.uid }); + } + }); + }); + } }, [client]); return ( @@ -443,6 +479,7 @@ let MTComp = (function () { {children.meetingName.propertyView({ label: trans("meeting.meetingName"), })} + {children.placement.propertyView({ label: trans("drawer.placement"), radioButton: true, @@ -516,11 +553,12 @@ MTComp = withMethodExposing(MTComp, [ }, execute: async (comp, values) => { let value = !comp.children.audioControl.getView().value; - let localUserData = comp.children.localUser.change({ + comp.children.localUser.change({ user: userId + "", audiostatus: value, + streamingVideo: comp.children.videoControl.getView().value, + speaking: false, }); - console.log(localUserData); await turnOnMicrophone(value); comp.children.audioControl.change(value); }, @@ -538,6 +576,14 @@ MTComp = withMethodExposing(MTComp, [ } else { await turnOnCamera(value); } + let localData = { + user: userId + "", + streamingVideo: value, + audiostatus: comp.children.audioControl.getView().value, + speaking: comp.children.localUser.getView().value.speaking, + }; + + comp.children.localUser.change(localData); comp.children.videoControl.change(value); }, }, @@ -552,7 +598,23 @@ MTComp = withMethodExposing(MTComp, [ comp.children.localUser.change({ user: userId + "", audiostatus: false, + speaking: false, + streamingVideo: true, }); + + comp.children.localUser.children.value.dispatch( + changeChildAction( + "localUser", + { + user: userId + "", + audiostatus: false, + speaking: false, + streamingVideo: true, + }, + false + ) + ); + comp.children.videoControl.change(true); await publishVideo( comp.children.appId.getView(), comp.children.meetingName.getView().value == "" @@ -561,6 +623,7 @@ MTComp = withMethodExposing(MTComp, [ comp.children, comp.children.certifiCateKey.getView().value ); + comp.children.meetingActive.change(true); }, }, { @@ -577,15 +640,12 @@ MTComp = withMethodExposing(MTComp, [ let message: any = { time: Date.now(), - from: userId, + from: comp.children.localUser.getView().value, }; message["data"] = otherData; - console.log(toUsers); - if (toUsers.length > 0 && toUsers[0] !== undefined) { let peers = toUsers?.map((u: any) => u.user); - console.log("peers", peers); peers.forEach((p: any) => { sendPeerMessageRtm(message, String(p)); }); @@ -594,6 +654,29 @@ MTComp = withMethodExposing(MTComp, [ } }, }, + { + method: { + name: "setMeetingName", + description: trans("meeting.meetingName"), + params: [], + }, + execute: async (comp, values) => { + let meetingName: any = values[0]; + comp.children.meetingName.change(meetingName); + }, + }, + { + method: { + name: "setUserName", + description: trans("meeting.meetingName"), + params: [], + }, + execute: async (comp, values) => { + let userName: any = values[0]; + let userLocal = comp.children.localUser.getView().value; + comp.children.localUser.change({ ...userLocal, userName: userName }); + }, + }, { method: { name: "endMeeting", @@ -603,7 +686,14 @@ MTComp = withMethodExposing(MTComp, [ execute: async (comp, values) => { let value = !comp.children.endCall.getView().value; comp.children.endCall.change(value); + comp.children.meetingActive.change(false); + await leaveChannel(); + + comp.children.localUser.change({ + user: userId + "", + streamingVideo: false, + }); }, }, { @@ -623,6 +713,7 @@ export const VideoMeetingControllerComp = withExposingConfigs(MTComp, [ new NameConfig("appId", trans("meeting.appid")), new NameConfig("localUser", trans("meeting.host")), new NameConfig("participants", trans("meeting.participants")), + new NameConfig("meetingActive", trans("meeting.meetingName")), new NameConfig("meetingName", trans("meeting.meetingName")), new NameConfig("messages", trans("meeting.meetingName")), ]); diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingStreamComp.tsx b/client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingStreamComp.tsx index b50cd8f19..2b22ede61 100644 --- a/client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingStreamComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingStreamComp.tsx @@ -1,11 +1,11 @@ import { BoolCodeControl } from "comps/controls/codeControl"; import { dropdownControl } from "comps/controls/dropdownControl"; -import { IconControl } from "comps/controls/iconControl"; +// import { IconControl } from "comps/controls/iconControl"; import { CompNameContext, EditorContext, EditorState } from "comps/editorState"; import { withDefault } from "comps/generators"; import { UICompBuilder } from "comps/generators/uiCompBuilder"; import ReactResizeDetector from "react-resize-detector"; -import _ from "lodash"; +// import _ from "lodash"; import { CommonBlueLabel, controlItem, @@ -34,9 +34,12 @@ import { IAgoraRTCRemoteUser } from "agora-rtc-sdk-ng"; import { MeetingEventHandlerControl, + StringControl, + StringStateControl, hiddenPropertyView, stringExposingStateControl, } from "@lowcoder-ee/index.sdk"; +import { BoolShareVideoControl } from "./meetingControlerUtils"; const FormLabel = styled(CommonBlueLabel)` font-size: 13px; @@ -52,33 +55,15 @@ function getFormOptions(editorState: EditorState) { value: info.name, })); } -const Container = styled.div<{ $style: any }>` - height: 100%; - width: 100%; - display: flex; - align-items: center; - justify-content: center; -`; -const VideoContainer = styled.video<{ $style: any }>` + +const VideoContainer = styled.video` height: 100%; width: 100%; display: flex; align-items: center; - justify-content: center; - ${(props) => props.$style && getStyle(props.$style)} + justify-content: space-around; `; -const getStyle = (style: any) => { - return css` - { - border: 1px solid ${style.border}; - border-radius: ${style.radius}; - margin: ${style.margin}; - padding: ${style.padding}; - background-color: ${style.background}; - } - `; -}; function getForm(editorState: EditorState, formName: string) { const comp = editorState?.getUICompByName(formName); if (comp && comp.children.compType.getView() === "form") { @@ -152,17 +137,25 @@ const typeOptions = [ export const meetingStreamChildren = { autoHeight: withDefault(AutoHeightControl, "fixed"), - shareScreen: withDefault(BoolCodeControl, false), + shareScreen: withDefault(BoolShareVideoControl, false), + profilePadding: withDefault(StringControl, "0px"), + profileBorderRadius: withDefault(StringControl, "0px"), + videoAspectRatio: withDefault(StringControl, "1 / 1"), type: dropdownControl(typeOptions, ""), onEvent: MeetingEventHandlerControl, disabled: BoolCodeControl, loading: BoolCodeControl, form: SelectFormControl, - prefixIcon: IconControl, - suffixIcon: IconControl, + // prefixIcon: IconControl, + // suffixIcon: IconControl, style: ButtonStyleControl, viewRef: RefControl, userId: stringExposingStateControl(""), + profileImageUrl: withDefault( + StringStateControl, + "https://via.placeholder.com/120" + ), + noVideoText: stringExposingStateControl("No Video"), }; let VideoCompBuilder = (function (props) { @@ -170,20 +163,25 @@ let VideoCompBuilder = (function (props) { const videoRef = useRef(null); const conRef = useRef(null); const [userId, setUserId] = useState(); + const [userName, setUsername] = useState(""); + const [showVideo, setVideo] = useState(true); - useEffect(() => { - onResize(); - }, []); - - const onResize = async () => { - const container = conRef.current; - let videoCo = videoRef.current; - videoCo!.style.height = container?.clientHeight + "px"; - videoCo!.style.width = container?.clientWidth + "px"; - }; useEffect(() => { if (props.userId.value !== "") { let userData = JSON.parse(props.userId?.value); + if ( + userData.user === userId && + userData.streamingVideo === false && + videoRef.current && + videoRef.current?.id === userId + "" + ) { + if (videoRef.current && videoRef.current?.id === userId + "") { + videoRef.current.srcObject = null; + setVideo(false); + } + } else { + setVideo(true); + } client.on( "user-published", async (user: IAgoraRTCRemoteUser, mediaType: "video" | "audio") => { @@ -192,12 +190,13 @@ let VideoCompBuilder = (function (props) { let userId = user.uid + ""; if ( user.hasVideo && - user.uid + "" != userData.user && - userData.user != "" + user.uid + "" !== userData.user && + userData.user !== "" ) { props.onEvent("videoOn"); } const element = document.getElementById(userId); + if (element) { remoteTrack.play(userId); } @@ -206,8 +205,8 @@ let VideoCompBuilder = (function (props) { const remoteTrack = await client.subscribe(user, mediaType); if ( user.hasAudio && - user.uid + "" != userData.user && - userData.user != "" + user.uid + "" !== userData.user && + userData.user !== "" ) { userData.audiostatus = user.hasVideo; @@ -223,40 +222,108 @@ let VideoCompBuilder = (function (props) { if (mediaType === "audio") { if ( !user.hasAudio && - user.uid + "" != userData.user && - userData.user != "" + user.uid + "" !== userData.user && + userData.user !== "" ) { userData.audiostatus = user.hasVideo; props.onEvent("audioMuted"); } } if (mediaType === "video") { + if (videoRef.current && videoRef.current?.id === user.uid + "") { + videoRef.current.srcObject = null; + } if ( !user.hasVideo && - user.uid + "" != userData.user && - userData.user != "" + user.uid + "" !== userData.user && + userData.user !== "" ) { props.onEvent("videoOff"); } } } ); + setUserId(userData.user); + setUsername(userData.userName); + // console.log(userData); } }, [props.userId.value]); return ( {(editorState) => ( - - - props.onEvent("videoClicked")} - ref={videoRef} - $style={props.style} - id={props.shareScreen ? "share-screen" : userId} - > - + +
+ {userId ? ( + showVideo ? ( + props.onEvent("videoClicked")} + ref={videoRef} + style={{ + display: `${showVideo ? "flex" : "none"}`, + aspectRatio: props.videoAspectRatio, + borderRadius: props.style.radius, + width: "auto", + }} + id={props.shareScreen ? "share-screen" : userId} + > + ) : ( +
+ +

{userName ?? ""}

+
+ ) + ) : ( +
+ +

{userName ?? ""}

+
+ )} +
)}
@@ -270,7 +337,12 @@ let VideoCompBuilder = (function (props) { {children.shareScreen.propertyView({ label: trans("meeting.shareScreen"), })} + {children.profileImageUrl.propertyView({ + label: trans("meeting.profileImageUrl"), + placeholder: "https://via.placeholder.com/120", + })} +
{children.onEvent.getPropertyView()}
@@ -278,6 +350,15 @@ let VideoCompBuilder = (function (props) { {hiddenPropertyView(children)}
+ {children.profilePadding.propertyView({ + label: "Profile Image Padding", + })} + {children.profileBorderRadius.propertyView({ + label: "Profile Image Border Radius", + })} + {children.videoAspectRatio.propertyView({ + label: "Video Aspect Ratio", + })} {children.style.getPropertyView()}
@@ -293,5 +374,7 @@ VideoCompBuilder = class extends VideoCompBuilder { export const VideoMeetingStreamComp = withExposingConfigs(VideoCompBuilder, [ new NameConfig("loading", trans("button.loadingDesc")), + new NameConfig("profileImageUrl", trans("meeting.profileImageUrl")), + ...CommonNameConfig, ]); diff --git a/client/packages/lowcoder/src/comps/comps/remoteComp/remoteComp.tsx b/client/packages/lowcoder/src/comps/comps/remoteComp/remoteComp.tsx index 0c345aceb..0abcde27f 100644 --- a/client/packages/lowcoder/src/comps/comps/remoteComp/remoteComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/remoteComp/remoteComp.tsx @@ -9,7 +9,7 @@ import { useState } from "react"; import { useMount } from "react-use"; import styled from "styled-components"; import { RemoteCompInfo, RemoteCompLoader } from "types/remoteComp"; -import { loaders } from "./loaders"; +import { loaders } from "./loaders"; import { withErrorBoundary } from "comps/generators/withErrorBoundary"; const ViewError = styled.div` diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/expansionControl.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/expansionControl.tsx index 2bf838baf..6b5b41380 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/expansionControl.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/expansionControl.tsx @@ -54,7 +54,7 @@ let ExpansionControlTmp = (function () { .setControlItemData({ filterText: label }) .setPropertyViewFn((children, dispatch) => { return ( - <> + <> {children.expandable.propertyView({ label })} {children.expandable.getView() && children.slot diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 518db3a52..7f20f9c9e 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -1459,6 +1459,8 @@ export const en = { shareScreen: "Share Screen", appid: "Application Id", meetingName: "Meeting Name", + videoCompText: "No video Text", + profileImageUrl: "Profile Image Url", right: "Right", bottom: "Bottom", videoId: "Video Id", diff --git a/client/yarn.lock b/client/yarn.lock index af6402202..2a05e158b 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2532,12 +2532,12 @@ __metadata: languageName: node linkType: hard -"@fullcalendar/list@npm:^6.1.6": - version: 6.1.8 - resolution: "@fullcalendar/list@npm:6.1.8" +"@fullcalendar/list@npm:^6.1.9": + version: 6.1.9 + resolution: "@fullcalendar/list@npm:6.1.9" peerDependencies: - "@fullcalendar/core": ~6.1.8 - checksum: b5c397040e0ed9566f0bdbe2c6377a3656f2db823b38085bf45bfb50c6415ecc854411e496b81d5932a5c77a1a9abfa8b582d67bad1e4dc562f41b6b14621dae + "@fullcalendar/core": ~6.1.9 + checksum: 978dd54b7131369d023e4d8a0e97b986a89a986b94a0d71dc6e9782e60e6c268184f2c596dcc7fa0580b143bfd39390a40ea4c9114afd1fa2eca5c48a7b0aaab languageName: node linkType: hard @@ -4025,10 +4025,10 @@ __metadata: languageName: node linkType: hard -"@types/intl@npm:^1.2.0": - version: 1.2.0 - resolution: "@types/intl@npm:1.2.0" - checksum: 95348313fee4949c6feba98bb35be45c0930ff816d7fedee8afd003282cbc004bf682ff94e6a662af5987c8fcc98bb4e8f93a4919d1ef6bbd9ce83577bd77fa6 +"@types/intl@npm:^1.2.1": + version: 1.2.1 + resolution: "@types/intl@npm:1.2.1" + checksum: 5e1c93792a0c61f55c21384d8a64e521deae878cb5ff730008aca43790b2a6e91d96ab5779c390014ec9184246e2420835455c261645a8877a2500c96646a49e languageName: node linkType: hard @@ -11843,7 +11843,7 @@ __metadata: "@fullcalendar/core": ^6.1.6 "@fullcalendar/daygrid": ^6.1.6 "@fullcalendar/interaction": ^6.1.6 - "@fullcalendar/list": ^6.1.6 + "@fullcalendar/list": ^6.1.9 "@fullcalendar/moment": ^6.1.6 "@fullcalendar/react": ^6.1.6 "@fullcalendar/timegrid": ^6.1.6 @@ -11999,7 +11999,7 @@ __metadata: "@rjsf/utils": ^5.10.0 "@rjsf/validator-ajv8": ^5.10.0 "@types/core-js": ^2.5.5 - "@types/intl": ^1.2.0 + "@types/intl": ^1.2.1 "@types/lodash": ^4.14.194 "@types/node": ^16.7.13 "@types/papaparse": ^5.3.5 diff --git a/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml b/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml index cc3ed4be8..5a6f7ac34 100644 --- a/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml +++ b/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml @@ -39,7 +39,7 @@ common: domain: default-value: lowcoder.org cloud: false - version: 1.1.8 + version: 2.1.4 block-hound-enable: false js-executor: host: ${LOWCODER_NODE_SERVICE_URL:http://127.0.0.1:6060}