Skip to content

Commit 5f54598

Browse files
committed
Merge branch 'dev' into actions_js_console
2 parents d6b0ef0 + 5467971 commit 5f54598

33 files changed

+721
-186
lines changed

client/packages/lowcoder/src/api/applicationApi.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class ApplicationApi extends Api {
9999
static publicToMarketplaceURL = (applicationId: string) => `/applications/${applicationId}/public-to-marketplace`;
100100
static getMarketplaceAppURL = (applicationId: string) => `/applications/${applicationId}/view_marketplace`;
101101
static setAppEditingStateURL = (applicationId: string) => `/applications/editState/${applicationId}`;
102+
static getAvailableGroupsMembersURL = (applicationId: string) => `/applications/${applicationId}/groups-members/available`;
102103
static serverSettingsURL = () => `/serverSettings`;
103104

104105
static fetchHomeData(request: HomeDataPayload): AxiosPromise<HomeDataResponse> {
@@ -217,6 +218,10 @@ class ApplicationApi extends Api {
217218
});
218219
}
219220

221+
static getAvailableGroupsMembers(applicationId: string, search: string): AxiosPromise<any> {
222+
return Api.get(ApplicationApi.getAvailableGroupsMembersURL(applicationId), {search})
223+
}
224+
220225
/**
221226
* set app as public
222227
*/

