1
1
import { default as Dropdown } from "antd/es/dropdown" ;
2
2
import { default as Skeleton } from "antd/es/skeleton" ;
3
3
import { default as Radio , RadioChangeEvent } from "antd/es/radio" ;
4
+ import { default as Statistic } from "antd/es/statistic" ;
5
+ import { default as Flex } from "antd/es/flex" ;
6
+ import { default as Popover } from "antd/es/popover" ;
7
+ import { default as Typography } from "antd/es/typography" ;
4
8
import LayoutHeader from "components/layout/Header" ;
5
9
import { SHARE_TITLE } from "constants/apiConstants" ;
6
10
import { AppTypeEnum } from "constants/applicationConstants" ;
@@ -20,12 +24,13 @@ import {
20
24
Middle ,
21
25
ModuleIcon ,
22
26
PackUpIcon ,
27
+ RefreshIcon ,
23
28
Right ,
24
29
TacoButton ,
25
30
} from "lowcoder-design" ;
26
31
import { trans } from "i18n" ;
27
32
import dayjs from "dayjs" ;
28
- import { useContext , useEffect , useMemo , useState } from "react" ;
33
+ import { useContext , useEffect , useMemo , useRef , useState } from "react" ;
29
34
import { useDispatch , useSelector } from "react-redux" ;
30
35
import {
31
36
publishApplication ,
@@ -58,7 +63,10 @@ import { LockOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
58
63
import Avatar from 'antd/es/avatar' ;
59
64
import UserApi from "@lowcoder-ee/api/userApi" ;
60
65
import { validateResponse } from "@lowcoder-ee/api/apiUtils" ;
66
+ import ProfileImage from "./profileImage" ;
61
67
68
+ const { Countdown } = Statistic ;
69
+ const { Text } = Typography ;
62
70
63
71
const StyledLink = styled . a `
64
72
display: flex;
@@ -186,6 +194,10 @@ const GrayBtn = styled(TacoButton)`
186
194
color: #ffffff;
187
195
border: none;
188
196
}
197
+
198
+ &[disabled] {
199
+ cursor: not-allowed;
200
+ }
189
201
}
190
202
` ;
191
203
@@ -282,6 +294,23 @@ const WarningIcon = styled(ExclamationCircleOutlined)`
282
294
color: #ff4d4f; /* Red color for the icon */
283
295
` ;
284
296
297
+ const StyledCountdown = styled ( Countdown ) `
298
+ .ant-statistic-content {
299
+ color: #ff4d4f;
300
+ margin-top: 2px;
301
+ text-align: center;
302
+ }
303
+ ` ;
304
+
305
+ const StyledRefreshIcon = styled ( RefreshIcon ) `
306
+ width: 16px !important;
307
+ height: 16px !important;
308
+ margin-right: -3px !important;
309
+ > g > g {
310
+ stroke: white;
311
+ }
312
+ ` ;
313
+
285
314
// Add the lock icon logic for disabled options
286
315
const DropdownMenuStyled = styled ( DropdownMenu ) `
287
316
.ant-dropdown-menu-item:hover {
@@ -314,6 +343,8 @@ function HeaderProfile(props: { user: User }) {
314
343
) ;
315
344
}
316
345
346
+ const setCountdown = ( ) => dayjs ( ) . add ( 3 , 'minutes' ) . toISOString ( ) ;
347
+
317
348
export type PanelStatus = { left : boolean ; bottom : boolean ; right : boolean } ;
318
349
export type TogglePanel = ( panel ?: keyof PanelStatus ) => void ;
319
350
@@ -332,7 +363,7 @@ type HeaderProps = {
332
363
// header in editor page
333
364
export default function Header ( props : HeaderProps ) {
334
365
const editorState = useContext ( EditorContext ) ;
335
- const { blockEditing } = useContext ( ExternalEditorContext ) ;
366
+ const { blockEditing, fetchApplication } = useContext ( ExternalEditorContext ) ;
336
367
const { togglePanel } = props ;
337
368
const { toggleEditorModeStatus } = props ;
338
369
const { left, bottom, right } = props . panelStatus ;
@@ -347,6 +378,8 @@ export default function Header(props: HeaderProps) {
347
378
const [ editing , setEditing ] = useState ( false ) ;
348
379
const [ permissionDialogVisible , setPermissionDialogVisible ] = useState ( false ) ;
349
380
const [ editingUser , setEditingUser ] = useState < CurrentUser > ( ) ;
381
+ const [ enableCheckEditingStatus , setEnableCheckEditingStatus ] = useState < boolean > ( false ) ;
382
+ const editingCountdown = useRef ( setCountdown ( ) ) ;
350
383
351
384
const isModule = appType === AppTypeEnum . Module ;
352
385
@@ -434,8 +467,6 @@ export default function Header(props: HeaderProps) {
434
467
435
468
const headerMiddle = (
436
469
< >
437
- < >
438
- </ >
439
470
< Radio . Group
440
471
onChange = { onEditorStateValueChange }
441
472
value = { props . editorModeStatus }
@@ -503,20 +534,54 @@ export default function Header(props: HeaderProps) {
503
534
< >
504
535
{ /* Display a hint about who is editing the app */ }
505
536
{ blockEditing && (
506
- < Tooltip
507
- title = "Changes will not be saved while another user is editing this app."
508
- color = "red"
509
- placement = "bottom"
537
+ < >
538
+ < Popover
539
+ style = { { width : 200 } }
540
+ content = { ( ) => {
541
+ return (
542
+ < Flex vertical gap = { 10 } align = "center" >
543
+ < Text >
544
+ Changes will not be saved while another < br /> user is editing this app.
545
+ </ Text >
546
+ < StyledCountdown
547
+ title = "Editing Availability"
548
+ value = { editingCountdown . current }
549
+ onFinish = { ( ) => {
550
+ setEnableCheckEditingStatus ( true )
551
+ } }
552
+ />
553
+ < Tooltip
554
+ title = "You will be able to check the editing status after the countdown."
555
+ placement = "bottom"
556
+ >
557
+ < TacoButton
558
+ style = { { width : '100%' } }
559
+ buttonType = "primary"
560
+ disabled = { blockEditing && ! enableCheckEditingStatus }
561
+ onClick = { ( ) => {
562
+ fetchApplication ?.( ) ;
563
+ setEnableCheckEditingStatus ( false ) ;
564
+ editingCountdown . current = setCountdown ( ) ;
565
+ } }
566
+ >
567
+ < StyledRefreshIcon />
568
+ < span > Check Editing Status</ span >
569
+ </ TacoButton >
570
+ </ Tooltip >
571
+ </ Flex >
572
+ )
573
+ } }
574
+ trigger = "hover"
510
575
>
511
576
< EditingNoticeWrapper >
512
- < Avatar size = "small" src = { user . avatarUrl } />
577
+ < ProfileImage source = { user . avatarUrl } userName = { user . username } side = { 24 } />
513
578
< EditingHintText >
514
- { /* {`${user.username} is currently editing this app.` } */ }
515
- { `${ editingUser ?. name || 'Someone' } is currently editing this app` }
579
+ { `${ editingUser ?. name || 'Someone' } is editing this app` }
516
580
</ EditingHintText >
517
581
< WarningIcon />
518
582
</ EditingNoticeWrapper >
519
- </ Tooltip >
583
+ </ Popover >
584
+ </ >
520
585
) }
521
586
522
587
{ applicationId && (
@@ -529,7 +594,7 @@ export default function Header(props: HeaderProps) {
529
594
/>
530
595
) }
531
596
{ canManageApp ( user , application ) && (
532
- < GrayBtn onClick = { ( ) => setPermissionDialogVisible ( true ) } >
597
+ < GrayBtn onClick = { ( ) => setPermissionDialogVisible ( true ) } disabled = { blockEditing } >
533
598
{ SHARE_TITLE }
534
599
</ GrayBtn >
535
600
) }
@@ -596,6 +661,7 @@ export default function Header(props: HeaderProps) {
596
661
applicationId ,
597
662
permissionDialogVisible ,
598
663
blockEditing , // Include the state in the dependency array
664
+ enableCheckEditingStatus ,
599
665
editingUser ?. name ,
600
666
] ) ;
601
667
0 commit comments