Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/canvas/CanvasUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type CanvasUIProps = {
onImageDelete: () => void;
hasImage: boolean;
colors: string[];
onZoomIn: () => void;
onZoomOut: () => void;
};

export default function CanvasUI(props: CanvasUIProps) {
Expand Down
48 changes: 47 additions & 1 deletion src/components/canvas/CanvasUIMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type CanvasUIProps = {
onImageDelete: () => void;
hasImage: boolean;
colors: string[];
onZoomIn: () => void;
onZoomOut: () => void;
};

export default function CanvasUIMobile({
Expand All @@ -26,6 +28,8 @@ export default function CanvasUIMobile({
onImageDelete,
hasImage,
colors,
onZoomIn,
onZoomOut,
}: CanvasUIProps) {
const [isPressed, setIsPressed] = useState(false);
const [showConfirmEffect, setShowConfirmEffect] = useState(false);
Expand Down Expand Up @@ -319,10 +323,52 @@ export default function CanvasUIMobile({
)}
</div>
{/* 좌표 표시창 */}
<div className='pointer-events-none fixed right-[20px] bottom-[20px] z-[9999] rounded-[8px] bg-[rgba(0,0,0,0.8)] p-[10px] text-white'>
<div className='pointer-events-none fixed right-[20px] bottom-[80px] z-[9999] rounded-[8px] bg-[rgba(0,0,0,0.8)] p-[10px] text-white'>
{hoverPos ? `(${hoverPos.x}, ${hoverPos.y})` : 'OutSide'}
</div>

{/* 확대/축소 버튼 */}
<div className='pointer-events-auto fixed bottom-[20px] right-[20px] z-[9999] flex flex-row gap-2'>
<button
onClick={onZoomIn}
className='flex h-12 w-12 items-center justify-center rounded-full bg-gray-700 text-white shadow-lg transition-transform hover:bg-gray-600 active:scale-95'
title='확대'
>
<svg
className='h-6 w-6'
fill='none'
stroke='currentColor'
viewBox='0 0 24 24'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d='M12 4v16m8-8H4'
/>
</svg>
</button>
<button
onClick={onZoomOut}
className='flex h-12 w-12 items-center justify-center rounded-full bg-gray-700 text-white shadow-lg transition-transform hover:bg-gray-600 active:scale-95'
title='축소'
>
<svg
className='h-6 w-6'
fill='none'
stroke='currentColor'
viewBox='0 0 24 24'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d='M20 12H4'
/>
</svg>
</button>
</div>

{/* 팔레트 */}
<div
className={`pointer-events-auto fixed z-[9999] rounded-2xl border border-cyan-400/20 bg-gradient-to-br from-slate-900/90 to-black/80 p-3 shadow-2xl backdrop-blur-xl transition-all duration-500 ease-out ${
Expand Down
36 changes: 36 additions & 0 deletions src/components/canvas/PixelCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,40 @@ function PixelCanvas({
[draw, updateOverlay, canvasSize]
);

const zoomCanvas = useCallback(
(scaleChange: number) => {
const canvas = renderCanvasRef.current;
if (!canvas) return;

const centerX = canvas.clientWidth / 2;
const centerY = canvas.clientHeight / 2;

const xs = (centerX - viewPosRef.current.x) / scaleRef.current;
const ys = (centerY - viewPosRef.current.y) / scaleRef.current;

const newScale = Math.max(
MIN_SCALE,
Math.min(MAX_SCALE, scaleRef.current * scaleChange)
);

scaleRef.current = newScale;
viewPosRef.current.x = centerX - xs * scaleRef.current;
viewPosRef.current.y = centerY - ys * scaleRef.current;

draw();
updateOverlay(centerX, centerY);
},
[draw, updateOverlay]
);

const handleZoomIn = useCallback(() => {
zoomCanvas(1.2);
}, [zoomCanvas]);

const handleZoomOut = useCallback(() => {
zoomCanvas(1 / 1.2);
}, [zoomCanvas]);

const handleCooltime = useCallback(() => {
startCooldown(10);
}, [startCooldown]);
Expand Down Expand Up @@ -1070,6 +1104,8 @@ function PixelCanvas({
onImageAttach={handleImageAttach}
onImageDelete={cancelImage}
hasImage={!!imageCanvasRef.current}
onZoomIn={handleZoomIn}
onZoomOut={handleZoomOut}
/>
)}
{showImageControls && !isImageFixed && (
Expand Down
95 changes: 75 additions & 20 deletions src/components/toast/InstructionsToast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

export const showInstructionsToast = () => {
toast(
const isMobile = window.innerWidth < 768; // 모바일 기기 판단 기준

const mobileInstructions = (
<div className='mx-auto max-w-2xl space-y-3 rounded-xl border border-cyan-400/50 bg-gray-800 p-4 font-sans shadow-lg'>
<div className='text-base font-medium text-cyan-300'>How to Play</div>
<div className='flex items-center space-x-3'>
Expand All @@ -20,8 +22,8 @@ export const showInstructionsToast = () => {
<circle cx='9.5' cy='7' r='1.5' fill='currentColor'></circle>
</svg>
<p className='text-gray-300'>
<span className='text-sm font-bold text-white'>마우스 좌클릭:</span>{' '}
<span className='text-sm'>픽셀 선택</span>
<span className='text-sm font-bold text-white'>픽셀 선택:</span>{' '}
<span className='text-sm'>탭 (클릭)</span>
</p>
</div>
<div className='flex items-center space-x-3'>
Expand All @@ -39,8 +41,10 @@ export const showInstructionsToast = () => {
/>
</svg>
<p className='text-gray-300'>
<span className='text-sm font-bold text-white'>마우스 휠:</span>{' '}
<span className='text-sm'>확대/축소</span>
<span className='text-sm font-bold text-white'>확대/축소:</span>{' '}
<span className='text-sm'>
오른쪽 하단 +/- 버튼 또는 두 손가락 핀치
</span>
</p>
</div>
<div className='flex items-center space-x-3'>
Expand All @@ -57,7 +61,19 @@ export const showInstructionsToast = () => {
<line x1='12' y1='4' x2='12' y2='10'></line>
<circle cx='9.5' cy='7' r='1.5' fill='currentColor'></circle>
</svg>
{/* <svg
<p className='text-gray-300'>
<span className='text-sm font-bold text-white'>캔버스 이동:</span>{' '}
<span className='text-sm'>한 손가락 드래그</span>
</p>
</div>
</div>
);

const desktopInstructions = (
<div className='mx-auto max-w-2xl space-y-3 rounded-xl border border-cyan-400/50 bg-gray-800 p-4 font-sans shadow-lg'>
<div className='text-base font-medium text-cyan-300'>How to Play</div>
<div className='flex items-center space-x-3'>
<svg
className='h-6 w-6 text-cyan-400'
viewBox='0 0 24 24'
fill='none'
Expand All @@ -68,8 +84,46 @@ export const showInstructionsToast = () => {
>
<rect x='7' y='4' width='10' height='16' rx='5' ry='5'></rect>
<line x1='12' y1='4' x2='12' y2='10'></line>
<circle cx='14.5' cy='7' r='1.5' fill='currentColor'></circle>
</svg> */}
<circle cx='9.5' cy='7' r='1.5' fill='currentColor'></circle>
</svg>
<p className='text-gray-300'>
<span className='text-sm font-bold text-white'>마우스 좌클릭:</span>{' '}
<span className='text-sm'>픽셀 선택</span>
</p>
</div>
<div className='flex items-center space-x-3'>
<svg
className='h-6 w-6 text-cyan-400'
fill='none'
viewBox='0 0 24 24'
stroke='currentColor'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='2'
d='M12 6v6m0 0v6m0-6h6m-6 0H6'
/>
</svg>
<p className='text-gray-300'>
<span className='text-sm font-bold text-white'>마우스 휠:</span>{' '}
<span className='text-sm'>확대/축소</span>
</p>
</div>
<div className='flex items-center space-x-3'>
<svg
className='h-6 w-6 text-cyan-400'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
>
<rect x='7' y='4' width='10' height='16' rx='5' ry='5'></rect>
<line x1='12' y1='4' x2='12' y2='10'></line>
<circle cx='9.5' cy='7' r='1.5' fill='currentColor'></circle>
</svg>
<p className='text-gray-300'>
<span className='text-sm font-bold text-white'>
마우스 좌클릭 후 드래그:
Expand All @@ -96,17 +150,18 @@ export const showInstructionsToast = () => {
<span className='text-sm'>좌상단 이미지 버튼 클릭</span>
</p>
</div>
</div>,
{
position: 'top-center',
autoClose: 5000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: 'dark',
style: { backgroundColor: '#1f2937', color: 'white' },
}
</div>
);

toast(isMobile ? mobileInstructions : desktopInstructions, {
position: 'top-center',
autoClose: 5000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: 'dark',
style: { backgroundColor: '#1f2937', color: 'white' },
});
};
4 changes: 2 additions & 2 deletions src/hooks/useSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const useSocket = (
canvas_id: string | undefined,
onCooldownReceived?: (cooldown: CooldownData) => void
) => {
const { accessToken } = useAuthStore();
const { accessToken, user } = useAuthStore();
const pixelCallbackRef = useRef(onPixelReceived);
const cooldownCallbackRef = useRef(onCooldownReceived);
const [isConnected, setIsConnected] = useState(false);
Expand Down Expand Up @@ -78,7 +78,7 @@ export const useSocket = (
socketService.disconnect();
setIsConnected(false);
};
}, [canvas_id, accessToken]);
}, [canvas_id, accessToken, user]);

const sendPixel = (pixel: PixelData) => {
if (!canvas_id) return;
Expand Down