오늘의 목표
- element 별 클릭 이벤트 구현으로 원하는 미디어를 배치할 수 있도록 구현 - 이미지, 링크
수행 내역
- 이미지, 링크 영역에도 모달에서 내용을 작성하고, 수정할 수 있도록 기능 구현
import { Link } from 'react-router-dom';
import { useLogBook } from '../../context/LogBookContext';
const GridItemTop = ({ item, type, handleClickDelete }) => {
const { setElements } = useLogBook();
return (
<div className='grid-item-top'>
<div className='grid-item-text'>
<img src={`/img/icon-${type}.png`} alt='' />
</div>
<button
className='grid-item-delete'
onClick={(e) => {
e.stopPropagation();
setElements((prev) => prev.filter((element) => element.i !== item.i));
handleClickDelete(item.i);
}}
>
{/* <svg xmlns='<http://www.w3.org/2000/svg>' viewBox='0 0 640 640'>
<path d='M262.2 48C248.9 48 236.9 56.3 232.2 68.8L216 112L120 112C106.7 112 96 122.7 96 136C96 149.3 106.7 160 120 160L520 160C533.3 160 544 149.3 544 136C544 122.7 533.3 112 520 112L424 112L407.8 68.8C403.1 56.3 391.2 48 377.8 48L262.2 48zM128 208L128 512C128 547.3 156.7 576 192 576L448 576C483.3 576 512 547.3 512 512L512 208L464 208L464 512C464 520.8 456.8 528 448 528L192 528C183.2 528 176 520.8 176 512L176 208L128 208zM288 280C288 266.7 277.3 256 264 256C250.7 256 240 266.7 240 280L240 456C240 469.3 250.7 480 264 480C277.3 480 288 469.3 288 456L288 280zM400 280C400 266.7 389.3 256 376 256C362.7 256 352 266.7 352 280L352 456C352 469.3 362.7 480 376 480C389.3 480 400 469.3 400 456L400 280z' />
</svg> */}
</button>
</div>
);
};
const GridContent = ({ type, content }) => {
switch (type) {
case 'image':
return content ? (
<img src={`https://${content.replace(/https?:\\/\\//, '')}`} alt='' />
) : (
<p className='default-text'>사진을 첨부하기 위해 클릭</p>
);
case 'link':
return content ? (
<Link to={`https://${content.replace(/https?:\\/\\//, '')}`}>{content}</Link>
) : (
<p className='default-text'>내용을 입력하기 위해 클릭</p>
);
default:
return content ? content : <p className='default-text'>내용을 입력하기 위해 클릭</p>;
}
};
const BlogLayoutItem = ({ item, handleClickDelete, enableModal }) => {
const { setClickedItem, elements } = useLogBook();
const itemType = item.i.split('-')[0];
const itemContent = elements.find((element) => element.i === item.i)?.content;
return (
<div
className={item.i}
onClick={() => {
setClickedItem(item);
enableModal();
}}
>
<GridItemTop item={item} type={itemType} handleClickDelete={handleClickDelete} />
<div className={`grid-${itemType}-content`}>
<GridContent type={itemType} content={itemContent} />
</div>
</div>
);
};
export default BlogLayoutItem;
- 타이틀 영역의 높이를 더 작은 폭으로 사용할 수 있도록 grid layout 수정
- height 값을 80px로 줄이고, 다른 element 들도 각자 특성에 맞는 w, h 값으로 지정
import { useState } from 'react';
import ReactGridLayout from 'react-grid-layout';
import { useLogBook } from '../../context/LogBookContext';
import BlogLayoutItem from './BlogLayoutItem';
const BlogGridLayout = ({ enableModal }) => {
const [layout, setLayout] = useState([]);
const [newItemCounter, setNewItemCounter] = useState(0);
const { draggingItem, setElements } = useLogBook();
const handleClickDelete = (i) => {
setLayout((prev) => prev.filter((item) => item.i !== i));
};
const onLayoutChange = (newLayout) => {
setLayout(newLayout);
};
const onDrop = (currentLayout, droppedItemProps, e) => {
if (!draggingItem) return;
const { x, y } = droppedItemProps;
const newId = `${draggingItem.className}-${newItemCounter}`;
const w = draggingItem.w;
const h = draggingItem.h;
const isOverlap = layout.some(
(item) => item.x < x + w && item.x + item.w > x && item.y < y + h && item.y + item.h > y
);
const newItem = {
i: newId,
x: x,
y: y,
w: w,
h: h,
};
let finalLayout;
if (isOverlap) {
const newLayout = layout.map((item) => {
// 드롭된 아이템의 x, y와 겹치는 아이템들을 아래로 이동
if (item.x < x + w && item.x + item.w > x && item.y >= y) {
return { ...item, y: item.y + h };
}
return item;
});
finalLayout = [...newLayout, newItem];
} else {
finalLayout = [...layout, newItem];
}
setLayout(finalLayout);
setElements((prev) => [...prev, { i: newId, content: null }]);
setNewItemCounter((prevCounter) => prevCounter + 1);
};
const renderGridItems = () => {
return layout.map((item) => {
if (item.i !== '__dropping-elem__') {
return (
<div key={item.i}>
<BlogLayoutItem
item={item}
handleClickDelete={handleClickDelete}
enableModal={enableModal}
/>
</div>
);
}
});
};
return (
<div className='blog-area'>
<ReactGridLayout
layout={layout}
cols={5}
rowHeight={80}
width={900}
onDrop={onDrop}
isDroppable={true}
isDraggable={false}
droppingItem={{
i: '__dropping-elem__',
w: draggingItem ? draggingItem.w : 1,
h: draggingItem ? draggingItem.h : 1,
}}
compactType={'vertical'}
onLayoutChange={onLayoutChange}
>
{renderGridItems()}
</ReactGridLayout>
</div>
);
};
export default BlogGridLayout;
문제 및 이슈
- 모달 영역에 transition을 적용하기 위해 isModalOpen 을 이용한 렌더링에서 className을 이용한 visibility 변경으로 수정하였으나, context에서 clickedItem을 불러오는 로직에서 렌더링 오류 발생.
- 렌더링 오류를 수정하기 위해 null 값 예외처리 하였으나, 같은 타입의 element를 여러 개 추가하였을 때 content 값을 불러오는 상황에서 버그 발생
참고자료
지원요청
내일 계획
- blog page layout을 json 파일로 데이터화하고, 유저 정보에 따른 layout을 불러와 수정, 조회 할 수 있도록 구현