diff --git a/.gitignore b/.gitignore
index 2e1b56cd5..81c0e6c82 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ client/node_modules/
client/packages/lowcoder-plugin-demo/.yarn/install-state.gz
client/packages/lowcoder-plugin-demo/yarn.lock
client/packages/lowcoder-plugin-demo/.yarn/cache/@types-node-npm-16.18.68-56f72825c0-094ae9ed80.zip
+application-dev.yml
diff --git a/README.md b/README.md
index 07a5693a5..f599bcb0a 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
Lowcoder
-
This is the only Platform, which closes the gap between App Development, Website Development and Slide-Presentations.
+
This is the only Platform, which closes the gap between App Development, Website Development, interactive Slides/Presentations and Collaboration Tools!
Create software applications (internal and customer-facing!) and Meeting/Collaboration tools for your Company and your Customers with minimal coding experience.
@@ -12,40 +12,42 @@

+
## 📢 Use Lowcoder in 3 steps
1. Connect to any data sources or APIs.
-2. Build UI with 60+ components.
+2. Build flexible and responsive UI with 100+ components and free layout / design possibilities.
3. Share with colleagues and customers.
## 💡 Why Lowcoder
-It's cumbersome to create a single app. You had to design user interfaces, write code in multiple languages and frameworks, and understand how all of that code works together.
+One platform for everything instead so many different softwares. (like Website Builders, CMS, CRM, POS, ERP, Dashboards & Data Story Visualization, Collaboration Tools).
-Low-code/No-code platforms are fast to get started with but quickly become unmaintainable and inflexible. This creates more problems than it solves.
+It's cumbersome to create a single app. You had to design user interfaces, write code in multiple languages and frameworks, and understand how all of that code works together.
NewGen Lowcode Platforms like Retool and others are great for their simplicity and flexibility - like Lowcoder too, but they can also be limited in different ways, especially when it comes to "external" applications for everyone.
Lowcoder wants to take a step forward. More specifically, Lowcoder is:
- An all-in-one IDE to create internal or customer-facing (external) apps.
-- A place to create, build and share building blocks of web applications.
+- A place to create, build and share building blocks of web applications and whole websites.
- The tool and community to support your business, and lower the cost and time to develop interactive applications.
- The only platform to embed Lowcode Apps natively in Websites (no iFrame!)
- The only platform where you can build your own Meeting Tool - like Teams, Zoom or Google Meets, - just in the Lowcode way.
+- The only platform which has extensibility plugin architecture [Check Community Contributions](https://www.npmjs.com/search?q=lowcoder-comp)
## 🪄 Features
-- **Visual UI builder** with 50+ built-in components. Save 90% of time to build apps.
-- **Modules** for reusable (!) component sets in the UI builder.
-- **Embed Lowcoder Apps as native parts of any Website** instead of iFrame (!). [Demo](https://github.com/lowcoder-org/lowcoder-sdk-demo)
+- **Visual UI builder** with 100+ built-in components. Save 90% of time to build apps.
+- **Modules** for reusable (!) embedable component sets in the UI builder.
+- **Embed Lowcoder Apps as native parts of any Website** instead of iFrame (!). [Demo](https://lowcoder.cloud/about), [Docu](https://docs.lowcoder.cloud/lowcoder-documentation/lowcoder-extension/native-embed-sdk)
- **Video Meeting Components** to create your own individual Web-Meeting tool.
- **Query Library** for reusable data queries of your data sources.
- **Custom components** to develop own components and use them in the UI builder.
- **Native Data connections** to PostgreSQL, MongoDB, MySQL, Redis, Elasticsearch, REST API, SMTP, etc.
-- **Stream Data connections** to Websockets for realtime data updates & collaboration
+- **Stream Data connections** to Websockets for realtime data updates & collaboration! [Docu](https://docs.lowcoder.cloud/lowcoder-documentation/connect-your-data/data-sources-in-lowcoder/websocket-datasource)
- **JavaScript supported everywhere** to transform data, control components, etc.
-- **Role-based access control (RBAC)** for granular permission management.
+- **Role-based access control (RBAC)** for granular permission management. [Docu](https://docs.lowcoder.cloud/lowcoder-documentation/workspaces-and-teamwork/members-and-groups)
- **Auto-saved and restorable history** for release and version control.
- **App Themes and Theme Editor** to precisely align with your company's brand guidelines.
-- **Self Hosting** to use Lowcoder in your internal company network, even behind the firewall.
+- **Self Hosting** to use Lowcoder in your internal company network, even behind the firewall. [Docu](https://docs.lowcoder.cloud/lowcoder-documentation/setup-and-run/self-hosting)
- **Free Community Cloud** to start within a minute and build your first Apps. [Start here](https://app.lowcoder.cloud)
## 🏆 Comparisons
@@ -59,6 +61,7 @@ Lowcoder wants to take a step forward. More specifically, Lowcoder is:
- Lowcoder is open-source. You don't need to worry about vendor lock-in or being stuck with an outdated version of the software.
- In Lowcoder, developers can build truly responsive apps - not as cumbersome as the "Desktop / Mobile switch" in Retool
- Lowcoder is free and you can contribute!
+- With Lowcoder you can design better Apps. More Layout & Design Components as also better support for powerful Data & Admin Dashboards.
- The EE Version of Lowcoder comes with a much better pricing model, so you have no "per-user costs".
### Lowcoder vs Appsmith, Tooljet
- Lowcoder has more components and richer configuration than Appsmith and Tooljet.
@@ -87,16 +90,24 @@ And we mean it... Day by day!
## 💻 Deployment Options
[](https://deploy.stitch.tech/lowcoder/lowcoder)
+[](https://elest.io/open-source/lowcoder)
+
You can access Lowcoder from [cloud-hosted version](https://app.lowcoder.cloud/) at any time, or use the following resources for self-host Lowcoder on different platforms:
- [Docker](https://docs.lowcoder.cloud/lowcoder-documentation/setup-and-run/self-hosting)
## 💪 Contributing
- Language support: If you have experience with a language that isn't currently supported by our product, send us a pull request.
- Create and share components or demos: If you've created something that might be useful to others, add the link here.
-- [Frontend contributing guide](https://github.com/lowcoder-org/lowcoder/tree/main/client)
+- [Contributing guide](https://docs.lowcoder.cloud/lowcoder-documentation/lowcoder-extension/opensource-contribution)
+
+Special tanks goes to [@sjhoeksma](https://github.com/sjhoeksma), [@mousheng](https://github.com/mousheng), [@mat02](https://github.com/mat02), [@jomedya](https://github.com/jomedya) and many other contributors!
## 🥇 Sponsors
Accelerate the growth of Lowcoder and unleash its potential with your Sponsorship – together, we're shaping the future of Lowcode for everyone!
[Be a Sponsor](https://github.com/sponsors/lowcoder-org)
-Like ... [@spacegoats-io](https://github.com/spacegoats-io), [@Jomedya](https://github.com/Jomedya), [@CHSchuepfer](https://github.com/CHSchuepfer), Thank you very much!!
\ No newline at end of file
+Like ... [@Darkjamin](https://github.com/Darkjamin), [@spacegoats-io](https://github.com/spacegoats-io), [@Jomedya](https://github.com/Jomedya), [@CHSchuepfer](https://github.com/CHSchuepfer), Thank you very much!!
+
+## Intro Video
+
+[](https://youtu.be/s4ltAqS0hzM?feature=shared)
\ No newline at end of file
diff --git a/client/VERSION b/client/VERSION
index a6254504e..9183195ac 100644
--- a/client/VERSION
+++ b/client/VERSION
@@ -1 +1 @@
-2.3.1
\ No newline at end of file
+2.4.0
\ No newline at end of file
diff --git a/client/config/test/jest.config.js b/client/config/test/jest.config.js
index 90fb74572..53aa19f78 100644
--- a/client/config/test/jest.config.js
+++ b/client/config/test/jest.config.js
@@ -6,14 +6,10 @@ export function currentDirName(importMetaUrl) {
return dirname(fileURLToPath(importMetaUrl));
}
-
const globals = {};
buildVars.forEach(({ name, defaultValue }) => {
globals[name] = process.env[name] || defaultValue;
});
-const edition = process.env.REACT_APP_EDITION;
-const isEEGlobal = edition === "enterprise-global";
-const isEE = edition === "enterprise" || isEEGlobal;
const currentDir = currentDirName(import.meta.url);
export default {
@@ -22,8 +18,7 @@ export default {
"react-markdown": path.resolve(currentDir, "./mocks/react-markdown.js"),
"\\.md\\?url$": path.resolve(currentDir, "./mocks/markdown-url-module.js"),
"^@lowcoder-ee(.*)$": path.resolve(
- currentDir,
- isEE ? "../../packages/lowcoder/src/ee/$1" : "../../packages/lowcoder/src/$1"
+ currentDir, "../../packages/lowcoder/src/$1"
),
"lowcoder-sdk": path.resolve(currentDir, "../../packages/lowcoder/src/index.sdk"),
},
diff --git a/client/package.json b/client/package.json
index 0034bb1d7..44087ce6a 100644
--- a/client/package.json
+++ b/client/package.json
@@ -13,7 +13,7 @@
"start": "yarn workspace lowcoder start",
"start-win": "LOWCODER_API_SERVICE_URL=http://localhost:3000 yarn start",
"start:ee": "REACT_APP_EDITION=enterprise yarn workspace lowcoder start",
- "start:ee-global": "REACT_APP_EDITION=enterprise-global yarn workspace lowcoder start",
+ "translate": "node --loader ts-node/esm ./scripts/translate.js",
"build": "yarn node ./scripts/build.js",
"test": "jest && yarn workspace lowcoder-comps test",
"prepare": "yarn workspace lowcoder prepare",
@@ -52,9 +52,9 @@
"mq-polyfill": "^1.1.8",
"prettier": "^3.1.0",
"rimraf": "^3.0.2",
- "rollup": "^2.79.0",
"shelljs": "^0.8.5",
"svgo": "^3.0.0",
+ "ts-node": "^10.4.0",
"typescript": "^4.8.4",
"whatwg-fetch": "^3.6.2"
},
@@ -76,11 +76,14 @@
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
"@types/styled-components": "^5.1.34",
- "antd-mobile": "^5.28.0",
+ "antd-mobile": "^5.34.0",
"chalk": "4",
+ "flag-icons": "^7.2.1",
"number-precision": "^1.6.0",
+ "react-countup": "^6.5.3",
"react-player": "^2.11.0",
"resize-observer-polyfill": "^1.5.1",
+ "rollup": "^4.13.0",
"simplebar": "^6.2.5",
"tui-image-editor": "^3.15.3"
}
diff --git a/client/packages/lowcoder-comps/.gitignore b/client/packages/lowcoder-comps/.gitignore
index 4df552dfa..c3bb773d5 100644
--- a/client/packages/lowcoder-comps/.gitignore
+++ b/client/packages/lowcoder-comps/.gitignore
@@ -1,3 +1,4 @@
/lib
/node_modules
+/package
*.tgz
\ No newline at end of file
diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json
index 029be11e2..1c417f1d4 100644
--- a/client/packages/lowcoder-comps/package.json
+++ b/client/packages/lowcoder-comps/package.json
@@ -1,6 +1,6 @@
{
"name": "lowcoder-comps",
- "version": "0.0.27",
+ "version": "2.4.5",
"type": "module",
"license": "MIT",
"dependencies": {
@@ -10,13 +10,17 @@
"@fullcalendar/interaction": "^6.1.6",
"@fullcalendar/list": "^6.1.9",
"@fullcalendar/moment": "^6.1.6",
+ "@fullcalendar/multimonth": "^6.1.6",
"@fullcalendar/react": "^6.1.6",
"@fullcalendar/resource": "^6.1.11",
"@fullcalendar/resource-timegrid": "^6.1.11",
"@fullcalendar/resource-timeline": "^6.1.11",
"@fullcalendar/timegrid": "^6.1.6",
+ "@fullcalendar/timeline": "^6.1.6",
"@types/react": "^18.2.45",
"@types/react-dom": "^18.2.18",
+ "agora-rtc-sdk-ng": "^4.20.2",
+ "agora-rtm-sdk": "^1.5.1",
"big.js": "^6.2.1",
"echarts-extension-gmap": "^1.6.0",
"echarts-wordcloud": "^2.1.0",
@@ -30,19 +34,27 @@
"lowcoder": {
"description": "",
"comps": {
- "chart": {
- "name": "Chart",
- "icon": "./icons/icon-chart.svg",
+ "meetingController": {
+ "name": "Agora Meeting Controller",
+ "icon": "./icons/icon-comp-calendar.svg",
+ "layoutInfo": {
+ "w": 1,
+ "h": 1
+ }
+ },
+ "meetingSharing": {
+ "name": "Agora Meeting Sharing",
+ "icon": "./icons/icon-comp-calendar.svg",
"layoutInfo": {
- "w": 15,
+ "w": 6,
"h": 40
}
},
- "imageEditor": {
- "name": "Image Editor",
- "icon": "./icons/icon-chart.svg",
+ "meetingStream": {
+ "name": "Video Stream",
+ "icon": "./icons/icon-comp-calendar.svg",
"layoutInfo": {
- "w": 15,
+ "w": 6,
"h": 40
}
},
@@ -50,17 +62,137 @@
"name": "Calendar",
"icon": "./icons/icon-comp-calendar.svg",
"layoutInfo": {
- "w": 15,
+ "w": 19,
"h": 60
}
},
+ "chart": {
+ "name": "Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "basicChart": {
+ "name": "Basic Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "imageEditor": {
+ "name": "Image Editor",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
"mermaid": {
"name": "Mermaid",
"icon": "./icons/mermaidchart.svg",
"layoutInfo": {
- "w": 15,
+ "w": 12,
+ "h": 40
+ }
+ },
+ "funnelChart": {
+ "name": "Funnel Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "gaugeChart": {
+ "name": "Gauge Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "sankeyChart": {
+ "name": "Sankey Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
"h": 40
}
+ },
+ "candleStickChart": {
+ "name": "CandleStick Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "radarChart": {
+ "name": "Radar Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "heatmapChart": {
+ "name": "Heatmap Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "graphChart": {
+ "name": "Graph Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "treeChart": {
+ "name": "Tree Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "treemapChart": {
+ "name": "Treemap Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "sunburstChart": {
+ "name": "Sunburst Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "themeriverChart": {
+ "name": "Themeriver Chart",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 12,
+ "h": 40
+ }
+ },
+ "chartsGeoMap": {
+ "name": "chartsGeoMap",
+ "icon": "./icons/icon-chart.svg",
+ "layoutInfo": {
+ "w": 19,
+ "h": 60
+ }
}
}
},
diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/meetingControlerUtils.tsx b/client/packages/lowcoder-comps/src/comps/agoraMeetingComp/meetingControlerUtils.tsx
similarity index 89%
rename from client/packages/lowcoder/src/comps/comps/meetingComp/meetingControlerUtils.tsx
rename to client/packages/lowcoder-comps/src/comps/agoraMeetingComp/meetingControlerUtils.tsx
index 81b11f622..10e685faf 100644
--- a/client/packages/lowcoder/src/comps/comps/meetingComp/meetingControlerUtils.tsx
+++ b/client/packages/lowcoder-comps/src/comps/agoraMeetingComp/meetingControlerUtils.tsx
@@ -1,6 +1,7 @@
import { CheckBox, controlItem, Switch, SwitchWrapper } from "lowcoder-design";
import { ReactNode } from "react";
-import { ControlParams, SimpleComp } from "@lowcoder-ee/index.sdk";
+import { ControlParams } from "comps/controls/controlParams";
+import { SimpleComp } from "lowcoder-core";
export class BoolShareVideoControl extends SimpleComp
{
readonly IGNORABLE_DEFAULT_VALUE = false;
diff --git a/client/packages/lowcoder-comps/src/comps/agoraMeetingComp/meetingControllerComp.tsx b/client/packages/lowcoder-comps/src/comps/agoraMeetingComp/meetingControllerComp.tsx
new file mode 100644
index 000000000..268b16404
--- /dev/null
+++ b/client/packages/lowcoder-comps/src/comps/agoraMeetingComp/meetingControllerComp.tsx
@@ -0,0 +1,882 @@
+import {
+ NameConfig,
+ BoolControl,
+ withDefault,
+ withExposingConfigs,
+ StringControl,
+ Section,
+ sectionNames,
+ styleControl,
+ BooleanStateControl,
+ AutoHeightControl,
+ stringStateControl,
+ InnerGrid,
+ useUserViewMode,
+ getData,
+ gridItemCompToGridItems,
+ Layers,
+ isNumeric,
+ withMethodExposing,
+ eventHandlerControl,
+ DrawerStyle,
+ PositionControl,
+ jsonObjectExposingStateControl,
+ stateComp,
+ Drawer,
+ changeChildAction,
+ HintPlaceHolder,
+ // styledm,
+ // DrawerWrapper,
+ BackgroundColorContext,
+ ContainerCompBuilder,
+ closeEvent,
+ MeetingEventHandlerControl,
+} from "lowcoder-sdk";
+import { default as CloseOutlined } from "@ant-design/icons/CloseOutlined";
+import type { JSONValue } from "../../../../lowcoder/src/util/jsonTypes";
+// import { default as Button } from "antd/es/button";
+
+const EventOptions = [closeEvent] as const;
+import { trans } from "../../i18n/comps";
+// const DrawerWrapper = styledm.div`
+// // Shield the mouse events of the lower layer, the mask can be closed in the edit mode to prevent the lower layer from sliding
+// pointer-events: auto;
+// `;
+import AgoraRTC, {
+ type ICameraVideoTrack,
+ type IMicrophoneAudioTrack,
+ type IAgoraRTCClient,
+ type IAgoraRTCRemoteUser,
+ type UID,
+ type ILocalVideoTrack,
+} from "agora-rtc-sdk-ng";
+
+import type { RtmChannel, RtmClient } from "agora-rtm-sdk";
+import { useCallback, useEffect, useState } from "react";
+import { ResizeHandle } from "react-resizable";
+import { v4 as uuidv4 } from "uuid";
+
+const DEFAULT_SIZE = 378;
+const DEFAULT_PADDING = 16;
+function transToPxSize(size: string | number) {
+ return isNumeric(size) ? size + "px" : (size as string);
+}
+
+export const client: IAgoraRTCClient = AgoraRTC.createClient({
+ mode: "rtc",
+ codec: "vp8",
+});
+
+AgoraRTC.setLogLevel(4);
+
+/*
+0: DEBUG. Output all API logs.
+1: INFO. Output logs of the INFO, WARNING and ERROR level.
+2: WARNING. Output logs of the WARNING and ERROR level.
+3: ERROR. Output logs of the ERROR level.
+4: NONE. Do not output any log.
+*/
+
+let audioTrack: IMicrophoneAudioTrack;
+let videoTrack: ICameraVideoTrack;
+let screenShareStream: ILocalVideoTrack;
+let userId: UID | null | undefined;
+let rtmChannelResponse: RtmChannel;
+let rtmClient: RtmClient;
+// const ButtonStyle = styledm(Button)`
+// position: absolute;
+// left: 0;
+// top: 0;
+// z-index: 10;
+// font-weight: 700;
+// box-shadow: none;
+// color: rgba(0, 0, 0, 0.45);
+// height: 54px;
+// width: 54px;
+
+// svg {
+// width: 16px;
+// height: 16px;
+// }
+
+// &,
+// :hover,
+// :focus {
+// background-color: transparent;
+// border: none;
+// }
+
+// :hover,
+// :focus {
+// color: rgba(0, 0, 0, 0.75);
+// }
+// `;
+const turnOnCamera = async (flag?: boolean) => {
+ if (videoTrack) {
+ return videoTrack.setEnabled(flag!);
+ }
+ videoTrack = await AgoraRTC.createCameraVideoTrack();
+ videoTrack.play(userId + "");
+};
+
+const turnOnMicrophone = async (flag?: boolean) => {
+ if (audioTrack) {
+ return audioTrack.setEnabled(flag!);
+ }
+ audioTrack = await AgoraRTC.createMicrophoneAudioTrack();
+ if (!flag) {
+ await client.unpublish(audioTrack);
+ } else {
+ await client.publish(audioTrack);
+ }
+};
+const shareScreen = async (sharing: boolean) => {
+ try {
+ if (sharing === false) {
+ await client.unpublish(screenShareStream);
+ screenShareStream.close();
+ await client.publish(videoTrack);
+ videoTrack.play(userId + "");
+ } else {
+ screenShareStream = await AgoraRTC.createScreenVideoTrack(
+ {
+ screenSourceType: "screen",
+ },
+ "disable"
+ );
+ await client.unpublish(videoTrack);
+ screenShareStream.play("share-screen");
+ await client.publish(screenShareStream);
+ }
+ } catch (error) {
+ console.error("Failed to create screen share stream:", error);
+ }
+};
+const leaveChannel = async () => {
+ //stops local sharing video
+ if (screenShareStream) {
+ screenShareStream.close();
+ }
+
+ //stops local video streaming and puts off the camera
+ if (videoTrack) {
+ await client.unpublish(videoTrack);
+ await turnOnCamera(false);
+ }
+
+ //mutes and stops locla audio stream
+ if (audioTrack) {
+ await turnOnMicrophone(false);
+ }
+ await client.leave();
+ await rtmChannelResponse.leave();
+};
+
+const publishVideo = async (
+ appId: string,
+ channel: string,
+ rtmToken: string,
+ rtcToken: string
+) => {
+ await turnOnCamera(true);
+ await client.join(appId, channel, rtcToken, userId);
+ await client.publish(videoTrack);
+ await rtmInit(appId, userId, rtmToken, channel);
+};
+
+const sendMessageRtm = (message: any) => {
+ rtmChannelResponse.sendMessage({ text: JSON.stringify(message) });
+};
+
+const sendPeerMessageRtm = (message: any, toId: string) => {
+ rtmClient.sendMessageToPeer({ text: JSON.stringify(message) }, toId);
+};
+
+const rtmInit = async (appId: any, uid: any, token: any, channel: any) => {
+ const AgoraRTM = (await import("agora-rtm-sdk")).default;
+ rtmClient = AgoraRTM.createInstance(appId);
+ let options = {
+ uid: String(uid),
+ token: token ? token : null,
+ };
+ await rtmClient.login(options);
+
+ rtmChannelResponse = rtmClient.createChannel(channel);
+
+ await rtmChannelResponse.join();
+};
+
+const CanvasContainerID = "__canvas_container__";
+const meetingControllerChildren = {
+ visible: withDefault(BooleanStateControl, "false"),
+ onEvent: eventHandlerControl(EventOptions),
+ onMeetingEvent: MeetingEventHandlerControl,
+ width: StringControl,
+ height: StringControl,
+ autoHeight: AutoHeightControl,
+ style: styleControl(DrawerStyle),
+ placement: PositionControl,
+ maskClosable: withDefault(BoolControl, true),
+ showMask: withDefault(BoolControl, true),
+ 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(""),
+ localUserID: withDefault(
+ stringStateControl(trans("meeting.localUserID")),
+ uuidv4() + ""
+ ),
+ meetingName: withDefault(
+ stringStateControl(trans("meeting.meetingName")),
+ uuidv4() + ""
+ ),
+ rtmToken: stringStateControl(trans("meeting.rtmToken")),
+ rtcToken: stringStateControl(trans("meeting.rtcToken")),
+ messages: stateComp([]),
+};
+
+let MeetingControllerComp = () => Meeting Component is not available. It needs Lowcoder from Version v2.4
;
+
+if (typeof ContainerCompBuilder === 'function') {
+
+let MTComp = (function () {
+ return new ContainerCompBuilder(
+ meetingControllerChildren, (props: any, dispatch: any) => {
+
+ const isTopBom = ["top", "bottom"].includes(props.placement);
+ const { items, ...otherContainerProps } = props.container;
+ // const userViewMode = useUserViewMode();
+ // const resizable = !userViewMode && (!isTopBom || !props.autoHeight);
+ const onResizeStop = useCallback(
+ (
+ e: React.SyntheticEvent,
+ node: HTMLElement,
+ size: { width: number; height: number },
+ handle: ResizeHandle
+ ) => {
+ isTopBom
+ ? dispatch(changeChildAction("height", size.height, true))
+ : dispatch(changeChildAction("width", size.width, true));
+ },
+ [dispatch, isTopBom]
+ );
+ const [userIds, setUserIds] = useState([]);
+ const [updateVolume, setUpdateVolume] = useState({
+ update: false,
+ userid: null,
+ });
+ const [rtmMessages, setRtmMessages] = useState([]);
+ const [localUserSpeaking, setLocalUserSpeaking] = useState(false);
+ const [localUserVideo, setLocalUserVideo] =
+ useState();
+ const [userJoined, setUserJoined] = useState();
+ const [userLeft, setUserLeft] = useState();
+
+ useEffect(() => {
+ if (userJoined) {
+ // console.log("userJoined ", userJoined);
+
+ let prevUsers: any[] = props.participants as [];
+ // console.log("prevUsers ", prevUsers);
+ let userData = {
+ user: userJoined.uid,
+ audiostatus: userJoined.hasAudio,
+ streamingVideo: true,
+ };
+ // console.log("userData ", userData);
+ setUserIds((userIds: any) => [...userIds, userData]);
+ // console.log("userIds ", userIds);
+ /* console.log(
+ "removeDuplicates ",
+ removeDuplicates(getData([...prevUsers, userData]).data, "user")
+ ); */
+ dispatch(
+ changeChildAction(
+ "participants",
+ removeDuplicates(getData([...prevUsers, userData]).data, "user"),
+ false
+ )
+ );
+ }
+ }, [userJoined]);
+
+ function removeDuplicates(arr: any, prop: any) {
+ const uniqueObjects = [];
+ const seenValues = new Set();
+
+ for (const obj of arr) {
+ const objValue = obj[prop];
+
+ if (!seenValues.has(objValue)) {
+ seenValues.add(objValue);
+ uniqueObjects.push(obj);
+ }
+ }
+
+ return uniqueObjects;
+ }
+ useEffect(() => {
+ if (userLeft) {
+ let newUsers = userIds.filter(
+ (item: any) => item.user !== userLeft.uid
+ );
+ let hostExists = newUsers.filter((f: any) => f.host === true);
+ if (hostExists.length == 0 && newUsers.length > 0) {
+ newUsers[0].host = true;
+ }
+ setUserIds(newUsers);
+ dispatch(
+ changeChildAction(
+ "participants",
+ removeDuplicates(getData(newUsers).data, "user"),
+ false
+ )
+ );
+ }
+ }, [userLeft]);
+
+ // console.log("sharing", props.sharing);
+
+ 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(() => {
+ let prevUsers: [] = props.participants as [];
+ const updatedItems = prevUsers.map((userInfo: any) => {
+ if (userInfo.user === localUserVideo?.uid) {
+ return { ...userInfo, streamingSharing: props.sharing.value };
+ }
+ return userInfo;
+ });
+ dispatch(
+ changeChildAction("participants", getData(updatedItems).data, false)
+ );
+
+ let localObject = {
+ user: userId + "",
+ audiostatus: props.audioControl.value,
+ streamingVideo: props.videoControl.value,
+ streamingSharing: props.sharing.value,
+ speaking: localUserSpeaking,
+ };
+ props.localUser.onChange(localObject);
+ }, [props.sharing.value]);
+
+ // console.log("participants ", props.participants);
+
+ useEffect(() => {
+ let prevUsers: [] = props.participants as [];
+ const updatedItems = prevUsers.map((userInfo: any) => {
+ if (userInfo.user === localUserVideo?.uid) {
+ return { ...userInfo, streamingVideo: localUserVideo?.hasVideo };
+ }
+ return userInfo;
+ });
+ dispatch(
+ changeChildAction("participants", getData(updatedItems).data, false)
+ );
+ }, [localUserVideo?.hasVideo]);
+
+ useEffect(() => {
+ if (rtmMessages) {
+ dispatch(
+ changeChildAction("messages", getData(rtmMessages).data, false)
+ );
+ }
+ }, [rtmMessages]);
+
+ useEffect(() => {
+ if (localUserSpeaking === true || localUserVideo) {
+ let localObject = {
+ user: userId + "",
+ audiostatus: props.audioControl.value,
+ streamingVideo: props.videoControl.value,
+ speaking: localUserSpeaking,
+ };
+ props.localUser.onChange(localObject);
+ }
+ }, [localUserSpeaking]);
+
+ useEffect(() => {
+ if (rtmChannelResponse) {
+ rtmClient.on("MessageFromPeer", function (message, peerId) {
+ setRtmMessages((prevMessages: any[]) => {
+ // Check if the messages array exceeds the maximum limit
+ if (prevMessages.length >= 500) {
+ prevMessages.pop(); // Remove the oldest message
+ }
+ return [
+ ...prevMessages,
+ { peermessage: JSON.parse(message.text + ""), from: peerId },
+ ];
+ });
+ });
+
+ rtmChannelResponse.on("ChannelMessage", function (message, memberId) {
+ setRtmMessages((prevMessages: any[]) => {
+ // Check if the messages array exceeds the maximum limit
+ if (prevMessages.length >= 500) {
+ prevMessages.pop(); // Remove the oldest message
+ }
+ return [
+ ...prevMessages,
+ {
+ channelmessage: JSON.parse(message.text + ""),
+ from: memberId,
+ },
+ ];
+ });
+
+ dispatch(
+ changeChildAction("messages", getData(rtmMessages).data, false)
+ );
+ });
+ }
+ }, [rtmChannelResponse]);
+ useEffect(() => {
+ if (client) {
+ //Enable Agora to send audio bytes
+ client.enableAudioVolumeIndicator();
+ //user activity listeners
+ client.on("user-joined", (user: IAgoraRTCRemoteUser) => {
+ setUserJoined(user);
+ });
+ client.on("user-left", (user: IAgoraRTCRemoteUser, reason: any) => {
+ setUserLeft(user);
+ });
+
+ //listen to user speaking,
+ client.on("volume-indicator", (volumeInfos: any) => {
+ if (volumeInfos.length === 0) return;
+ volumeInfos.map((volumeInfo: any) => {
+ //when the volume is above 30, user is probably speaking
+ const speaking = volumeInfo.level >= 30;
+ if (
+ volumeInfo.uid === userId &&
+ props.localUser.value.speaking != speaking
+ ) {
+ setLocalUserSpeaking(speaking);
+ } else {
+ setUpdateVolume({ update: speaking, userid: volumeInfo.uid });
+ }
+ });
+ });
+
+ client.on(
+ "user-published",
+ async (user: IAgoraRTCRemoteUser, mediaType: "video" | "audio") => {
+ setLocalUserVideo(user);
+ }
+ );
+ client.on(
+ "user-unpublished",
+ (user: IAgoraRTCRemoteUser, mediaType: "video" | "audio") => {
+ setLocalUserVideo(user);
+ }
+ );
+ }
+ }, [client]);
+
+ return (
+
+ {/* */}
+
+ document.querySelector(`#${CanvasContainerID}`) || document.body
+ }
+ footer={null}
+ width={transToPxSize(props.width || DEFAULT_SIZE)}
+ height={
+ !props.autoHeight
+ ? transToPxSize(props.height || DEFAULT_SIZE)
+ : ""
+ }
+ onClose={(e: any) => {
+ props.visible.onChange(false);
+ }}
+ afterOpenChange={(visible: any) => {
+ if (!visible) {
+ props.onEvent("close");
+ }
+ }}
+ zIndex={Layers.drawer}
+ maskClosable={props.maskClosable}
+ mask={props.showMask}
+ >
+ {/* {
+ props.visible.onChange(false);
+ }}
+ >
+
+ */}
+
+
+ {/* */}
+
+ );
+ }
+ )
+ .setPropertyViewFn((children: any) => (
+ <>
+ {/* {(EditorContext.editorModeStatus === "logic" ||
+ EditorContext.editorModeStatus === "both") && (
+ <> */}
+
+ {children.appId.propertyView({
+ label: trans("meeting.appid"),
+ })}
+ {children.meetingName.propertyView({
+ label: trans("meeting.meetingName"),
+ })}
+ {children.localUserID.propertyView({
+ label: trans("meeting.localUserID"),
+ })}
+ {children.rtmToken.propertyView({
+ label: trans("meeting.rtmToken"),
+ })}
+ {children.rtcToken.propertyView({
+ label: trans("meeting.rtcToken"),
+ })}
+
+
+ {children.onEvent.getPropertyView()}
+ {children.onMeetingEvent.getPropertyView()}
+
+ {/* >
+ )} */}
+
+ {/* {(EditorContext.editorModeStatus === "layout" ||
+ EditorContext.editorModeStatus === "both") && (
+ <> */}
+ {/*
+ {children.placement.propertyView({
+ label: trans("meeting.placement"),
+ radioButton: true,
+ })}
+ {["top", "bottom"].includes(children.placement.getView())
+ ? children.autoHeight.getPropertyView()
+ : children.width.propertyView({
+ label: trans("meeting.width"),
+ tooltip: trans("meeting.widthTooltip"),
+ placeholder: DEFAULT_SIZE + "",
+ })}
+ {!children.autoHeight.getView() &&
+ ["top", "bottom"].includes(children.placement.getView()) &&
+ children.height.propertyView({
+ label: trans("meeting.height"),
+ tooltip: trans("meeting.heightTooltip"),
+ placeholder: DEFAULT_SIZE + "",
+ })}
+ {children.maskClosable.propertyView({
+ label: trans("meeting.maskClosable"),
+ })}
+ {children.showMask.propertyView({
+ label: trans("meeting.showMask"),
+ })}
+
+
+
+ {children.style.getPropertyView()}
+ */}
+ {/* > */}
+ {/* )} */}
+ >
+ ))
+ .build();
+
+ })();
+
+ MTComp = class extends MTComp {
+ autoHeight(): boolean {
+ return false;
+ }
+ };
+
+ MTComp = withMethodExposing(MTComp, [
+ {
+ method: {
+ name: "openDrawer",
+ params: [],
+ },
+ execute: (comp: any, values: any) => {
+ comp.children.visible.getView().onChange(true);
+ },
+ },
+ {
+ method: {
+ name: "startSharing",
+ params: [],
+ },
+ execute: async (comp: any, values: any) => {
+ if (!comp.children.meetingActive.getView().value) return;
+ let sharing = !comp.children.sharing.getView().value;
+ await shareScreen(sharing);
+ comp.children.sharing.change(sharing);
+ },
+ },
+ {
+ method: {
+ name: "audioControl",
+ description: trans("meeting.actionBtnDesc"),
+ params: [],
+ },
+ execute: async (comp: any, values: any) => {
+ if (!comp.children.meetingActive.getView().value) return;
+ let value = !comp.children.audioControl.getView().value;
+ comp.children.localUser.change({
+ user: userId + "",
+ audiostatus: value,
+ streamingVideo: comp.children.videoControl.getView().value,
+ speaking: false,
+ });
+ await turnOnMicrophone(value);
+ comp.children.audioControl.change(value);
+ },
+ },
+ {
+ method: {
+ name: "videoControl",
+ description: trans("meeting.actionBtnDesc"),
+ params: [],
+ },
+ execute: async (comp: any, values: any) => {
+ //check if meeting is active
+ if (!comp.children.meetingActive.getView().value) return;
+ //toggle videoControl
+ let value = !comp.children.videoControl.getView().value;
+ if (videoTrack) {
+ videoTrack.setEnabled(value);
+ } else {
+ await turnOnCamera(value);
+ }
+ //change my local user data
+ 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);
+ },
+ },
+ {
+ method: {
+ name: "startMeeting",
+ description: trans("meeting.actionBtnDesc"),
+ params: [],
+ },
+ execute: async (comp: any, values: any) => {
+ /* console.log("startMeeting ", {
+ // user: userId + "",
+ audiostatus: false,
+ speaking: false,
+ streamingVideo: true,
+ }); */
+ if (comp.children.meetingActive.getView().value) return;
+ userId =
+ comp.children.localUserID.getView().value === ""
+ ? uuidv4()
+ : comp.children.localUserID.getView().value;
+ comp.children.localUser.change({
+ user: userId + "",
+ audiostatus: false,
+ speaking: false,
+ streamingVideo: true,
+ });
+ /* console.log("startMeeting localUser ", {
+ 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 === ""
+ ? uuidv4()
+ : comp.children.meetingName.getView().value,
+ comp.children.rtmToken.getView().value,
+ comp.children.rtcToken.getView().value
+ );
+ comp.children.meetingActive.change(true);
+ },
+ },
+ {
+ method: {
+ name: "broadCast",
+ description: trans("meeting.broadCast"),
+ params: [],
+ },
+ execute: async (comp: any, values: any) => {
+ if (!comp.children.meetingActive.getView().value) return;
+ let messagedata =
+ values !== undefined && values[0] !== undefined ? values[0] : "";
+ let toUsers: any =
+ values !== undefined && values[1] !== undefined ? values[1] : "";
+
+ let message: any = {
+ time: Date.now(),
+ message: messagedata,
+ };
+
+ if (toUsers.length > 0 && toUsers[0] !== undefined) {
+ toUsers.forEach((peer: any) => {
+ message.to = peer;
+ sendPeerMessageRtm(message, String(peer));
+ });
+ } else {
+ sendMessageRtm(message);
+ }
+ },
+ },
+ {
+ method: {
+ name: "setMeetingName",
+ description: trans("meeting.meetingName"),
+ params: [],
+ },
+ execute: async (comp: any, values: any) => {
+ let meetingName: any = values[0];
+ comp.children.meetingName.change(meetingName);
+ },
+ },
+ {
+ method: {
+ name: "setUserName",
+ description: trans("meeting.userName"),
+ params: [],
+ },
+ execute: async (comp: any, values: any) => {
+ let userName: any = values[0];
+ let userLocal = comp.children.localUser.getView().value;
+ comp.children.localUser.change({ ...userLocal, userName: userName });
+ },
+ },
+ {
+ method: {
+ name: "setRTCToken",
+ description: trans("meeting.rtcToken"),
+ params: [],
+ },
+ execute: async (comp: any, values: any) => {
+ let rtcToken: any = values[0];
+ comp.children.rtcToken.change(rtcToken);
+ },
+ },
+ {
+ method: {
+ name: "setRTMToken",
+ description: trans("meeting.rtmToken"),
+ params: [],
+ },
+ execute: async (comp: any, values: any) => {
+ let rtmToken: any = values[0];
+ comp.children.rtmToken.change(rtmToken);
+ },
+ },
+ {
+ method: {
+ name: "endMeeting",
+ description: trans("meeting.actionBtnDesc"),
+ params: [],
+ },
+ execute: async (comp: any, values: any) => {
+ if (!comp.children.meetingActive.getView().value) return;
+
+ 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,
+ });
+ },
+ },
+ ]);
+
+ MeetingControllerComp = 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.meetingActive")),
+ new NameConfig("meetingName", trans("meeting.meetingName")),
+ new NameConfig("localUserID", trans("meeting.localUserID")),
+ new NameConfig("messages", trans("meeting.messages")),
+ new NameConfig("rtmToken", trans("meeting.rtmToken")),
+ new NameConfig("rtcToken", trans("meeting.rtcToken")),
+ ]);
+
+} else {
+ console.error("ContainerCompBuilder for Meeting Comp is not available. Please ensure that Lowcoder SDK version v2.4 or higher is installed.");
+}
+
+export { MeetingControllerComp };
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingStreamComp.tsx b/client/packages/lowcoder-comps/src/comps/agoraMeetingComp/videoMeetingStreamComp.tsx
similarity index 72%
rename from client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingStreamComp.tsx
rename to client/packages/lowcoder-comps/src/comps/agoraMeetingComp/videoMeetingStreamComp.tsx
index 68497e186..7c5569804 100644
--- a/client/packages/lowcoder/src/comps/comps/meetingComp/videoMeetingStreamComp.tsx
+++ b/client/packages/lowcoder-comps/src/comps/agoraMeetingComp/videoMeetingStreamComp.tsx
@@ -1,29 +1,28 @@
-import { BoolCodeControl, StringControl } from "comps/controls/codeControl";
-import { EditorContext } from "comps/editorState";
-import { withDefault } from "comps/generators/simpleGenerators";
-import { UICompBuilder } from "comps/generators/uiCompBuilder";
-import ReactResizeDetector from "react-resize-detector";
import {
+ NameConfig,
+ withDefault,
+ withExposingConfigs,
+ StringControl,
Section,
sectionNames,
-} from "lowcoder-design";
-import { trans } from "i18n";
-import styled from "styled-components";
-import {
+ AutoHeightControl,
+ EditorContext,
+ styled,
+ MeetingEventHandlerControl,
+ BoolCodeControl,
+ RefControl,
+ stringExposingStateControl,
+ StringStateControl,
+ UICompBuilder,
CommonNameConfig,
- NameConfig,
- withExposingConfigs,
-} from "../../generators/withExposing";
+} from "lowcoder-sdk";
import { ButtonStyleControl } from "./videobuttonCompConstants";
-import { RefControl } from "comps/controls/refControl";
+import { trans } from "../../i18n/comps";
+
+import { client } from "./meetingControllerComp";
+import type { IAgoraRTCRemoteUser } from "agora-rtc-sdk-ng";
import { useEffect, useRef, useState } from "react";
-import { AutoHeightControl } from "comps/controls/autoHeightControl";
-import { client } from "./videoMeetingControllerComp";
-import { IAgoraRTCRemoteUser } from "agora-rtc-sdk-ng";
-import { useContext } from "react";
-import { MeetingEventHandlerControl } from "comps/controls/eventHandlerControl";
-import { StringStateControl, stringExposingStateControl } from "comps/controls/codeStateControl";
-import { hiddenPropertyView } from "comps/utils/propertyUtils";
+import ReactResizeDetector from "react-resize-detector";
const VideoContainer = styled.video`
height: 100%;
@@ -42,14 +41,17 @@ const meetingStreamChildren = {
disabled: BoolCodeControl,
loading: BoolCodeControl,
style: ButtonStyleControl,
- viewRef: RefControl,
+ viewRef: RefControl,
userId: withDefault(stringExposingStateControl(""), "{{meeting1.localUser}}"),
- profileImageUrl: withDefault(StringStateControl, "https://api.dicebear.com/7.x/fun-emoji/svg?seed=Peanut&radius=50&backgroundColor=transparent&randomizeIds=true&eyes=wink,sleepClose"),
+ profileImageUrl: withDefault(
+ StringStateControl,
+ "https://api.dicebear.com/7.x/fun-emoji/svg?seed=Peanut&radius=50&backgroundColor=transparent&randomizeIds=true&eyes=wink,sleepClose"
+ ),
noVideoText: stringExposingStateControl(trans("meeting.noVideo")),
};
let VideoCompBuilder = (function () {
- return new UICompBuilder(meetingStreamChildren, (props) => {
+ return new UICompBuilder(meetingStreamChildren, (props: any) => {
const videoRef = useRef(null);
const conRef = useRef(null);
const [userId, setUserId] = useState();
@@ -116,7 +118,7 @@ let VideoCompBuilder = (function () {
!user.hasVideo &&
user.uid + "" !== userData.user &&
userData.user !== ""
- ) {
+ ) {
props.onEvent("videoOff");
}
}
@@ -128,13 +130,12 @@ let VideoCompBuilder = (function () {
setVideo(userData.streamingVideo);
}
}, [props.userId.value]);
-
- // console.log(props.userId);
+ console.log("userId", userId);
return (
- {(editorState) => (
+ {(editorState: any) => (
);
})
- .setPropertyViewFn((children) => (
+ .setPropertyViewFn((children: any) => (
<>
{children.userId.propertyView({ label: trans("meeting.videoId") })}
-
+
{children.profileImageUrl.propertyView({
label: trans("meeting.profileImageUrl"),
- placeholder: "https://api.dicebear.com/7.x/fun-emoji/svg?seed=Peanut&radius=50&backgroundColor=transparent&randomizeIds=true&eyes=wink,sleepClose",
+ placeholder:
+ "https://api.dicebear.com/7.x/fun-emoji/svg?seed=Peanut&radius=50&backgroundColor=transparent&randomizeIds=true&eyes=wink,sleepClose",
})}
- {(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && (
+ {/* {(useContext(EditorContext).editorModeStatus === "logic" ||
+ useContext(EditorContext).editorModeStatus === "both") && (
{children.onEvent.getPropertyView()}
{hiddenPropertyView(children)}
)}
- {(useContext(EditorContext).editorModeStatus === "layout" || useContext(EditorContext).editorModeStatus === "both") && (
- <>
- {children.autoHeight.getPropertyView()}
-
-
- {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()}
-
- >
- )}
+ {(useContext(EditorContext).editorModeStatus === "layout" ||
+ useContext(EditorContext).editorModeStatus === "both") && (
+ <> */}
+
+ {children.autoHeight.getPropertyView()}
+
+
+ {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()}
+
+ {/* > */}
+ {/* )} */}
>
))
.build();
})();
VideoCompBuilder = class extends VideoCompBuilder {
- override autoHeight(): boolean {
+ autoHeight(): boolean {
return this.children.autoHeight.getView();
}
};
export const VideoMeetingStreamComp = withExposingConfigs(VideoCompBuilder, [
- new NameConfig("loading", trans("button.loadingDesc")),
+ new NameConfig("loading", trans("meeting.loadingDesc")),
new NameConfig("profileImageUrl", trans("meeting.profileImageUrl")),
- ...CommonNameConfig,
+ ...CommonNameConfig,
]);
diff --git a/client/packages/lowcoder/src/comps/comps/meetingComp/videoSharingStreamComp.tsx b/client/packages/lowcoder-comps/src/comps/agoraMeetingComp/videoSharingStreamComp.tsx
similarity index 69%
rename from client/packages/lowcoder/src/comps/comps/meetingComp/videoSharingStreamComp.tsx
rename to client/packages/lowcoder-comps/src/comps/agoraMeetingComp/videoSharingStreamComp.tsx
index 192a09ed4..35b94ebdf 100644
--- a/client/packages/lowcoder/src/comps/comps/meetingComp/videoSharingStreamComp.tsx
+++ b/client/packages/lowcoder-comps/src/comps/agoraMeetingComp/videoSharingStreamComp.tsx
@@ -1,29 +1,26 @@
-import { BoolCodeControl, StringControl } from "comps/controls/codeControl";
-import { EditorContext } from "comps/editorState";
-import { withDefault } from "comps/generators";
-import { UICompBuilder } from "comps/generators/uiCompBuilder";
-import ReactResizeDetector from "react-resize-detector";
import {
+ NameConfig,
+ withDefault,
+ withExposingConfigs,
+ StringControl,
Section,
sectionNames,
-} from "lowcoder-design";
-import { trans } from "i18n";
-import styled from "styled-components";
-import {
+ AutoHeightControl,
+ EditorContext,
+ styled,
+ MeetingEventHandlerControl,
+ BoolCodeControl,
+ RefControl,
+ stringExposingStateControl,
+ UICompBuilder,
CommonNameConfig,
- NameConfig,
- withExposingConfigs,
-} from "../../generators/withExposing";
-import { ButtonStyleControl } from "./videobuttonCompConstants";
-import { RefControl } from "comps/controls/refControl";
+} from "lowcoder-sdk";
import { useEffect, useRef, useState } from "react";
-import { AutoHeightControl } from "comps/controls/autoHeightControl";
-import { client } from "./videoMeetingControllerComp";
-import { IAgoraRTCRemoteUser } from "agora-rtc-sdk-ng";
-import { useContext } from "react";
-import { MeetingEventHandlerControl } from "comps/controls/eventHandlerControl";
-import { stringExposingStateControl } from "comps/controls/codeStateControl";
-import { hiddenPropertyView } from "comps/utils/propertyUtils";
+import { client } from "./meetingControllerComp";
+import type { IAgoraRTCRemoteUser } from "agora-rtc-sdk-ng";
+import { trans } from "../../i18n/comps";
+import ReactResizeDetector from "react-resize-detector";
+import { ButtonStyleControl } from "./videobuttonCompConstants";
const VideoContainer = styled.video`
height: 100%;
@@ -42,13 +39,13 @@ const sharingStreamChildren = {
disabled: BoolCodeControl,
loading: BoolCodeControl,
style: ButtonStyleControl,
- viewRef: RefControl
,
+ viewRef: RefControl,
userId: withDefault(stringExposingStateControl(""), "{{meeting1.localUser}}"),
noVideoText: stringExposingStateControl(trans("meeting.noVideo")),
};
let SharingCompBuilder = (function () {
- return new UICompBuilder(sharingStreamChildren, (props) => {
+ return new UICompBuilder(sharingStreamChildren, (props: any) => {
const videoRef = useRef(null);
const conRef = useRef(null);
const [userId, setUserId] = useState();
@@ -95,8 +92,6 @@ let SharingCompBuilder = (function () {
client.on(
"user-unpublished",
(user: IAgoraRTCRemoteUser, mediaType: "video" | "audio") => {
- // console.log("user-unpublished");
-
if (mediaType === "audio") {
if (
!user.hasAudio &&
@@ -130,7 +125,7 @@ let SharingCompBuilder = (function () {
return (
- {(editorState) => (
+ {(editorState: any) => (
{userId ? (
@@ -161,7 +156,7 @@ let SharingCompBuilder = (function () {
) : (
<>>
)}
- {/*
{userName ?? ""}
-
*/}
+
)}
);
})
- .setPropertyViewFn((children) => (
+ .setPropertyViewFn((children: any) => (
<>