Skip to content

Commit

Permalink
Merge pull request #62 from Strexas/JTE/PKFE-37
Browse files Browse the repository at this point in the history
JTE/PKFE-37
  • Loading branch information
justinnas authored Sep 21, 2024
2 parents 7452996 + 35b02ce commit dd78258
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { FileTreeItemContextMenuStyledDialog } from '@/features/editor/components/fileTreeView/fileTreeItem';
import { useStatusContext } from '@/hooks';
import { Close as CloseIcon } from '@mui/icons-material';
import {
Box,
Button,
DialogActions,
DialogContent,
DialogTitle,
Grid,
IconButton,
Typography,
useTheme,
} from '@mui/material';
import { useCallback } from 'react';

interface EditorConfirmLeaveDialogProps {
onConfirm: () => void;
isOpen: boolean;
onClose: () => void;
}

export const EditorConfirmLeave: React.FC<EditorConfirmLeaveDialogProps> = ({ onConfirm, isOpen, onClose }) => {
const { unsavedStateUpdate } = useStatusContext();
const Theme = useTheme();

const handleConfirm = useCallback(() => {
unsavedStateUpdate(false);
onConfirm();
onClose();
}, [onConfirm, onClose, unsavedStateUpdate]);

return (
<FileTreeItemContextMenuStyledDialog open={isOpen} onClose={onClose}>
<Grid container spacing={2} justifyContent='center' alignItems='center'>
<Grid item xs={8}>
<DialogTitle sx={{ color: Theme.palette.primary.main, pl: '1.5rem', pt: '1.5rem', fontWeight: '700' }}>
Unsaved changes
</DialogTitle>
</Grid>
<Grid item xs={4}>
<Box display='flex' justifyContent='flex-end'>
<IconButton
aria-label='close'
onClick={onClose}
sx={{
color: Theme.palette.primary.main,
mt: '0.5rem',
mr: '1.5rem',
}}
>
<CloseIcon />
</IconButton>
</Box>
</Grid>
</Grid>
<DialogContent sx={{ py: 0 }}>
<Typography sx={{ color: Theme.palette.text.primary, fontSize: '1rem', mb: '1rem' }}>
You have unsaved changes. If you continue, your <b>changes will be lost</b>. <br />
Do you wish to continue?
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>
<Typography sx={{ fontSize: '1rem', color: Theme.palette.text.secondary }}>Cancel</Typography>
</Button>
<Button
onClick={handleConfirm}
variant='outlined'
sx={{
borderColor: Theme.palette.primary.main,
':hover': { borderColor: Theme.palette.primary.main, bgcolor: Theme.palette.secondary.main },
'& .MuiTouchRipple-root': {
color: Theme.palette.primary.main,
},
}}
>
Continue
</Button>
</DialogActions>
</FileTreeItemContextMenuStyledDialog>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useStatusContext } from '@/hooks';
import { socket } from '@/lib';
import { Events } from '@/types';
import { Done as DoneIcon, Error as ErrorIcon } from '@mui/icons-material';
import { Box, Button, CircularProgress, useTheme } from '@mui/material';
import { alpha, Box, Button, CircularProgress, Typography, useTheme } from '@mui/material';
import {
GridToolbarColumnsButton,
GridToolbarContainer,
Expand Down Expand Up @@ -52,7 +52,7 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({ handleSave }) => {
const [saveStatus, setSaveStatus] = useState(true);

const Theme = useTheme();
const { blocked } = useStatusContext();
const { blocked, unsavedStateUpdate, unsaved } = useStatusContext();

useEffect(() => {
const handleWorkspaceFileSaveFeedback = (data: { status: 'success' | 'error' }) => {
Expand All @@ -73,10 +73,17 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({ handleSave }) => {
<GridToolbarDensitySelector />
{/* <GridToolbarExport /> */}
<Box sx={{ flexGrow: 1 }} />
{unsaved && (
<Typography sx={{ fontSize: '0.9rem', color: alpha(Theme.palette.text.primary, 0.5), pr: '0.5rem' }}>
Changes not saved
</Typography>
)}

<Button
onClick={() => {
setIsSaving(true);
handleSave();
unsavedStateUpdate(false);
}}
disabled={blocked}
startIcon={
Expand Down
125 changes: 90 additions & 35 deletions app/front-end/src/features/editor/components/editorView/editorView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { EditorColumnMenu, EditorHeader, EditorToolbar } from '@/features/editor/components/editorView';
import {
EditorColumnMenu,
EditorConfirmLeave,
EditorHeader,
EditorToolbar,
} from '@/features/editor/components/editorView';
import { useWorkspaceContext } from '@/features/editor/hooks';
import { FileContentAggregationActions, FileDataRequestDTO, FileDataResponseDTO } from '@/features/editor/types';
import { useSessionContext, useStatusContext } from '@/hooks';
Expand Down Expand Up @@ -57,7 +62,7 @@ export const EditorView: React.FC = () => {

const { connected } = useSessionContext();
const { file, fileContent, filePagination, fileStateUpdate } = useWorkspaceContext();
const { blocked, blockedStateUpdate } = useStatusContext();
const { blocked, blockedStateUpdate, unsaved, unsavedStateUpdate } = useStatusContext();
const ref = useGridApiRef();

const handleSave = async () => {
Expand Down Expand Up @@ -125,6 +130,10 @@ export const EditorView: React.FC = () => {
}
};

const onCellEditStart = () => {
unsavedStateUpdate(true);
};

const getWorkspaceFile = useCallback(async () => {
if (!file.id) {
setFileContentResponse({ totalRows: 0, header: [], rows: [], page: 0 });
Expand Down Expand Up @@ -196,39 +205,85 @@ export const EditorView: React.FC = () => {
);
}, [fileContentResponse, fileContent.aggregations]);

// Browser tab close/refresh warning if there are unsaved changes effect
useEffect(() => {
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
if (unsaved) event.preventDefault();
};

window.addEventListener('beforeunload', handleBeforeUnload);

return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [unsaved]);

const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false);
const [pendingModel, setPendingModel] = useState<{ page: number; pageSize: number } | null>(null);

const [paginationModel, setPaginationModel] = useState({
page: filePagination.page,
pageSize: filePagination.rowsPerPage,
});

const handleConfirm = () => {
if (pendingModel) {
fileStateUpdate(undefined, undefined, {
page: pendingModel.page,
rowsPerPage: pendingModel.pageSize,
totalRows: filePagination.totalRows,
});
setPaginationModel(pendingModel);
setPendingModel(null);
}
setIsConfirmDialogOpen(false);
};

const handleCancel = () => {
setPendingModel(null);
setIsConfirmDialogOpen(false);
};

return (
<DataGrid
sx={{ height: '100%', border: 'none' }}
loading={blocked || isLoading}
rows={fileContent.rows}
columns={fileContent.columns}
pagination
paginationMode='server'
rowCount={filePagination.totalRows}
disableColumnSorting
initialState={{
pagination: {
paginationModel: { pageSize: filePagination.rowsPerPage, page: filePagination.page },
},
}}
disableColumnMenu={blocked}
pageSizeOptions={[25, 50, 100]}
onPaginationModelChange={(model) => {
fileStateUpdate(undefined, undefined, {
page: model.page,
rowsPerPage: model.pageSize,
totalRows: filePagination.totalRows,
});
}}
slots={{
toolbar: (props) => <EditorToolbar {...props} disabled={blocked || !file.id} handleSave={handleSave} />,
columnMenu: (props) => <EditorColumnMenu {...props} disabled={blocked} handleAggregation={handleAggregation} />,
pagination: (props) => <GridPagination disabled={blocked} {...props} />,
}}
slotProps={{
toolbar: {},
}}
apiRef={ref}
/>
<>
<DataGrid
sx={{ height: '100%', border: 'none' }}
loading={blocked || isLoading}
rows={fileContent.rows}
columns={fileContent.columns}
pagination
paginationMode='server'
rowCount={filePagination.totalRows}
disableColumnSorting
pageSizeOptions={[25, 50, 100]}
paginationModel={paginationModel}
onPaginationModelChange={(model) => {
if (unsaved) {
setPendingModel(model);
setIsConfirmDialogOpen(true);
} else {
fileStateUpdate(undefined, undefined, {
page: model.page,
rowsPerPage: model.pageSize,
totalRows: filePagination.totalRows,
});
setPaginationModel(model);
}
}}
slots={{
toolbar: (props) => <EditorToolbar {...props} disabled={blocked || !file.id} handleSave={handleSave} />,
columnMenu: (props) => (
<EditorColumnMenu {...props} disabled={blocked} handleAggregation={handleAggregation} />
),
pagination: (props) => <GridPagination disabled={blocked} {...props} />,
}}
slotProps={{
toolbar: {},
}}
apiRef={ref}
onCellEditStart={onCellEditStart}
/>
<EditorConfirmLeave isOpen={isConfirmDialogOpen} onClose={handleCancel} onConfirm={handleConfirm} />
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { EditorColumnMenu } from './editorColumnMenu';
export { EditorColumnMenuAggregationItem } from './editorColumnMenuAggregationItem';
export type { EditorColumnMenuAggregationItemProps } from './editorColumnMenuAggregationItem';
export { EditorConfirmLeave } from './editorConfirmLeave';
export { EditorHeader } from './editorHeader';
export type { EditorHeaderProps } from './editorHeader';
export { EditorToolbar } from './editorToolbar';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EditorConfirmLeave } from '@/features/editor/components/editorView';
import { FileTreeItemContextMenu, FileTreeItemLabel } from '@/features/editor/components/fileTreeView/fileTreeItem';
import { useWorkspaceContext } from '@/features/editor/hooks';
import { FileTypes } from '@/features/editor/types';
Expand Down Expand Up @@ -136,7 +137,7 @@ export const FileTreeItem = React.forwardRef(function CustomTreeItem(
icon = getIconFromFileType(item.fileType);
}

const { fileStateUpdate, filesHistoryStateUpdate } = useWorkspaceContext();
const { fileStateUpdate, filesHistoryStateUpdate, file } = useWorkspaceContext();
const { blocked } = useStatusContext();
const [contextMenu, setContextMenu] = useState<(EventTarget & HTMLDivElement) | null>(null);
const [contextMenuPosition, setContextMenuPosition] = useState<{ top: number; left: number }>({
Expand Down Expand Up @@ -166,6 +167,13 @@ export const FileTreeItem = React.forwardRef(function CustomTreeItem(
setContextMenuPosition({ top: 0, left: 0 });
};

const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false);
const { unsaved } = useStatusContext();
const handleConfirm = () => {
handleClick(item.id, item.label, item.fileType);
setIsConfirmDialogOpen(false);
};

return (
<TreeItem2Provider itemId={itemId}>
<StyledFileTreeItemRoot {...getRootProps(other)}>
Expand All @@ -174,7 +182,11 @@ export const FileTreeItem = React.forwardRef(function CustomTreeItem(
onClick: (event) => {
if (!blocked) {
if (getContentProps().onClick) getContentProps().onClick(event);
handleClick(item.id, item.label, item.fileType);
if (unsaved) {
if (item.fileType !== FileTypes.FOLDER && item.id !== file.id) setIsConfirmDialogOpen(true);
} else {
handleClick(item.id, item.label, item.fileType);
}
}
},

Expand Down Expand Up @@ -204,6 +216,11 @@ export const FileTreeItem = React.forwardRef(function CustomTreeItem(
onClose={handleCloseContextMenu}
/>
{children && <TransitionComponent {...getGroupTransitionProps()} />}
<EditorConfirmLeave
isOpen={isConfirmDialogOpen}
onClose={() => setIsConfirmDialogOpen(false)}
onConfirm={handleConfirm}
/>
</StyledFileTreeItemRoot>
</TreeItem2Provider>
);
Expand Down
Loading

0 comments on commit dd78258

Please sign in to comment.