Skip to content

Commit 737c25c

Browse files
logic to block editing if someone else is editing
1 parent 8470a42 commit 737c25c

File tree

7 files changed

+75
-19
lines changed

7 files changed

+75
-19
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export interface ApplicationMeta {
9292
isLocalMarketplace?: boolean;
9393
applicationStatus: "NORMAL" | "RECYCLED" | "DELETED";
9494
editingUserId: string | null;
95+
lastEditedAt: number;
9596
}
9697

9798
export interface FolderMeta {

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ type HeaderProps = {
332332
// header in editor page
333333
export default function Header(props: HeaderProps) {
334334
const editorState = useContext(EditorContext);
335+
const { blockEditing } = useContext(ExternalEditorContext);
335336
const { togglePanel } = props;
336337
const { toggleEditorModeStatus } = props;
337338
const { left, bottom, right } = props.panelStatus;
@@ -348,23 +349,17 @@ export default function Header(props: HeaderProps) {
348349
const [editingUser, setEditingUser] = useState<CurrentUser>();
349350

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

356353
useEffect(() => {
357354
if(blockEditing && application && Boolean(application?.editingUserId)) {
358355
UserApi.getUserDetail(application.editingUserId!)
359356
.then(resp => {
360357
if (validateResponse(resp)) {
361-
console.log(resp.data.data);
362358
setEditingUser(resp.data.data);
363359
}
364360
});
365361
}
366362
}, [blockEditing]);
367-
console.log(user.id, application?.editingUserId);
368363

369364
const editorModeOptions = [
370365
{

client/packages/lowcoder/src/pages/editor/AppEditor.tsx

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { ALL_APPLICATIONS_URL } from "@lowcoder-ee/constants/routesURL";
3232
import history from "util/history";
3333
import Flex from "antd/es/flex";
3434
import React from "react";
35+
import dayjs from "dayjs";
36+
import { currentApplication } from "@lowcoder-ee/redux/selectors/applicationSelector";
3537

3638
const AppSnapshot = lazy(() => {
3739
return import("pages/editor/appSnapshot")
@@ -52,13 +54,16 @@ const AppEditor = React.memo(() => {
5254
const paramViewMode = params.viewMode || window.location.pathname.split("/")[3];
5355
const viewMode = (paramViewMode === "view" || paramViewMode === "admin") ? "published" : paramViewMode === "view_marketplace" ? "view_marketplace" : "editing";
5456
const currentUser = useSelector(getUser);
57+
const application = useSelector(currentApplication);
5558
const dispatch = useDispatch();
5659
const fetchOrgGroupsFinished = useSelector(getFetchOrgGroupsFinished);
5760
const isCommonSettingsFetching = useSelector(getIsCommonSettingFetching);
5861
const orgId = currentUser.currentOrgId;
5962
const firstRendered = useRef(false);
63+
const fetchInterval = useRef<number>(0);
6064
const [isDataSourcePluginRegistered, setIsDataSourcePluginRegistered] = useState(false);
6165
const [appError, setAppError] = useState('');
66+
const [blockEditing, setBlockEditing] = useState<boolean>(false);
6267

6368
setGlobalSettings({ applicationId, isViewMode: paramViewMode === "view" });
6469

@@ -83,7 +88,22 @@ const AppEditor = React.memo(() => {
8388
});
8489

8590
const readOnly = isUserViewMode;
86-
const compInstance = useRootCompInstance(appInfo, readOnly, isDataSourcePluginRegistered);
91+
const compInstance = useRootCompInstance(
92+
appInfo,
93+
readOnly,
94+
isDataSourcePluginRegistered,
95+
blockEditing,
96+
);
97+
98+
useEffect(() => {
99+
if (currentUser && application) {
100+
const lastEditedAt = dayjs(application?.lastEditedAt);
101+
const lastEditedDiff = dayjs().diff(lastEditedAt, 'minutes');
102+
const shouldBlockEditing = currentUser.id !== application?.createBy && lastEditedDiff < 5;
103+
setBlockEditing(shouldBlockEditing);
104+
console.log('blockEditing', shouldBlockEditing, {user_id: currentUser.id, editingUserId: application.createBy, lastEditedDiff});
105+
}
106+
}, [application, currentUser]);
87107

88108
// fetch dataSource and plugin
89109
useEffect(() => {
@@ -100,7 +120,7 @@ const AppEditor = React.memo(() => {
100120
dispatch(fetchQueryLibraryDropdown());
101121
}
102122
}, [dispatch, applicationId, paramViewMode]);
103-
123+
104124
const fetchJSDataSourceByApp = () => {
105125
DatasourceApi.fetchJsDatasourceByApp(applicationId).then((res) => {
106126
res.data.data.forEach((i) => {
@@ -117,7 +137,7 @@ const AppEditor = React.memo(() => {
117137
}
118138
}, [dispatch, fetchOrgGroupsFinished, orgId]);
119139

120-
useEffect(() => {
140+
const fetchApplication = useCallback(() => {
121141
dispatch(
122142
fetchApplicationInfo({
123143
type: viewMode,
@@ -142,6 +162,20 @@ const AppEditor = React.memo(() => {
142162
);
143163
}, [viewMode, applicationId, dispatch]);
144164

165+
useEffect(() => {
166+
fetchApplication();
167+
}, [fetchApplication]);
168+
169+
// useEffect(() => {
170+
// if(!blockEditing) return clearInterval(fetchInterval.current);
171+
// if(blockEditing) {
172+
// fetchInterval.current = window.setInterval(() => {
173+
// fetchApplication();
174+
// }, 60000);
175+
// }
176+
// return () => clearInterval(fetchInterval.current);
177+
// }, [blockEditing, fetchApplication]);
178+
145179
const fallbackUI = useMemo(() => (
146180
<Flex align="center" justify="center" vertical style={{
147181
height: '300px',
@@ -182,6 +216,7 @@ const AppEditor = React.memo(() => {
182216
<AppEditorInternalView
183217
appInfo={appInfo}
184218
readOnly={readOnly}
219+
blockEditing={blockEditing}
185220
loading={
186221
!fetchOrgGroupsFinished || !isDataSourcePluginRegistered || isCommonSettingsFetching
187222
}

client/packages/lowcoder/src/pages/editor/appEditorInternal.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import { isEqual } from "lodash";
3535
function useSaveComp(
3636
applicationId: string,
3737
readOnly: boolean,
38-
rootCompInstance: RootCompInstanceType | undefined
38+
rootCompInstance: RootCompInstanceType | undefined,
39+
blockEditing?: boolean,
3940
) {
4041
const originalComp = rootCompInstance?.comp;
4142
// throttle comp change
@@ -45,7 +46,7 @@ function useSaveComp(
4546
const [prevJsonStr, setPrevJsonStr] = useState<string>();
4647

4748
useEffect(() => {
48-
if (readOnly) {
49+
if (readOnly || blockEditing) {
4950
return;
5051
}
5152
if (!comp || comp === prevComp) {
@@ -74,6 +75,7 @@ function useSaveComp(
7475

7576
interface AppEditorInternalViewProps {
7677
readOnly: boolean;
78+
blockEditing?: boolean;
7779
appInfo: AppSummaryInfo;
7880
loading: boolean;
7981
compInstance: RootCompInstanceType;
@@ -83,7 +85,7 @@ export const AppEditorInternalView = React.memo((props: AppEditorInternalViewPro
8385
const isUserViewMode = useUserViewMode();
8486
const extraExternalEditorState = useSelector(getExternalEditorState);
8587
const dispatch = useDispatch();
86-
const { readOnly, appInfo, compInstance } = props;
88+
const { readOnly, blockEditing, appInfo, compInstance } = props;
8789

8890
const [externalEditorState, setExternalEditorState] = useState<ExternalEditorContextState>({
8991
changeExternalState: (state: Partial<ExternalEditorContextState>) => {
@@ -92,8 +94,7 @@ export const AppEditorInternalView = React.memo((props: AppEditorInternalViewPro
9294
applicationId: appInfo.id,
9395
appType: AppTypeEnum.Application,
9496
});
95-
useSaveComp(appInfo.id, readOnly, compInstance);
96-
97+
9798
useEffect(() => {
9899
setExternalEditorState((s) => ({
99100
...s,
@@ -102,16 +103,25 @@ export const AppEditorInternalView = React.memo((props: AppEditorInternalViewPro
102103
appType: appInfo.appType,
103104
applicationId: appInfo.id,
104105
hideHeader: window.location.pathname.split("/")[3] === "admin",
106+
blockEditing,
105107
...extraExternalEditorState,
106108
}));
107-
}, [compInstance?.history, extraExternalEditorState, readOnly, appInfo.appType, appInfo.id]);
109+
}, [
110+
compInstance?.history,
111+
extraExternalEditorState,
112+
readOnly,
113+
appInfo.appType, appInfo.id,
114+
blockEditing,
115+
]);
108116

109117
useEffect(() => {
110118
message.config({
111119
top: isUserViewMode ? 0 : 48,
112120
});
113121
}, [isUserViewMode]);
114122

123+
useSaveComp(appInfo.id, readOnly, compInstance, blockEditing);
124+
115125
const loading =
116126
!compInstance || !compInstance.comp || !compInstance.comp.preloaded || props.loading;
117127

client/packages/lowcoder/src/pages/editor/useRootCompInstance.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { useCompInstance } from "comps/utils/useCompInstance";
77
import { MarkAppInitialized, perfMark } from "util/perfUtils";
88
import { QueryApi } from "api/queryApi";
99

10-
export function useRootCompInstance(appInfo: AppSummaryInfo, readOnly: boolean, isReady: boolean) {
10+
export function useRootCompInstance(
11+
appInfo: AppSummaryInfo,
12+
readOnly: boolean,
13+
isReady: boolean,
14+
blockEditing?: boolean,
15+
) {
1116
const appId = appInfo.id;
1217
const params = useMemo(() => {
1318
return {
@@ -28,7 +33,7 @@ export function useRootCompInstance(appInfo: AppSummaryInfo, readOnly: boolean,
2833
};
2934
}, [appId, appInfo.dsl, appInfo.moduleDsl, isReady, readOnly]);
3035
const [comp, container] = useCompInstance(params);
31-
const history = useAppHistory(container, readOnly, appId);
36+
const history = useAppHistory(container, readOnly, appId, blockEditing);
3237

3338
useUnmount(() => {
3439
comp?.clearPreload();

client/packages/lowcoder/src/util/context/ExternalEditorContext.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ export interface ExternalEditorContextState {
3838
*/
3939
showScriptsAndStyleModal?: boolean;
4040

41+
/**
42+
* whether to block editing if someone else is editing the app
43+
*/
44+
blockEditing?: boolean;
45+
4146
changeExternalState?: (state: Partial<ExternalEditorContextState>) => void;
4247
}
4348

client/packages/lowcoder/src/util/editoryHistory.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import { showCost } from "util/perfUtils";
1212

1313
type OperationType = AppSnapshotContext["operations"];
1414

15-
export function useAppHistory(compContainer: CompContainer, readOnly: boolean, appId: string) {
15+
export function useAppHistory(
16+
compContainer: CompContainer,
17+
readOnly: boolean,
18+
appId: string,
19+
blockEditing?: boolean,
20+
) {
1621
const reduxDispatch = useDispatch();
1722

1823
return useMemo(() => {
@@ -32,7 +37,7 @@ export function useAppHistory(compContainer: CompContainer, readOnly: boolean, a
3237
};
3338

3439
compContainer.addChangeListener((actions) => {
35-
if (readOnly || !actions) {
40+
if (readOnly || !actions || blockEditing) {
3641
return;
3742
}
3843
// maybe slow: comparing dsl by `toJson`

0 commit comments

Comments
 (0)