Skip to content

Commit 8470a42

Browse files
Block editing if someone else is editing the app
1 parent 4ac37a8 commit 8470a42

File tree

6 files changed

+68
-16
lines changed

6 files changed

+68
-16
lines changed

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
PublishApplicationPayload,
1010
RecycleApplicationPayload,
1111
RestoreApplicationPayload,
12+
SetAppEditingStatePayload,
1213
UpdateAppPermissionPayload,
1314
} from "redux/reduxActions/applicationActions";
1415
import { ApiResponse, GenericApiResponse } from "./apiResponses";
@@ -96,7 +97,7 @@ class ApplicationApi extends Api {
9697
static publicToAllURL = (applicationId: string) => `/applications/${applicationId}/public-to-all`;
9798
static publicToMarketplaceURL = (applicationId: string) => `/applications/${applicationId}/public-to-marketplace`;
9899
static getMarketplaceAppURL = (applicationId: string) => `/applications/${applicationId}/view_marketplace`;
99-
100+
static setAppEditingStateURL = (applicationId: string) => `/applications/editState/${applicationId}`;
100101

101102
static fetchHomeData(request: HomeDataPayload): AxiosPromise<HomeDataResponse> {
102103
return Api.get(ApplicationApi.fetchHomeDataURL, request);
@@ -232,6 +233,13 @@ class ApplicationApi extends Api {
232233
static getMarketplaceApp(appId: string) {
233234
return Api.get(ApplicationApi.getMarketplaceAppURL(appId));
234235
}
236+
237+
static setAppEditingState(request: SetAppEditingStatePayload): AxiosPromise<ApplicationResp> {
238+
const { applicationId, editingFinished } = request;
239+
return Api.put(ApplicationApi.setAppEditingStateURL(applicationId), {
240+
editingFinished,
241+
});
242+
}
235243
}
236244

237245
export default ApplicationApi;

client/packages/lowcoder/src/constants/applicationConstants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export interface ApplicationMeta {
9191
folder: false;
9292
isLocalMarketplace?: boolean;
9393
applicationStatus: "NORMAL" | "RECYCLED" | "DELETED";
94+
editingUserId: string | null;
9495
}
9596

9697
export interface FolderMeta {

client/packages/lowcoder/src/constants/reduxActionConstants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export const ReduxActionTypes = {
144144
FETCH_ALL_MODULES_SUCCESS: "FETCH_ALL_MODULES_SUCCESS",
145145
FETCH_ALL_MARKETPLACE_APPS: "FETCH_ALL_MARKETPLACE_APPS",
146146
FETCH_ALL_MARKETPLACE_APPS_SUCCESS: "FETCH_ALL_MARKETPLACE_APPS_SUCCESS",
147+
SET_APP_EDITING_STATE: "SET_APP_EDITING_STATE",
147148

148149
/* user profile */
149150
SET_USER_PROFILE_SETTING_MODAL_VISIBLE: "SET_USER_PROFILE_SETTING_MODAL_VISIBLE",

client/packages/lowcoder/src/pages/common/header.tsx

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
AUTH_LOGIN_URL,
1010
preview,
1111
} from "constants/routesURL";
12-
import { User } from "constants/userConstants";
12+
import { CurrentUser, User } from "constants/userConstants";
1313
import {
1414
CommonTextLabel,
1515
CustomModal,
@@ -56,6 +56,8 @@ import { EditorContext } from "../../comps/editorState";
5656
import Tooltip from "antd/es/tooltip";
5757
import { LockOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
5858
import Avatar from 'antd/es/avatar';
59+
import UserApi from "@lowcoder-ee/api/userApi";
60+
import { validateResponse } from "@lowcoder-ee/api/apiUtils";
5961

6062

6163
const StyledLink = styled.a`
@@ -343,12 +345,26 @@ export default function Header(props: HeaderProps) {
343345
const [editName, setEditName] = useState(false);
344346
const [editing, setEditing] = useState(false);
345347
const [permissionDialogVisible, setPermissionDialogVisible] = useState(false);
348+
const [editingUser, setEditingUser] = useState<CurrentUser>();
346349

347350
const isModule = appType === AppTypeEnum.Module;
351+
const blockEditing = useMemo(
352+
() => user.id !== application?.editingUserId,
353+
[application?.editingUserId]
354+
);
348355

349-
// Raheel: Todo - get concurrent editing state by API
350-
// maybe via editorState.getConcurrentAppEditingState(); as a new function?
351-
const [concurrentAppEditingState, setConcurrentAppEditingState] = useState(true);
356+
useEffect(() => {
357+
if(blockEditing && application && Boolean(application?.editingUserId)) {
358+
UserApi.getUserDetail(application.editingUserId!)
359+
.then(resp => {
360+
if (validateResponse(resp)) {
361+
console.log(resp.data.data);
362+
setEditingUser(resp.data.data);
363+
}
364+
});
365+
}
366+
}, [blockEditing]);
367+
console.log(user.id, application?.editingUserId);
352368

353369
const editorModeOptions = [
354370
{
@@ -491,7 +507,7 @@ export default function Header(props: HeaderProps) {
491507
) : (
492508
<>
493509
{/* Display a hint about who is editing the app */}
494-
{concurrentAppEditingState && (
510+
{blockEditing && (
495511
<Tooltip
496512
title="Changes will not be saved while another user is editing this app."
497513
color="red"
@@ -500,7 +516,8 @@ export default function Header(props: HeaderProps) {
500516
<EditingNoticeWrapper>
501517
<Avatar size="small" src={user.avatarUrl} />
502518
<EditingHintText>
503-
{`${user.username} is currently editing this app.`}
519+
{/* {`${user.username} is currently editing this app.`} */}
520+
{`${editingUser?.name || 'Someone'} is currently editing this app`}
504521
</EditingHintText>
505522
<WarningIcon />
506523
</EditingNoticeWrapper>
@@ -534,7 +551,7 @@ export default function Header(props: HeaderProps) {
534551
<DropdownMenuStyled
535552
style={{ minWidth: "110px", borderRadius: "4px" }}
536553
onClick={(e) => {
537-
if (concurrentAppEditingState) return; // Prevent clicks if the app is being edited by someone else
554+
if (blockEditing) return; // Prevent clicks if the app is being edited by someone else
538555
if (e.key === "deploy") {
539556
dispatch(publishApplication({ applicationId }));
540557
} else if (e.key === "snapshot") {
@@ -546,31 +563,31 @@ export default function Header(props: HeaderProps) {
546563
key: "deploy",
547564
label: (
548565
<div style={{ display: 'flex', alignItems: 'center' }}>
549-
{concurrentAppEditingState && <LockOutlined style={{ marginRight: '8px' }} />}
550-
<CommonTextLabel style= {{color: concurrentAppEditingState ? "#ccc" : "#222"}}>
566+
{blockEditing && <LockOutlined style={{ marginRight: '8px' }} />}
567+
<CommonTextLabel style= {{color: blockEditing ? "#ccc" : "#222"}}>
551568
{trans("header.deploy")}
552569
</CommonTextLabel>
553570
</div>
554571
),
555-
disabled: concurrentAppEditingState,
572+
disabled: blockEditing,
556573
},
557574
{
558575
key: "snapshot",
559576
label: (
560577
<div style={{ display: 'flex', alignItems: 'center' }}>
561-
{concurrentAppEditingState && <LockOutlined style={{ marginRight: '8px' }} />}
562-
<CommonTextLabel style= {{color: concurrentAppEditingState ? "#ccc" : "#222"}}>
578+
{blockEditing && <LockOutlined style={{ marginRight: '8px' }} />}
579+
<CommonTextLabel style= {{color: blockEditing ? "#ccc" : "#222"}}>
563580
{trans("header.snapshot")}
564581
</CommonTextLabel>
565582
</div>
566583
),
567-
disabled: concurrentAppEditingState,
584+
disabled: blockEditing,
568585
},
569586
]}
570587
/>
571588
)}
572589
>
573-
<PackUpBtn buttonType="primary" disabled={concurrentAppEditingState}>
590+
<PackUpBtn buttonType="primary" disabled={blockEditing}>
574591
<PackUpIcon />
575592
</PackUpBtn>
576593
</Dropdown>
@@ -583,7 +600,8 @@ export default function Header(props: HeaderProps) {
583600
showAppSnapshot,
584601
applicationId,
585602
permissionDialogVisible,
586-
concurrentAppEditingState, // Include the state in the dependency array
603+
blockEditing, // Include the state in the dependency array
604+
editingUser?.name,
587605
]);
588606

589607
return (

client/packages/lowcoder/src/redux/reduxActions/applicationActions.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ export type FetchAppInfoPayload = {
134134
onSuccess?: (info: AppSummaryInfo) => void;
135135
onError?: (error: string) => void;
136136
};
137+
138+
export type SetAppEditingStatePayload = {
139+
applicationId: string;
140+
editingFinished: boolean;
141+
};
142+
137143
export const fetchApplicationInfo = (payload: FetchAppInfoPayload) => ({
138144
type: ReduxActionTypes.FETCH_APPLICATION_DETAIL,
139145
payload: payload,
@@ -170,3 +176,8 @@ export const deleteAppPermission = (payload: DeleteAppPermissionPayload) => ({
170176
type: ReduxActionTypes.DELETE_APP_PERMISSION,
171177
payload: payload,
172178
});
179+
180+
export const setAppEditingState = (payload: SetAppEditingStatePayload) => ({
181+
type: ReduxActionTypes.SET_APP_EDITING_STATE,
182+
payload: payload,
183+
});

client/packages/lowcoder/src/redux/sagas/applicationSagas.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
PublishApplicationPayload,
2323
RecycleApplicationPayload,
2424
RestoreApplicationPayload,
25+
SetAppEditingStatePayload,
2526
UpdateApplicationPayload,
2627
UpdateAppMetaPayload,
2728
UpdateAppPermissionPayload,
@@ -391,6 +392,17 @@ function* fetchAllMarketplaceAppsSaga() {
391392
}
392393
}
393394

395+
function* setAppEditingStateSaga(action: ReduxAction<SetAppEditingStatePayload>) {
396+
try {
397+
yield call(
398+
ApplicationApi.setAppEditingState,
399+
action.payload
400+
);
401+
} catch (error) {
402+
log.debug("set app editing state: ", error);
403+
}
404+
}
405+
394406
export default function* applicationSagas() {
395407
yield all([
396408
takeLatest(ReduxActionTypes.FETCH_HOME_DATA, fetchHomeDataSaga),
@@ -416,5 +428,6 @@ export default function* applicationSagas() {
416428
ReduxActionTypes.FETCH_ALL_MARKETPLACE_APPS,
417429
fetchAllMarketplaceAppsSaga,
418430
),
431+
takeLatest(ReduxActionTypes.SET_APP_EDITING_STATE, setAppEditingStateSaga),
419432
]);
420433
}

0 commit comments

Comments
 (0)