144 lines
5.1 KiB
TypeScript
144 lines
5.1 KiB
TypeScript
import { Add, Close, Restore } from '@mui/icons-material';
|
|
import { Button, IconButton, Tooltip } from '@mui/material';
|
|
import clsx from 'clsx';
|
|
import cloneDeep from 'lodash/cloneDeep';
|
|
import get from 'lodash/get';
|
|
import { useTranslation } from 'next-i18next';
|
|
import { DragDropContext, Draggable, DraggableLocation, Droppable, DropResult } from 'react-beautiful-dnd';
|
|
|
|
import Heading from '@/components/shared/Heading';
|
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
|
import { addPage, deletePage, setResumeState } from '@/store/resume/resumeSlice';
|
|
|
|
import styles from './Layout.module.scss';
|
|
|
|
const getIndices = (location: DraggableLocation) => ({
|
|
page: +location.droppableId.split('.')[0],
|
|
column: +location.droppableId.split('.')[1],
|
|
section: +location.index,
|
|
});
|
|
|
|
const Layout = () => {
|
|
const { t } = useTranslation();
|
|
|
|
const dispatch = useAppDispatch();
|
|
|
|
const layout = useAppSelector((state) => state.resume.metadata.layout);
|
|
const resumeSections = useAppSelector((state) => state.resume.sections);
|
|
|
|
const onDragEnd = (dropResult: DropResult) => {
|
|
const { source: srcLoc, destination: destLoc } = dropResult;
|
|
|
|
if (!destLoc) return;
|
|
|
|
const newLayout = cloneDeep(layout);
|
|
|
|
const srcIndex = getIndices(srcLoc);
|
|
const destIndex = getIndices(destLoc);
|
|
const section = layout[srcIndex.page][srcIndex.column][srcIndex.section];
|
|
|
|
// Remove item at source
|
|
newLayout[srcIndex.page][srcIndex.column].splice(srcIndex.section, 1);
|
|
|
|
// Insert item at destination
|
|
newLayout[destIndex.page][destIndex.column].splice(destIndex.section, 0, section);
|
|
|
|
dispatch(setResumeState({ path: 'metadata.layout', value: newLayout }));
|
|
};
|
|
|
|
const handleAddPage = () => dispatch(addPage());
|
|
|
|
const handleDeletePage = (page: number) => dispatch(deletePage({ page }));
|
|
|
|
const handleResetLayout = () => {
|
|
for (let i = layout.length - 1; i > 0; i--) {
|
|
handleDeletePage(i);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Heading
|
|
path="metadata.layout"
|
|
name={t('builder.rightSidebar.sections.layout.heading')}
|
|
action={
|
|
<Tooltip title={t<string>('builder.rightSidebar.sections.layout.tooltip.reset-layout')}>
|
|
<IconButton onClick={handleResetLayout}>
|
|
<Restore />
|
|
</IconButton>
|
|
</Tooltip>
|
|
}
|
|
/>
|
|
|
|
<DragDropContext onDragEnd={onDragEnd}>
|
|
{/* Pages */}
|
|
{layout.map((columns, pageIndex) => (
|
|
<div key={pageIndex} className={styles.page}>
|
|
<div className="flex items-center justify-between pr-3">
|
|
<p className={styles.heading}>
|
|
{t('builder.common.glossary.page')} {pageIndex + 1}
|
|
</p>
|
|
|
|
<div className={clsx(styles.delete, { hidden: pageIndex === 0 })}>
|
|
<Tooltip
|
|
title={t<string>('builder.common.actions.delete', { token: t('builder.common.glossary.page') })}
|
|
>
|
|
<IconButton size="small" onClick={() => handleDeletePage(pageIndex)}>
|
|
<Close fontSize="small" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.container}>
|
|
{/* Sections */}
|
|
{columns.map((sections, columnIndex) => {
|
|
const index = `${pageIndex}.${columnIndex}`;
|
|
|
|
return (
|
|
<Droppable key={index} droppableId={index}>
|
|
{(provided) => (
|
|
<div ref={provided.innerRef} className={styles.column} {...provided.droppableProps}>
|
|
<p className={styles.heading}>{columnIndex ? 'Sidebar' : 'Main'}</p>
|
|
|
|
<div className={styles.base} />
|
|
|
|
{/* Sections */}
|
|
{sections.map((sectionId, sectionIndex) => (
|
|
<Draggable key={sectionId} draggableId={sectionId} index={sectionIndex}>
|
|
{(provided) => (
|
|
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
|
<div
|
|
className={clsx(styles.section, {
|
|
[styles.disabled]: !get(resumeSections, `${sectionId}.visible`, true),
|
|
})}
|
|
>
|
|
{get(resumeSections, `${sectionId}.name`)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Draggable>
|
|
))}
|
|
|
|
{provided.placeholder}
|
|
</div>
|
|
)}
|
|
</Droppable>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
<div className="flex items-center justify-end">
|
|
<Button variant="outlined" startIcon={<Add />} onClick={handleAddPage}>
|
|
{t('builder.common.actions.add', { token: t('builder.common.glossary.page') })}
|
|
</Button>
|
|
</div>
|
|
</DragDropContext>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Layout;
|