The Open Source Retool, Tooljet and Appsmith Alternative
+
Lowcoder is the best Retool, Appsmith or Tooljet Alternative.
- Build internal and customer facing Apps fast, with no limitations
+ Create internal and external software applications for your Company and your Customers with minimal coding experience.
-
+
## 📢 Use Lowcoder in 3 steps
1. Connect to any data sources or APIs.
@@ -21,32 +21,42 @@ It's cumbersome to create a single app. You had to design user interfaces, write
Low-code/No-code platforms are fast to get started with but quickly become unmaintainable and inflexible. This creates more problems than it solves.
-Retool-like solutions are great for their simplicity and flexibility, but they can also be limited in different ways compared to frameworks like React/Vue.
+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 apps.
+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 domain-specific language that UI-configurable block is the first-class citizen.
+- 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.
## 🪄 Features
-- **Visual UI builder** with 50+ built-in components.
+- **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 React component** instead of iFrame (!). [Demo](https://github.com/lowcoder-org/lowcoder-sdk-demo)
+- **Embed Lowcoder Apps as native parts of any Website** instead of iFrame (!). [Demo](https://github.com/lowcoder-org/lowcoder-sdk-demo)
+- **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
- **JavaScript supported everywhere** to transform data, control components, etc.
- **Role-based access control (RBAC)** for granular permission management.
- **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.
+- **Self Hosting** to use Lowcoder in your internal company network, even behind the firewall.
- **Free Community Cloud** to start within a minute and build your first Apps. [Start here](https://app.lowcoder.cloud)
## 🏆 Comparisons
+### Lowcoder vs Teams, Google Meets, Zoom
+- build a Meeting tool with peace in mind. Blue buttons - ok. Red corners or circle Videostream - ok too.
+- embedd applications in your Video-Meetings, so attendees can enjoy collaborative "anything". From shopping to working and gaming...
+### Lowcoder vs Powerapps
+- build a apps way faster than in Power Apps. Save up to 50& of the time at least.
+- Use self-hosting to keep all apps and data under your control for example at the own baremetals.
### Lowcoder vs Retool
- 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 create and use their own components instead of depending on official updates.
+- 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!
- The EE Version of Lowcoder comes with a much better pricing model, so you have no "per-user costs".
### Lowcoder vs Appsmith, Tooljet
@@ -55,15 +65,17 @@ Lowcoder wants to take a step forward. More specifically, Lowcoder is
- In Lowcoder, you can reuse common structures when building apps with modules and query library features.
### Lowcoder vs Mendix, Outsystems, Pega
- Lowcoder is modern. The codebase is fresh and uses modern standards.
-- Lowcoder Apps do not need a compile and deployment. Just publish and use.
+- Lowcoder Apps do not need a compile and deployment. Just publish and use. Within seconds!
- Lowcoder Apps can get embedded natively in websites and apps, even in mobile apps.
### Lowcoder vs internal Tool platforms
- Lowcoder supports internal tools like admin panels perfectly, but also customer-facing apps can get developed and published.
- The Lowcoder UI builder is straightforward and better to use than Bubble.
- App release cycles and updates can be done nearly daily without service downtimes for customers and users.
+
## 👐 Support and Community
If you have any questions, please feel free to contact us or share them with our community. Our team is here ready to help.
+And we mean it... Day by day!
📮 Best way is to chat with us on [Discord](https://discord.gg/qMG9uTmAx2)
@@ -72,10 +84,16 @@ If you have any questions, please feel free to contact us or share them with our
🔎 Submit an issue here on [GitHub](https://github.com/lowcoder-org/lowcoder/issues)
## 💻 Deployment Options
-You can access Lowcoder from [cloud-hosted version](https://www.lowcoder.cloud/) at any time, or use the following resources for deploying Lowcoder on different platforms:
-- [Docker](docs/self-hosting/README.md)
+You can access Lowcoder from [cloud-hosted version](https://app.lowcoder.cloud/) at any time, or use the following resources for deploying 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/develop/client)
+
+## 🥇 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 ... @CHSchuepfer. Thank you very much!
\ No newline at end of file
diff --git a/app.json b/app.json
index 0889503a9..5d6a647c0 100644
--- a/app.json
+++ b/app.json
@@ -1,11 +1,16 @@
{
"name": "lowcoder",
- "description": "Lowcoder is a developer-friendly open-source low code platform to build internal apps within minutes.",
+ "description": "An all-in-one IDE to create internal or customer-facing apps. · Visual UI builder with 50+ built-in components",
"repository": "https://github.com/lowcoder-org/lowcoder",
- "logo": "https://cdn-files.openblocks.dev/logo.png",
+ "logo": "https://lowcoder.cloud/images/webclip.png",
"keywords": [
- "low code",
- "develop tool"
+ "LowCode",
+ "Low code",
+ "develop tool",
+ "Fast Application Development",
+ "Rapid development",
+ "Collaboration tool",
+ "Video conferencing"
],
"stack": "container",
"formation": {
diff --git a/client/VERSION b/client/VERSION
index cd57a8b95..9671f9a9b 100644
--- a/client/VERSION
+++ b/client/VERSION
@@ -1 +1 @@
-2.1.5
+2.1.7
\ No newline at end of file
diff --git a/client/packages/lowcoder-design/src/components/Section.tsx b/client/packages/lowcoder-design/src/components/Section.tsx
index a0c18134f..da58f9588 100644
--- a/client/packages/lowcoder-design/src/components/Section.tsx
+++ b/client/packages/lowcoder-design/src/components/Section.tsx
@@ -142,5 +142,5 @@ export const sectionNames = {
validation: trans("prop.validation"),
layout: trans("prop.layout"),
style: trans("prop.style"),
- meetings : trans("prop.meetings"),
+ meetings : trans("prop.meetings"), // added by Falk Wolsky
};
diff --git a/client/packages/lowcoder-design/src/icons/icon-autocomplete-comp.svg b/client/packages/lowcoder-design/src/icons/icon-autocomplete-comp.svg
index dd882963a..505c59adb 100644
--- a/client/packages/lowcoder-design/src/icons/icon-autocomplete-comp.svg
+++ b/client/packages/lowcoder-design/src/icons/icon-autocomplete-comp.svg
@@ -1 +1,20 @@
-
\ No newline at end of file
+
+
+
diff --git a/client/packages/lowcoder-design/src/icons/icon-comment-comp.svg b/client/packages/lowcoder-design/src/icons/icon-comment-comp.svg
index b6828e6a0..5a737ee86 100644
--- a/client/packages/lowcoder-design/src/icons/icon-comment-comp.svg
+++ b/client/packages/lowcoder-design/src/icons/icon-comment-comp.svg
@@ -1 +1,13 @@
-
\ No newline at end of file
+
+
+
diff --git a/client/packages/lowcoder-design/src/icons/icon-mention-comp.svg b/client/packages/lowcoder-design/src/icons/icon-mention-comp.svg
index 4c04c61e2..5b311e0b4 100644
--- a/client/packages/lowcoder-design/src/icons/icon-mention-comp.svg
+++ b/client/packages/lowcoder-design/src/icons/icon-mention-comp.svg
@@ -1 +1,20 @@
-
\ No newline at end of file
+
+
+
diff --git a/client/packages/lowcoder-design/src/icons/icon-timeline-comp.svg b/client/packages/lowcoder-design/src/icons/icon-timeline-comp.svg
index 329690d6d..e47f5fc79 100644
--- a/client/packages/lowcoder-design/src/icons/icon-timeline-comp.svg
+++ b/client/packages/lowcoder-design/src/icons/icon-timeline-comp.svg
@@ -1 +1,23 @@
-
\ No newline at end of file
+
+
+
diff --git a/client/packages/lowcoder-design/src/icons/icon-undo.svg b/client/packages/lowcoder-design/src/icons/icon-undo.svg
index e7296453f..a1199d1bb 100644
--- a/client/packages/lowcoder-design/src/icons/icon-undo.svg
+++ b/client/packages/lowcoder-design/src/icons/icon-undo.svg
@@ -1,3 +1,10 @@
-
\ No newline at end of file
+
+
+
diff --git a/client/packages/lowcoder/site.webmanifest b/client/packages/lowcoder/site.webmanifest
new file mode 100644
index 000000000..91352b1a9
--- /dev/null
+++ b/client/packages/lowcoder/site.webmanifest
@@ -0,0 +1 @@
+{"name":"Lowcoder.cloud","short_name":"Lowcoder","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
diff --git a/client/packages/lowcoder/src/assets/images/android-chrome-192x192.png b/client/packages/lowcoder/src/assets/images/android-chrome-192x192.png
new file mode 100644
index 000000000..23d6e525e
Binary files /dev/null and b/client/packages/lowcoder/src/assets/images/android-chrome-192x192.png differ
diff --git a/client/packages/lowcoder/src/assets/images/android-chrome-512x512.png b/client/packages/lowcoder/src/assets/images/android-chrome-512x512.png
new file mode 100644
index 000000000..c690d113c
Binary files /dev/null and b/client/packages/lowcoder/src/assets/images/android-chrome-512x512.png differ
diff --git a/client/packages/lowcoder/src/assets/images/apple-touch-icon.png b/client/packages/lowcoder/src/assets/images/apple-touch-icon.png
new file mode 100644
index 000000000..6fff652aa
Binary files /dev/null and b/client/packages/lowcoder/src/assets/images/apple-touch-icon.png differ
diff --git a/client/packages/lowcoder/src/assets/images/favicon-16x16.png b/client/packages/lowcoder/src/assets/images/favicon-16x16.png
new file mode 100644
index 000000000..a87cf442d
Binary files /dev/null and b/client/packages/lowcoder/src/assets/images/favicon-16x16.png differ
diff --git a/client/packages/lowcoder/src/assets/images/favicon-32x32.png b/client/packages/lowcoder/src/assets/images/favicon-32x32.png
new file mode 100644
index 000000000..127dc44cb
Binary files /dev/null and b/client/packages/lowcoder/src/assets/images/favicon-32x32.png differ
diff --git a/client/packages/lowcoder/src/assets/images/favicon.ico b/client/packages/lowcoder/src/assets/images/favicon.ico
index 91f699e41..91f1543d0 100644
Binary files a/client/packages/lowcoder/src/assets/images/favicon.ico and b/client/packages/lowcoder/src/assets/images/favicon.ico differ
diff --git a/client/packages/lowcoder/src/components/CompName.tsx b/client/packages/lowcoder/src/components/CompName.tsx
index cf63dbab4..2f0b26b8b 100644
--- a/client/packages/lowcoder/src/components/CompName.tsx
+++ b/client/packages/lowcoder/src/components/CompName.tsx
@@ -11,6 +11,7 @@ import { GreyTextColor } from "constants/style";
import { UICompType } from "comps/uiCompRegistry";
import { trans } from "i18n";
import { getComponentDocUrl } from "comps/utils/compDocUtil";
+import { getComponentPlaygroundUrl } from "comps/utils/compDocUtil";
import { parseCompType } from "comps/utils/remote";
const CompDiv = styled.div<{ width?: number; hasSearch?: boolean; showSearch?: boolean }>`
@@ -78,6 +79,7 @@ export const CompName = (props: Iprops) => {
const compType = selectedComp.children.compType.getView() as UICompType;
const compInfo = parseCompType(compType);
const docUrl = getComponentDocUrl(compType);
+ const playgroundUrl = getComponentPlaygroundUrl(compType);
const items: EditPopoverItemType[] = [];
@@ -99,6 +101,16 @@ export const CompName = (props: Iprops) => {
});
}
+ if (playgroundUrl) {
+ items.push({
+ text: trans("comp.menuViewPlayground"),
+ onClick: () => {
+ window.open(playgroundUrl, "_blank");
+ },
+ });
+ }
+
+
if (compInfo.isRemote) {
items.push({
text: trans("comp.menuUpgradeToLatest"),
diff --git a/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx b/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx
index 62b55a7da..0999a4012 100644
--- a/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx
@@ -38,15 +38,13 @@ export class LayoutMenuItemComp extends MultiBaseComp {
}
override getPropertyView(): ReactNode {
- const isLeaf = this.children.items.getView().length === 0;
return (
<>
- {isLeaf &&
- this.children.action.propertyView({
- onAppChange: (label) => {
- label && this.children.label.dispatchChangeValueAction(label);
- },
- })}
+ {this.children.action.propertyView({
+ onAppChange: (label) => {
+ label && this.children.label.dispatchChangeValueAction(label);
+ },
+ })}
{this.children.label.propertyView({ label: trans("label") })}
{this.children.icon.propertyView({
label: trans("icon"),
@@ -98,12 +96,17 @@ const LayoutMenuItemCompMigrate = migrateOldData(LayoutMenuItemComp, (oldData: a
export class LayoutMenuItemListComp extends list(LayoutMenuItemCompMigrate) {
addItem(value?: any) {
const data = this.getView();
+
this.dispatch(
this.pushAction(
- value || {
- label: trans("menuItem") + " " + (data.length + 1),
- itemKey: genRandomKey(),
- }
+ value
+ ? {
+ ...value,
+ itemKey: value.itemKey || genRandomKey(),
+ } : {
+ label: trans("menuItem") + " " + (data.length + 1),
+ itemKey: genRandomKey(),
+ }
)
);
}
diff --git a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx
index 5e8d47320..368e459a9 100644
--- a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx
+++ b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx
@@ -1,4 +1,4 @@
-import { Layout, Menu as AntdMenu, MenuProps } from "antd";
+import { Layout, Menu as AntdMenu, MenuProps, Segmented } from "antd";
import MainContent from "components/layout/MainContent";
import { LayoutMenuItemComp, LayoutMenuItemListComp } from "comps/comps/layout/layoutMenuItemComp";
import { menuPropertyView } from "comps/comps/navComp/components/MenuItemList";
@@ -8,12 +8,38 @@ import { withDispatchHook } from "comps/generators/withDispatchHook";
import { NameAndExposingInfo } from "comps/utils/exposingTypes";
import { ALL_APPLICATIONS_URL } from "constants/routesURL";
import { TopHeaderHeight } from "constants/style";
-import { Section } from "lowcoder-design";
+import { Section, controlItem, sectionNames } from "lowcoder-design";
import { trans } from "i18n";
import { EditorContainer, EmptyContent } from "pages/common/styledComponent";
import { useCallback, useEffect, useMemo, useState } from "react";
import styled from "styled-components";
import { isUserViewMode, useAppPathParam } from "util/hooks";
+import { StringControl, jsonControl } from "comps/controls/codeControl";
+import { styleControl } from "comps/controls/styleControl";
+import {
+ NavLayoutStyle,
+ NavLayoutItemStyle,
+ NavLayoutItemStyleType,
+ NavLayoutItemHoverStyle,
+ NavLayoutItemHoverStyleType,
+ NavLayoutItemActiveStyle,
+ NavLayoutItemActiveStyleType,
+} from "comps/controls/styleControlConstants";
+import { dropdownControl } from "comps/controls/dropdownControl";
+import _ from "lodash";
+import { check } from "util/convertUtils";
+import { genRandomKey } from "comps/utils/idGenerator";
+import history from "util/history";
+import {
+ DataOption,
+ DataOptionType,
+ ModeOptions,
+ jsonMenuItems,
+ menuItemStyleOptions
+} from "./navLayoutConstants";
+
+const DEFAULT_WIDTH = 240;
+type MenuItemStyleOptionValue = "normal" | "hover" | "active";
const StyledSide = styled(Layout.Sider)`
max-height: calc(100vh - ${TopHeaderHeight});
@@ -39,22 +65,192 @@ const ContentWrapper = styled.div`
}
`;
+const StyledMenu = styled(AntdMenu)<{
+ $navItemStyle?: NavLayoutItemStyleType & { width: string},
+ $navItemHoverStyle?: NavLayoutItemHoverStyleType,
+ $navItemActiveStyle?: NavLayoutItemActiveStyleType,
+}>`
+ .ant-menu-item {
+ height: auto;
+ width: ${(props) => props.$navItemStyle?.width};
+ background-color: ${(props) => props.$navItemStyle?.background};
+ color: ${(props) => props.$navItemStyle?.text};
+ border-radius: ${(props) => props.$navItemStyle?.radius} !important;
+ border: ${(props) => `1px solid ${props.$navItemStyle?.border}`};
+ margin: ${(props) => props.$navItemStyle?.margin};
+ padding: ${(props) => props.$navItemStyle?.padding};
+
+ }
+ .ant-menu-item-active {
+ background-color: ${(props) => props.$navItemHoverStyle?.background} !important;
+ color: ${(props) => props.$navItemHoverStyle?.text} !important;
+ border: ${(props) => `1px solid ${props.$navItemHoverStyle?.border}`};
+ }
+
+ .ant-menu-item-selected {
+ background-color: ${(props) => props.$navItemActiveStyle?.background} !important;
+ color: ${(props) => props.$navItemActiveStyle?.text} !important;
+ border: ${(props) => `1px solid ${props.$navItemActiveStyle?.border}`};
+ }
+
+ .ant-menu-submenu {
+ margin: ${(props) => props.$navItemStyle?.margin};
+ width: ${(props) => props.$navItemStyle?.width};
+
+ .ant-menu-submenu-title {
+ width: 100%;
+ height: auto !important;
+ background-color: ${(props) => props.$navItemStyle?.background};
+ color: ${(props) => props.$navItemStyle?.text};
+ border-radius: ${(props) => props.$navItemStyle?.radius} !important;
+ border: ${(props) => `1px solid ${props.$navItemStyle?.border}`};
+ margin: 0;
+ padding: ${(props) => props.$navItemStyle?.padding};
+
+ }
+
+ .ant-menu-item {
+ width: 100%;
+ }
+
+ &.ant-menu-submenu-active {
+ >.ant-menu-submenu-title {
+ width: 100%;
+ background-color: ${(props) => props.$navItemHoverStyle?.background} !important;
+ color: ${(props) => props.$navItemHoverStyle?.text} !important;
+ border: ${(props) => `1px solid ${props.$navItemHoverStyle?.border}`};
+ }
+ }
+ &.ant-menu-submenu-selected {
+ >.ant-menu-submenu-title {
+ width: 100%;
+ background-color: ${(props) => props.$navItemActiveStyle?.background} !important;
+ color: ${(props) => props.$navItemActiveStyle?.text} !important;
+ border: ${(props) => `1px solid ${props.$navItemActiveStyle?.border}`};
+ }
+ }
+ }
+
+`;
+
+const StyledImage = styled.img`
+ height: 1em;
+ color: currentColor;
+`;
+
+const defaultStyle = {
+ radius: '0px',
+ margin: '0px',
+ padding: '0px',
+}
+
+type UrlActionType = {
+ url?: string;
+ newTab?: boolean;
+}
+
+export type MenuItemNode = {
+ label: string;
+ key: string;
+ hidden?: boolean;
+ icon?: any;
+ action?: UrlActionType,
+ children?: MenuItemNode[];
+}
+
+function checkDataNodes(value: any, key?: string): MenuItemNode[] | undefined {
+ return check(value, ["array", "undefined"], key, (node, k) => {
+ check(node, ["object"], k);
+ check(node["label"], ["string"], "label");
+ check(node["hidden"], ["boolean", "undefined"], "hidden");
+ check(node["icon"], ["string", "undefined"], "icon");
+ check(node["action"], ["object", "undefined"], "action");
+ checkDataNodes(node["children"], "children");
+ return node;
+ });
+}
+
+function convertTreeData(data: any) {
+ return data === "" ? [] : checkDataNodes(data) ?? [];
+}
+
let NavTmpLayout = (function () {
const childrenMap = {
+ dataOptionType: dropdownControl(DataOptionType, DataOption.Manual),
items: withDefault(LayoutMenuItemListComp, [
{
label: trans("menuItem") + " 1",
+ itemKey: genRandomKey(),
},
]),
+ jsonItems: jsonControl(convertTreeData, jsonMenuItems),
+ width: withDefault(StringControl, DEFAULT_WIDTH),
+ backgroundImage: withDefault(StringControl, ""),
+ mode: dropdownControl(ModeOptions, "inline"),
+ navStyle: withDefault(styleControl(NavLayoutStyle), defaultStyle),
+ navItemStyle: withDefault(styleControl(NavLayoutItemStyle), defaultStyle),
+ navItemHoverStyle: withDefault(styleControl(NavLayoutItemHoverStyle), {}),
+ navItemActiveStyle: withDefault(styleControl(NavLayoutItemActiveStyle), {}),
};
return new MultiCompBuilder(childrenMap, (props) => {
return null;
})
.setPropertyViewFn((children) => {
+ const [styleSegment, setStyleSegment] = useState('normal')
+
return (
- <>
- {menuPropertyView(children.items)}
- >
+