client/packages/lowcoder/src/api/userApi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ export interface GetMyOrgsResponse extends ApiResponse {
6565
data: Array<{
6666
orgId: string;
6767
orgName: string;
68+
createdAt?: number;
69+
updatedAt?: number;
6870
}>;
6971
pageNum: number;
7072
pageSize: number;

client/packages/lowcoder/src/components/PermissionDialog/Permission.tsx

Lines changed: 83 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ import {
33
CloseIcon,
44
CommonTextLabel,
55
CustomSelect,
6+
Search,
67
TacoButton,
78
} from "lowcoder-design";
8-
import { useEffect, useRef, useState } from "react";
9+
import { useEffect, useRef, useState, useCallback } from "react";
910
import styled from "styled-components";
11+
import { debounce } from "lodash";
1012
import ProfileImage from "pages/common/profileImage";
11-
import { useDispatch, useSelector } from "react-redux";
12-
import { fetchGroupsAction, fetchOrgUsersAction } from "redux/reduxActions/orgActions";
13-
import { getOrgGroups, getOrgUsers } from "redux/selectors/orgSelectors";
14-
import { OrgGroup, OrgUser } from "constants/orgConstants";
15-
import { ApplicationPermissionType, ApplicationRoleType } from "constants/applicationConstants";
13+
import { useSelector } from "react-redux";
14+
import { ApplicationPermissionType, ApplicationRoleType, GroupsMembersPermission } from "constants/applicationConstants";
1615
import {
1716
PermissionItemName,
1817
RoleSelectOption,
@@ -27,6 +26,8 @@ import { getUser } from "redux/selectors/usersSelectors";
2726
import { EmptyContent } from "pages/common/styledComponent";
2827
import { trans } from "i18n";
2928
import { PermissionItem } from "./PermissionList";
29+
import { currentApplication } from "@lowcoder-ee/redux/selectors/applicationSelector";
30+
import { fetchAvailableGroupsMembers } from "@lowcoder-ee/util/pagination/axios";
3031

3132
const AddAppUserContent = styled.div`
3233
display: flex;
@@ -86,20 +87,19 @@ const PermissionSelectWrapper = styled.div`
8687
padding: 4px 8px;
8788
margin-top: 8px;
8889
background: #fdfdfd;
89-
outline: 1px solid #d7d9e0;
90-
border-radius: 4px;
90+
outline: 1px dashed #d7d9e0;
9191
9292
.ant-select {
9393
font-size: 13px;
9494
line-height: 13px;
9595
}
9696
9797
&:hover {
98-
outline: 1px solid #8b8fa3;
98+
outline: 1px dashed #8b8fa3;
9999
}
100100
101101
&:focus-within {
102-
outline: 1px solid #315efb;
102+
outline: 1px dashed rgb(203, 212, 245);
103103
border-radius: 4px;
104104
box-shadow: 0 0 0 3px rgb(24 144 255 / 20%);
105105
}
@@ -199,48 +199,34 @@ type PermissionAddEntity = {
199199
key: string;
200200
};
201201

202-
/**
203-
* compose users and groups's permissions, filter the data
204-
*
205-
* @param orgGroups groups
206-
* @param orgUsers users
207-
* @param currentUser currentUser
208-
* @param filterItems filterItems
209-
*/
202+
function isGroup(data: GroupsMembersPermission) {
203+
return data?.type === "Group"
204+
}
205+
210206
function getPermissionOptionView(
211-
orgGroups: OrgGroup[],
212-
orgUsers: OrgUser[],
213-
currentUser: User,
207+
groupsMembers: GroupsMembersPermission[],
214208
filterItems: PermissionItem[]
215209
): AddAppOptionView[] {
216-
let permissionViews: AddAppOptionView[] = orgGroups.map((group) => {
210+
211+
let permissionsViews = groupsMembers?.map((user) => {
217212
return {
218-
type: "GROUP",
219-
id: group.groupId,
220-
name: group.groupName,
221-
};
222-
});
223-
permissionViews = permissionViews.concat(
224-
orgUsers.map((user) => {
225-
return {
226-
type: "USER",
227-
id: user.userId,
228-
name: user.name,
229-
avatarUrl: user.avatarUrl,
230-
};
231-
})
232-
);
233-
permissionViews = permissionViews.filter(
234-
(v) =>
235-
!filterItems.find((i) => i.id === v.id && i.type === v.type) &&
236-
!(v.type === "USER" && v.id === currentUser.id)
213+
type: user.type as ApplicationPermissionType,
214+
id: isGroup(user) ? user.data.groupId : user.data.userId,
215+
name: isGroup(user) ? user.data.groupName : user.data.name,
216+
...(isGroup(user) ? {} : { avatarUrl: user.data.avatarUrl })
217+
}
218+
})
219+
220+
permissionsViews = permissionsViews.filter((v) =>
221+
!filterItems.find((i) => i.id === v.id && i.type === v.type)
237222
);
238-
return permissionViews;
223+
224+
return permissionsViews.filter((v) => v.id && v.name) as AddAppOptionView[];
239225
}
240226

241227
function PermissionSelectorOption(props: { optionView: AddAppOptionView }) {
242228
const { optionView } = props;
243-
const groupIcon = optionView.type === "GROUP" && (
229+
const groupIcon = optionView.type === "Group" && (
244230
<StyledGroupIcon $color={getInitialsAndColorCode(optionView.name)[1]} />
245231
);
246232
return (
@@ -258,7 +244,7 @@ function PermissionSelectorOption(props: { optionView: AddAppOptionView }) {
258244

259245
function PermissionSelectorLabel(props: { view: AddAppOptionView }) {
260246
const { view } = props;
261-
const groupIcon = view.type === "GROUP" && (
247+
const groupIcon = view.type === "Group" && (
262248
<StyledGroupIcon $color={getInitialsAndColorCode(view.name)[1]} $side={9} />
263249
);
264250
return (
@@ -309,12 +295,52 @@ const PermissionSelector = (props: {
309295
filterItems: PermissionItem[];
310296
supportRoles: { label: string; value: PermissionRole }[];
311297
}) => {
312-
const orgGroups = useSelector(getOrgGroups);
313-
const orgUsers = useSelector(getOrgUsers);
314298
const { selectedItems, setSelectRole, setSelectedItems, user } = props;
315-
const optionViews = getPermissionOptionView(orgGroups, orgUsers, user, props.filterItems);
316299
const [roleSelectVisible, setRoleSelectVisible] = useState(false);
317300
const selectRef = useRef<HTMLDivElement>(null);
301+
const [optionViews, setOptionViews] = useState<AddAppOptionView[]>()
302+
const [searchValue, setSearchValue] = useState("");
303+
const [isLoading, setIsLoading] = useState(false);
304+
const application = useSelector(currentApplication)
305+
306+
const debouncedUserSearch = useCallback(
307+
debounce((searchTerm: string) => {
308+
if (!application) return;
309+
310+
setIsLoading(true);
311+
fetchAvailableGroupsMembers(application.applicationId, searchTerm).then(res => {
312+
if(res.success) {
313+
setOptionViews(getPermissionOptionView(res.data, props.filterItems))
314+
}
315+
setIsLoading(false);
316+
}).catch(() => {
317+
setIsLoading(false);
318+
});
319+
}, 500),
320+
[application, props.filterItems]
321+
);
322+
323+
useEffect(() => {
324+
debouncedUserSearch(searchValue);
325+
326+
return () => {
327+
debouncedUserSearch.cancel();
328+
};
329+
}, [searchValue, debouncedUserSearch]);
330+
331+
useEffect(() => {
332+
if (!application) return;
333+
334+
setIsLoading(true);
335+
fetchAvailableGroupsMembers(application.applicationId, "").then(res => {
336+
if(res.success) {
337+
setOptionViews(getPermissionOptionView(res.data, props.filterItems))
338+
}
339+
setIsLoading(false);
340+
}).catch(() => {
341+
setIsLoading(false);
342+
});
343+
}, [application, props.filterItems]);
318344

319345
useEffect(() => {
320346
setRoleSelectVisible(selectedItems.length > 0);
@@ -325,12 +351,18 @@ const PermissionSelector = (props: {
325351

326352
return (
327353
<>
354+
<Search
355+
placeholder={trans("home.addPermissionPlaceholder")}
356+
value={searchValue}
357+
onChange={(e) => setSearchValue(e.target.value)}
358+
/>
328359
<PermissionSelectWrapper>
329360
<AddPermissionsSelect
330361
open
331362
ref={selectRef}
332-
placeholder={trans("home.addPermissionPlaceholder")}
363+
placeholder={trans("home.selectedUsersAndGroups")}
333364
mode="multiple"
365+
showSearch={false}
334366
getPopupContainer={() => document.getElementById("add-app-user-permission-dropdown")!}
335367
optionLabelProp="label"
336368
tagRender={PermissionTagRender}
@@ -350,7 +382,7 @@ const PermissionSelector = (props: {
350382
setSelectedItems(selectedItems.filter((item) => item.key !== option.key));
351383
}}
352384
>
353-
{optionViews.map((view) => {
385+
{optionViews?.map((view) => {
354386
return (
355387
<CustomSelect.Option
356388
key={`${view.type}-${view.id}`}
@@ -395,16 +427,10 @@ export const Permission = (props: {
395427
addPermission: (userIds: string[], groupIds: string[], role: string) => void;
396428
}) => {
397429
const { onCancel } = props;
398-
const dispatch = useDispatch();
399430
const user = useSelector(getUser);
400431
const [selectRole, setSelectRole] = useState<ApplicationRoleType>("viewer");
401432
const [selectedItems, setSelectedItems] = useState<PermissionAddEntity[]>([]);
402433

403-
useEffect(() => {
404-
dispatch(fetchOrgUsersAction(user.currentOrgId));
405-
dispatch(fetchGroupsAction(user.currentOrgId));
406-
}, []);
407-
408434
return (
409435
<AddAppUserContent>
410436
<CommonTextLabel style={{ marginTop: "16px" }}>
@@ -426,10 +452,10 @@ export const Permission = (props: {
426452
buttonType="primary"
427453
onClick={() => {
428454
const uids = selectedItems
429-
.filter((item) => item.type === "USER")
455+
.filter((item) => item.type === "User")
430456
.map((item) => item.id);
431457
const gids = selectedItems
432-
.filter((item) => item.type === "GROUP")
458+
.filter((item) => item.type === "Group")
433459
.map((item) => item.id);
434460
if (uids.length === 0 && gids.length === 0) {
435461
onCancel();

client/packages/lowcoder/src/comps/comps/buttonComp/buttonComp.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
ButtonCompWrapper,
2222
buttonRefMethods,
2323
ButtonStyleControl,
24+
DisabledButtonStyleControl,
2425
} from "./buttonCompConstants";
2526
import { RefControl } from "comps/controls/refControl";
2627
import { Tooltip } from "antd";
@@ -133,6 +134,7 @@ const childrenMap = {
133134
prefixIcon: IconControl,
134135
suffixIcon: IconControl,
135136
style: ButtonStyleControl,
137+
disabledStyle: DisabledButtonStyleControl,
136138
animationStyle: styleControl(AnimationStyle, 'animationStyle'),
137139
viewRef: RefControl<HTMLElement>,
138140
tooltip: StringControl
@@ -173,6 +175,7 @@ const ButtonPropertyView = React.memo((props: {
173175
{props.children.suffixIcon.propertyView({ label: trans("button.suffixIcon") })}
174176
</Section>
175177
<Section name={sectionNames.style}>{props.children.style.getPropertyView()}</Section>
178+
<Section name={trans("prop.disabledStyle")}>{props.children.disabledStyle.getPropertyView()}</Section>
176179
</>
177180
)}
178181
</>
@@ -212,6 +215,7 @@ const ButtonView = React.memo((props: ToViewReturn<ChildrenType>) => {
212215
<Button100
213216
ref={props.viewRef}
214217
$buttonStyle={props.style}
218+
$disabledStyle={props.disabledStyle}
215219
loading={props.loading}
216220
disabled={
217221
props.disabled ||

client/packages/lowcoder/src/comps/comps/buttonComp/buttonCompConstants.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { default as Button } from "antd/es/button";
22
import { styleControl } from "comps/controls/styleControl";
3-
import { ButtonStyleType, ButtonStyle } from "comps/controls/styleControlConstants";
3+
import { ButtonStyleType, ButtonStyle, DisabledButtonStyle, DisabledButtonStyleType } from "comps/controls/styleControlConstants";
44
import { migrateOldData } from "comps/generators/simpleGenerators";
55
import styled, { css } from "styled-components";
66
import { genActiveColor, genHoverColor } from "lowcoder-design";
77
import { refMethods } from "comps/generators/withMethodExposing";
88
import { blurMethod, clickMethod, focusWithOptions } from "comps/utils/methodUtils";
99

10-
export function getButtonStyle(buttonStyle: ButtonStyleType) {
10+
export function getButtonStyle(buttonStyle: ButtonStyleType, disabledStyle: DisabledButtonStyleType = {} as any) {
1111
const hoverColor = buttonStyle.background && genHoverColor(buttonStyle.background);
1212
const activeColor = buttonStyle.background && genActiveColor(buttonStyle.background);
1313
return css`
@@ -48,12 +48,23 @@ export function getButtonStyle(buttonStyle: ButtonStyleType) {
4848
: buttonStyle.border} !important;
4949
}
5050
}
51+
52+
/* Disabled state styling */
53+
&:disabled,
54+
&.ant-btn-disabled {
55+
color: ${disabledStyle.disabledText};
56+
background: ${disabledStyle.disabledBackground};
57+
cursor: not-allowed;
58+
}
5159
}
5260
`;
5361
}
5462

55-
export const Button100 = styled(Button)<{ $buttonStyle?: ButtonStyleType }>`
56-
${(props) => props.$buttonStyle && getButtonStyle(props.$buttonStyle)}
63+
export const Button100 = styled(Button)<{
64+
$buttonStyle?: ButtonStyleType;
65+
$disabledStyle?: DisabledButtonStyleType;
66+
}>`
67+
${(props) => props.$buttonStyle && getButtonStyle(props.$buttonStyle, props.$disabledStyle)}
5768
width: 100%;
5869
height: auto;
5970
display: inline-flex;
@@ -73,13 +84,15 @@ export const ButtonCompWrapper = styled.div<{ $disabled: boolean }>`
7384
7485
// The button component is disabled but can respond to drag & select events
7586
${(props) =>
76-
props.$disabled &&
77-
`
78-
cursor: not-allowed;
79-
button:disabled {
80-
pointer-events: none;
81-
}
82-
`};
87+
props.$disabled
88+
? `
89+
cursor: not-allowed;
90+
button:disabled {
91+
pointer-events: none;
92+
}
93+
`
94+
: ""
95+
}
8396
`;
8497

8598
/**
@@ -103,6 +116,10 @@ function fixOldData(oldData: any) {
103116
const ButtonTmpStyleControl = styleControl(ButtonStyle, 'style');
104117
export const ButtonStyleControl = migrateOldData(ButtonTmpStyleControl, fixOldData);
105118

119+
// Create disabled style control
120+
const DisabledButtonTmpStyleControl = styleControl(DisabledButtonStyle, 'disabledStyle');
121+
export const DisabledButtonStyleControl = migrateOldData(DisabledButtonTmpStyleControl, fixOldData);
122+
106123
export const buttonRefMethods = refMethods<HTMLElement>([
107124
focusWithOptions,
108125
blurMethod,

0 commit comments

Comments
 (0)