- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경 - DB 연결 하드코딩 → .env 기반으로 변경 - MySQL strict mode DATE 오류 수정
1265 lines
52 KiB
PHP
1265 lines
52 KiB
PHP
<?php
|
|
/**
|
|
* 그리기 기능 컴포넌트
|
|
*
|
|
* 사용법:
|
|
* 1. CSS 파일 포함: <link href="css/style.css" rel="stylesheet">
|
|
* 2. 이 파일 포함: <?php include 'draw.php'; ?>
|
|
* 3. 그리기 버튼 추가: <button type="button" id="drawBtn" class="btn btn-primary">그리기</button>
|
|
* 4. 미리보기 컨테이너 추가: <div id="previewContainer" class="text-center mb-3"></div>
|
|
*/
|
|
|
|
// 필수 요소들이 있는지 확인
|
|
if (!isset($previewContainerId)) {
|
|
$previewContainerId = 'previewContainer';
|
|
}
|
|
if (!isset($drawBtnId)) {
|
|
$drawBtnId = 'drawBtn';
|
|
}
|
|
?>
|
|
|
|
<!-- 그리기 도구 HTML -->
|
|
<div class="drawing-tools" style="display: none;">
|
|
<div class="row drawing-row">
|
|
<div class="col-sm-12">
|
|
<div class="drawing-container">
|
|
<div class="drawing-canvas-area" id="drawingCanvasArea">
|
|
<!-- 캔버스가 여기에 생성됩니다 -->
|
|
</div>
|
|
<div class="drawing-controls">
|
|
<h6 class="mb-3">그리기 도구</h6>
|
|
<div class="mb-3">
|
|
<label class="form-label">그리기 모드:</label>
|
|
<select id="drawMode" class="form-select form-select-sm">
|
|
<option value="polyline">점연결</option>
|
|
<option value="line">직선</option>
|
|
<option value="free">자유선</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">색상:</label>
|
|
<input type="color" id="drawColor" class="form-control form-control-sm" value="#000000">
|
|
</div>
|
|
<div class="mb-3">
|
|
<button type="button" id="lineBtn" class="btn btn-outline-primary btn-sm me-2">선</button>
|
|
<button type="button" id="textBtn" class="btn btn-outline-primary btn-sm me-2">텍스트</button>
|
|
<button type="button" id="eraserBtn" class="btn btn-outline-warning btn-sm">지우개</button>
|
|
</div>
|
|
<div class="mb-3" id="eraserSizeContainer" style="display: none;">
|
|
<label class="form-label">지우개 크기:</label>
|
|
<input type="range" id="eraserSize" class="form-range" min="5" max="50" value="15">
|
|
<small class="text-muted" id="eraserSizeLabel">15px</small>
|
|
</div>
|
|
<div class="mb-3" id="straightModeContainer" style="display: block;">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="straightToggle">
|
|
<label class="form-check-label" for="straightToggle" id="straightToggleLabel">
|
|
직각모드
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<button type="button" id="saveDrawingBtn" class="btn btn-success btn-sm me-2">저장</button>
|
|
<button type="button" id="clearDrawingBtn" class="btn btn-danger btn-sm">초기화</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 그리기 기능 JavaScript
|
|
(function() {
|
|
'use strict';
|
|
|
|
// 전역 변수
|
|
let canvas = null;
|
|
let ctx = null;
|
|
let polyPrev = null;
|
|
let drawing = false;
|
|
let startX = 0;
|
|
let startY = 0;
|
|
let previewLine = null;
|
|
let drawingData = [];
|
|
let originalImage = null;
|
|
let eraserRadius = 15;
|
|
let currentPath = [];
|
|
let textMode = false;
|
|
let isEraser = false;
|
|
let drawMode = 'polyline';
|
|
let drawColor = '#000000';
|
|
let handlers, onPolyClick, onPolyKeydown;
|
|
// 전역에 이미지 객체를 둡니다.
|
|
let erasedImageObj = null;
|
|
|
|
// 그리기 기능 초기화 함수
|
|
function initializeDrawingFeatures() {
|
|
// 이미 초기화된 경우 중복 실행 방지
|
|
if ($('#<?= $drawBtnId ?>').data('initialized')) {
|
|
console.log('이미 그리기 기능이 초기화됨');
|
|
return;
|
|
}
|
|
$('#<?= $drawBtnId ?>').data('initialized', true);
|
|
console.log('initializeDrawingFeatures 함수 호출됨');
|
|
|
|
// 그리기 관련 요소들
|
|
const preview = document.getElementById('<?= $previewContainerId ?>');
|
|
const drawBtn = document.getElementById('<?= $drawBtnId ?>');
|
|
const textBtn = document.getElementById('textBtn');
|
|
const saveDrawingBtn = document.getElementById('saveDrawingBtn');
|
|
const clearBtn = document.getElementById('clearDrawingBtn');
|
|
const modeSelect = document.getElementById('drawMode');
|
|
const colorPicker = document.getElementById('drawColor');
|
|
const lineBtn = document.getElementById('lineBtn');
|
|
const eraserBtn = document.getElementById('eraserBtn');
|
|
const eraserSize = document.getElementById('eraserSize');
|
|
const eraserSizeLabel = document.getElementById('eraserSizeLabel');
|
|
|
|
// 그리기 버튼 이벤트
|
|
if (drawBtn) {
|
|
drawBtn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
console.log('그리기 버튼 클릭 이벤트 발생');
|
|
drawBtnClickHandler();
|
|
});
|
|
}
|
|
|
|
// 그리기 컨트롤 이벤트 핸들러 추가
|
|
attachDrawingControlEvents();
|
|
}
|
|
|
|
// 그리기 버튼 클릭 핸들러 함수
|
|
function drawBtnClickHandler() {
|
|
const drawBtn = document.getElementById('<?= $drawBtnId ?>');
|
|
console.log('그리기 버튼 클릭됨, 현재 상태:', drawBtn.classList.contains('active'));
|
|
|
|
if (drawBtn.classList.contains('active')) {
|
|
// 그리기 중지: 모든 상태 원복
|
|
console.log('그리기 중지 모드 - 원상태로 복원');
|
|
|
|
// 1. 캔버스 완전 제거
|
|
if (canvas) {
|
|
try {
|
|
['mousedown','mousemove','mouseup','mouseout'].forEach(evt =>
|
|
canvas.removeEventListener(evt, handlers[evt])
|
|
);
|
|
canvas.removeEventListener('click', onPolyClick);
|
|
document.removeEventListener('keydown', onPolyKeydown);
|
|
canvas.remove();
|
|
} catch (e) {
|
|
console.log('canvas 제거 중 오류:', e);
|
|
}
|
|
canvas = null;
|
|
ctx = null;
|
|
console.log('캔버스 제거 완료');
|
|
}
|
|
|
|
// 기존 drawingCanvas도 제거
|
|
const existingCanvas = document.getElementById('drawingCanvas');
|
|
if (existingCanvas) {
|
|
console.log('기존 drawingCanvas 제거');
|
|
existingCanvas.remove();
|
|
}
|
|
|
|
// 2. 버튼 상태 변경
|
|
drawBtn.classList.remove('active');
|
|
drawBtn.textContent = '그리기';
|
|
|
|
// 3. 그리기 row 제거
|
|
let drawingRow = document.querySelector('.drawing-row');
|
|
if (drawingRow) {
|
|
drawingRow.remove();
|
|
console.log('그리기 row 제거됨');
|
|
}
|
|
|
|
// 4. 원본 이미지 다시 표시 및 미러링 제거
|
|
const preview = document.getElementById('<?= $previewContainerId ?>');
|
|
const originalImgElement = preview.querySelector('img:not(.mirror-image)');
|
|
const mirrorImg = preview.querySelector('.mirror-image');
|
|
|
|
if (originalImgElement) {
|
|
originalImgElement.style.display = '';
|
|
console.log('원본 이미지 다시 표시');
|
|
}
|
|
|
|
if (mirrorImg) {
|
|
mirrorImg.remove();
|
|
console.log('미러링 이미지 제거');
|
|
}
|
|
|
|
// 그리기 데이터가 없으면 "아직 등록된 이미지가 없습니다" 메시지 표시
|
|
if (drawingData.length === 0) {
|
|
showNoImageMessage();
|
|
}
|
|
|
|
// 지우개 가상 커서 제거
|
|
removeEraserCursor();
|
|
|
|
// previewContainer에서 drawing-mode 클래스 제거
|
|
if (preview) {
|
|
preview.classList.remove('drawing-mode');
|
|
console.log('previewContainer에서 drawing-mode 클래스 제거');
|
|
}
|
|
|
|
// drawBtn의 initialized 플래그를 false로 돌려줌
|
|
$('#<?= $drawBtnId ?>').data('initialized', false);
|
|
|
|
console.log('그리기 중지 완료 - 모든 상태 원복됨');
|
|
|
|
} else {
|
|
// 그리기 시작: 새로운 row 생성
|
|
console.log('그리기 시작 모드 - 새로운 row 생성');
|
|
|
|
// 1. 버튼 상태 변경
|
|
drawBtn.classList.add('active');
|
|
drawBtn.textContent = '그리기 중지';
|
|
|
|
// 2. "아직 등록된 이미지가 없습니다" 메시지 즉시 숨기기
|
|
hideNoImageMessage();
|
|
|
|
// 3. 기존 미러링 이미지 제거
|
|
const preview = document.getElementById('<?= $previewContainerId ?>');
|
|
if (preview) {
|
|
const mirrorImg = preview.querySelector('.mirror-image');
|
|
if (mirrorImg) {
|
|
mirrorImg.remove();
|
|
console.log('기존 미러링 이미지 제거');
|
|
}
|
|
}
|
|
|
|
// 4. 그리기 도구 표시
|
|
const drawingTools = document.querySelector('.drawing-tools');
|
|
if (drawingTools) {
|
|
drawingTools.style.display = 'block';
|
|
}
|
|
|
|
// 5. 캔버스 생성 및 설정
|
|
setTimeout(() => {
|
|
createAndShowCanvas();
|
|
// 6. 직각모드 기본 체크 설정
|
|
setTimeout(() => {
|
|
const straightToggle = document.getElementById('straightToggle');
|
|
if (straightToggle) {
|
|
straightToggle.checked = true;
|
|
console.log('직각모드 기본 체크 설정 완료');
|
|
}
|
|
|
|
// previewContainer에 drawing-mode 클래스 추가
|
|
if (preview) {
|
|
preview.classList.add('drawing-mode');
|
|
console.log('previewContainer에 drawing-mode 클래스 추가');
|
|
}
|
|
|
|
// 그리기 시작 시 "아직 등록된 이미지가 없습니다" 메시지 즉시 숨기기
|
|
hideNoImageMessage();
|
|
}, 100);
|
|
console.log('그리기 시작 완료 - 새로운 row 생성됨');
|
|
}, 200);
|
|
}
|
|
}
|
|
|
|
// 그리기 컨트롤 이벤트 핸들러 추가
|
|
function attachDrawingControlEvents() {
|
|
console.log('그리기 컨트롤 이벤트 핸들러 추가 시작');
|
|
|
|
// 그리기 관련 요소들
|
|
const modeSelect = document.getElementById('drawMode');
|
|
const colorPicker = document.getElementById('drawColor');
|
|
const lineBtn = document.getElementById('lineBtn');
|
|
const textBtn = document.getElementById('textBtn');
|
|
const eraserBtn = document.getElementById('eraserBtn');
|
|
const eraserSize = document.getElementById('eraserSize');
|
|
const eraserSizeLabel = document.getElementById('eraserSizeLabel');
|
|
|
|
// 그리기 모드 선택
|
|
if (modeSelect) {
|
|
modeSelect.addEventListener('change', function() {
|
|
drawMode = this.value;
|
|
|
|
// 지우개 크기 컨테이너 숨기기 (선택된 모드가 지우개가 아닌 경우)
|
|
const eraserSizeContainer = document.getElementById('eraserSizeContainer');
|
|
if (eraserSizeContainer) {
|
|
eraserSizeContainer.style.display = 'none';
|
|
console.log('지우개 크기 컨테이너 숨김 (모드 변경)');
|
|
}
|
|
|
|
// 직각모드 컨테이너 숨기기 (모드 변경 시)
|
|
const straightModeContainer = document.getElementById('straightModeContainer');
|
|
if (straightModeContainer) {
|
|
straightModeContainer.style.display = 'none';
|
|
console.log('직각모드 컨테이너 숨김 (모드 변경)');
|
|
}
|
|
|
|
console.log('그리기 모드 변경:', drawMode);
|
|
});
|
|
}
|
|
|
|
// 색상 선택
|
|
if (colorPicker) {
|
|
colorPicker.addEventListener('input', function() {
|
|
drawColor = this.value;
|
|
console.log('색상 변경:', drawColor);
|
|
});
|
|
}
|
|
|
|
// 선 버튼
|
|
if (lineBtn) {
|
|
lineBtn.addEventListener('click', function() {
|
|
isEraser = false;
|
|
if (ctx) ctx.globalCompositeOperation = 'source-over';
|
|
textMode = false;
|
|
drawMode = 'polyline';
|
|
if (modeSelect) modeSelect.value = 'polyline';
|
|
lineBtn.classList.add('active');
|
|
textBtn.classList.remove('active');
|
|
eraserBtn.classList.remove('active');
|
|
|
|
// 지우개 크기 컨테이너 숨기기
|
|
const eraserSizeContainer = document.getElementById('eraserSizeContainer');
|
|
if (eraserSizeContainer) {
|
|
eraserSizeContainer.style.display = 'none';
|
|
console.log('지우개 크기 컨테이너 숨김');
|
|
}
|
|
|
|
// 직각모드 컨테이너 표시
|
|
const straightModeContainer = document.getElementById('straightModeContainer');
|
|
if (straightModeContainer) {
|
|
straightModeContainer.style.display = 'block';
|
|
console.log('직각모드 컨테이너 표시됨');
|
|
}
|
|
|
|
// 지우개 가상 커서 제거
|
|
removeEraserCursor();
|
|
|
|
console.log('선 모드 활성화');
|
|
});
|
|
}
|
|
|
|
// 텍스트 버튼
|
|
if (textBtn) {
|
|
textBtn.addEventListener('click', function() {
|
|
isEraser = false;
|
|
if (ctx) ctx.globalCompositeOperation = 'source-over';
|
|
textMode = true;
|
|
drawMode = null;
|
|
textBtn.classList.add('active');
|
|
lineBtn.classList.remove('active');
|
|
eraserBtn.classList.remove('active');
|
|
|
|
// 지우개 크기 컨테이너 숨기기
|
|
const eraserSizeContainer = document.getElementById('eraserSizeContainer');
|
|
if (eraserSizeContainer) {
|
|
eraserSizeContainer.style.display = 'none';
|
|
console.log('지우개 크기 컨테이너 숨김');
|
|
}
|
|
|
|
// 직각모드 컨테이너 숨기기
|
|
const straightModeContainer = document.getElementById('straightModeContainer');
|
|
if (straightModeContainer) {
|
|
straightModeContainer.style.display = 'none';
|
|
console.log('직각모드 컨테이너 숨김');
|
|
}
|
|
|
|
// 지우개 가상 커서 제거
|
|
removeEraserCursor();
|
|
|
|
console.log('텍스트 모드 활성화');
|
|
});
|
|
}
|
|
|
|
// 지우개 버튼
|
|
if (eraserBtn) {
|
|
eraserBtn.addEventListener('click', function() {
|
|
isEraser = true;
|
|
textMode = false;
|
|
drawMode = null;
|
|
eraserBtn.classList.add('active');
|
|
lineBtn.classList.remove('active');
|
|
textBtn.classList.remove('active');
|
|
|
|
// 지우개 크기 컨테이너 표시
|
|
const eraserSizeContainer = document.getElementById('eraserSizeContainer');
|
|
if (eraserSizeContainer) {
|
|
eraserSizeContainer.style.display = 'block';
|
|
console.log('지우개 크기 컨테이너 표시됨');
|
|
}
|
|
|
|
// 직각모드 컨테이너 숨기기
|
|
const straightModeContainer = document.getElementById('straightModeContainer');
|
|
if (straightModeContainer) {
|
|
straightModeContainer.style.display = 'none';
|
|
console.log('직각모드 컨테이너 숨김');
|
|
}
|
|
|
|
// 지우개 가상 커서 생성
|
|
updateEraserCursor();
|
|
|
|
console.log('지우개 모드 활성화');
|
|
});
|
|
}
|
|
|
|
// 지우개 크기 조절
|
|
if (eraserSize && eraserSizeLabel) {
|
|
eraserSize.addEventListener('input', function() {
|
|
eraserRadius = parseInt(this.value);
|
|
eraserSizeLabel.textContent = eraserRadius + 'px';
|
|
|
|
// 가상 커서 크기 업데이트
|
|
updateEraserCursor();
|
|
});
|
|
}
|
|
|
|
// 초기화 버튼
|
|
const clearBtn = document.getElementById('clearDrawingBtn');
|
|
if (clearBtn) {
|
|
clearBtn.addEventListener('click', function() {
|
|
if (canvas && ctx) {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
if (originalImage) {
|
|
ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
|
|
} else {
|
|
ctx.fillStyle = '#ffffff';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
}
|
|
drawingData = [];
|
|
currentPath = [];
|
|
console.log('캔버스 초기화 완료');
|
|
|
|
// 미러링 업데이트
|
|
updatePreviewMirror();
|
|
|
|
// 그리기 데이터가 없으면 "아직 등록된 이미지가 없습니다" 메시지 표시
|
|
if (drawingData.length === 0) {
|
|
showNoImageMessage();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
console.log('그리기 컨트롤 이벤트 핸들러 추가 완료');
|
|
}
|
|
|
|
// 캔버스 겹치기 생성 함수
|
|
function createCanvasOverlay() {
|
|
console.log('createCanvasOverlay 함수 시작');
|
|
|
|
const cv = document.createElement('canvas');
|
|
cv.id = 'drawingCanvas';
|
|
cv.classList.add('drawing-canvas');
|
|
|
|
// 기준 크기 설정 (320px x 240px)
|
|
const STANDARD_WIDTH = 320;
|
|
const STANDARD_HEIGHT = 240;
|
|
|
|
cv.width = STANDARD_WIDTH;
|
|
cv.height = STANDARD_HEIGHT;
|
|
|
|
// 캔버스 스타일 설정 - 기준 크기로 고정
|
|
cv.style.width = STANDARD_WIDTH + 'px';
|
|
cv.style.height = STANDARD_HEIGHT + 'px';
|
|
cv.style.maxWidth = '100%';
|
|
cv.style.maxHeight = '100%';
|
|
cv.style.objectFit = 'contain';
|
|
cv.style.display = 'block';
|
|
cv.style.margin = '0 auto';
|
|
|
|
const cctx = cv.getContext('2d');
|
|
|
|
console.log('캔버스 크기 설정 (1:1):', cv.width, 'x', cv.height);
|
|
|
|
// 원본 이미지 저장 및 즉시 그리기
|
|
const preview = document.getElementById('<?= $previewContainerId ?>');
|
|
const img = preview ? preview.querySelector('img') : null;
|
|
console.log('preview 내부의 img 요소:', img);
|
|
|
|
if (img) {
|
|
console.log('이미지가 존재합니다. originalImage 설정');
|
|
originalImage = new Image();
|
|
originalImage.src = img.src;
|
|
originalImage.onload = function() {
|
|
console.log('originalImage 로드 완료');
|
|
cctx.drawImage(originalImage, 0, 0, cv.width, cv.height);
|
|
// 미러링 업데이트 (이미지가 있을 때만)
|
|
updatePreviewMirror();
|
|
};
|
|
cctx.drawImage(img, 0, 0, cv.width, cv.height);
|
|
console.log('이미지를 캔버스에 그렸습니다.');
|
|
|
|
// 이미지가 있으면 "아직 등록된 이미지가 없습니다" 메시지 숨기기
|
|
hideNoImageMessage();
|
|
} else {
|
|
console.log('이미지가 없습니다. 흰색 배경으로 채웁니다.');
|
|
// 이미지가 없으면 흰색 배경 채우기
|
|
cctx.fillStyle = '#ffffff';
|
|
cctx.fillRect(0, 0, cv.width, cv.height);
|
|
|
|
// 그리기 모드에서는 메시지를 표시하지 않음 (나중에 그리기 시작 시 숨김)
|
|
// showNoImageMessage(); // 이 줄을 제거
|
|
}
|
|
|
|
console.log('createCanvasOverlay 함수 완료, 캔버스 반환:', cv);
|
|
return cv;
|
|
}
|
|
|
|
// "아직 등록된 이미지가 없습니다" 메시지 표시 함수
|
|
function showNoImageMessage() {
|
|
const preview = document.getElementById('<?= $previewContainerId ?>');
|
|
if (preview) {
|
|
// 기존 메시지가 있는지 확인
|
|
let noImageMsg = preview.querySelector('.no-image-message');
|
|
if (!noImageMsg) {
|
|
noImageMsg = document.createElement('div');
|
|
noImageMsg.className = 'no-image-message text-center text-muted mt-3';
|
|
noImageMsg.innerHTML = '<i class="fas fa-image"></i> 아직 등록된 이미지가 없습니다.';
|
|
preview.appendChild(noImageMsg);
|
|
}
|
|
noImageMsg.style.display = 'block';
|
|
console.log('"아직 등록된 이미지가 없습니다" 메시지 표시');
|
|
}
|
|
}
|
|
|
|
// "아직 등록된 이미지가 없습니다" 메시지 숨기기 함수
|
|
function hideNoImageMessage() {
|
|
const preview = document.getElementById('<?= $previewContainerId ?>');
|
|
if (preview) {
|
|
const noImageMsg = preview.querySelector('.no-image-message');
|
|
if (noImageMsg) {
|
|
noImageMsg.style.display = 'none';
|
|
console.log('"아직 등록된 이미지가 없습니다" 메시지 숨김');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 지우개 가상 커서 생성/업데이트 함수
|
|
function updateEraserCursor() {
|
|
const canvas = document.getElementById('drawingCanvas');
|
|
if (!canvas || !isEraser) return;
|
|
|
|
// 기존 커서 제거
|
|
removeEraserCursor();
|
|
|
|
// 새로운 커서 생성
|
|
const cursor = document.createElement('div');
|
|
cursor.id = 'eraserCursor';
|
|
cursor.style.position = 'absolute';
|
|
cursor.style.width = (eraserRadius * 2) + 'px';
|
|
cursor.style.height = (eraserRadius * 2) + 'px';
|
|
cursor.style.border = '2px solid #ff4444';
|
|
cursor.style.borderRadius = '50%';
|
|
cursor.style.backgroundColor = 'rgba(255, 68, 68, 0.2)';
|
|
cursor.style.pointerEvents = 'none';
|
|
cursor.style.zIndex = '1000';
|
|
cursor.style.display = 'none';
|
|
|
|
// 캔버스 컨테이너에 추가
|
|
const canvasArea = document.getElementById('drawingCanvasArea');
|
|
if (canvasArea) {
|
|
canvasArea.style.position = 'relative';
|
|
canvasArea.appendChild(cursor);
|
|
}
|
|
}
|
|
|
|
// 지우개 가상 커서 제거 함수
|
|
function removeEraserCursor() {
|
|
const cursor = document.getElementById('eraserCursor');
|
|
if (cursor) {
|
|
cursor.remove();
|
|
}
|
|
}
|
|
|
|
// 지우개 가상 커서 위치 업데이트 함수
|
|
function updateEraserCursorPosition(e) {
|
|
const cursor = document.getElementById('eraserCursor');
|
|
const canvas = document.getElementById('drawingCanvas');
|
|
if (!cursor || !canvas || !isEraser) return;
|
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
|
|
cursor.style.left = (x - eraserRadius) + 'px';
|
|
cursor.style.top = (y - eraserRadius) + 'px';
|
|
cursor.style.display = 'block';
|
|
}
|
|
|
|
// 미러링 업데이트 함수
|
|
function updatePreviewMirror() {
|
|
const canvas = document.getElementById('drawingCanvas');
|
|
const preview = document.getElementById('<?= $previewContainerId ?>');
|
|
|
|
if (canvas && preview) {
|
|
// 그리기 데이터가 있을 때만 미러링 표시
|
|
if (drawingData.length > 0) {
|
|
// 캔버스의 현재 상태를 이미지로 변환
|
|
const dataURL = canvas.toDataURL('image/png');
|
|
|
|
// previewContainer에 미러링 이미지 표시
|
|
let mirrorImg = preview.querySelector('.mirror-image');
|
|
if (!mirrorImg) {
|
|
mirrorImg = document.createElement('img');
|
|
mirrorImg.className = 'mirror-image';
|
|
// 기준 크기로 설정 (320px x 240px)
|
|
mirrorImg.style.width = '320px';
|
|
mirrorImg.style.height = '240px';
|
|
mirrorImg.style.maxWidth = '100%';
|
|
mirrorImg.style.maxHeight = '100%';
|
|
mirrorImg.style.objectFit = 'contain';
|
|
mirrorImg.style.border = '1px solid #ddd';
|
|
mirrorImg.style.borderRadius = '4px';
|
|
preview.appendChild(mirrorImg);
|
|
} else {
|
|
// 기존 미러링 이미지도 기준 크기로 업데이트
|
|
mirrorImg.style.width = '320px';
|
|
mirrorImg.style.height = '240px';
|
|
}
|
|
|
|
mirrorImg.src = dataURL;
|
|
mirrorImg.style.display = 'block';
|
|
|
|
// 원본 이미지 숨기기
|
|
const originalImg = preview.querySelector('img:not(.mirror-image)');
|
|
if (originalImg) {
|
|
originalImg.style.display = 'none';
|
|
}
|
|
|
|
console.log('미러링 업데이트 완료');
|
|
} else {
|
|
// 그리기 데이터가 없으면 미러링 이미지 숨기기
|
|
const mirrorImg = preview.querySelector('.mirror-image');
|
|
if (mirrorImg) {
|
|
mirrorImg.style.display = 'none';
|
|
}
|
|
|
|
// 원본 이미지 표시
|
|
const originalImg = preview.querySelector('img:not(.mirror-image)');
|
|
if (originalImg) {
|
|
originalImg.style.display = '';
|
|
}
|
|
|
|
console.log('미러링 숨김 (그리기 데이터 없음)');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 그리기 데이터 저장 함수
|
|
function saveDrawingData(type, data) {
|
|
drawingData.push({
|
|
type: type,
|
|
data: data,
|
|
color: drawColor,
|
|
lineWidth: ctx.lineWidth || 2
|
|
});
|
|
|
|
// 그리기 데이터가 추가되면 미러링 업데이트
|
|
setTimeout(() => {
|
|
updatePreviewMirror();
|
|
}, 10);
|
|
}
|
|
|
|
// 이벤트 핸들러들 정의
|
|
handlers = {
|
|
mousedown(e) {
|
|
if (!isEraser && drawMode!=='free' && drawMode!=='line' && drawMode!=='polyline') return;
|
|
|
|
if (drawMode === 'polyline') return;
|
|
|
|
drawing = true;
|
|
const rect = canvas.getBoundingClientRect();
|
|
startX = e.clientX - rect.left;
|
|
startY = e.clientY - rect.top;
|
|
if (drawMode === 'free') {
|
|
ctx.beginPath();
|
|
ctx.moveTo(startX, startY);
|
|
currentPath = [{x: startX, y: startY}];
|
|
}
|
|
},
|
|
mousemove(e) {
|
|
const rect = canvas.getBoundingClientRect();
|
|
const x = e.clientX - rect.left, y = e.clientY - rect.top;
|
|
|
|
if (drawMode === 'polyline' && polyPrev) {
|
|
drawPreviewLine(x, y);
|
|
}
|
|
|
|
if (!drawing) return;
|
|
|
|
if (isEraser) {
|
|
ctx.save();
|
|
ctx.globalCompositeOperation = 'destination-out';
|
|
ctx.lineWidth = eraserRadius * 2;
|
|
ctx.lineCap = 'round';
|
|
ctx.lineJoin = 'round';
|
|
|
|
const gradient = ctx.createRadialGradient(x, y, 0, x, y, eraserRadius);
|
|
gradient.addColorStop(0, 'rgba(0,0,0,1)');
|
|
gradient.addColorStop(1, 'rgba(0,0,0,0)');
|
|
|
|
ctx.fillStyle = gradient;
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, eraserRadius, 0, 2 * Math.PI);
|
|
ctx.fill();
|
|
|
|
ctx.restore();
|
|
|
|
// 지우개 사용 시 실시간 미러링 업데이트
|
|
setTimeout(() => {
|
|
updatePreviewMirror();
|
|
}, 10);
|
|
} else {
|
|
if (drawMode !== 'free' || !drawing) return;
|
|
|
|
ctx.strokeStyle = drawColor;
|
|
|
|
if (e.shiftKey) {
|
|
const deltaX = Math.abs(x - startX);
|
|
const deltaY = Math.abs(y - startY);
|
|
|
|
if (deltaX > deltaY) {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
redrawCanvas();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(startX, startY);
|
|
ctx.lineTo(x, startY);
|
|
ctx.stroke();
|
|
currentPath = [{x: startX, y: startY}, {x: x, y: startY}];
|
|
} else {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
redrawCanvas();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(startX, startY);
|
|
ctx.lineTo(startX, y);
|
|
ctx.stroke();
|
|
currentPath = [{x: startX, y: startY}, {x: startX, y: y}];
|
|
}
|
|
} else {
|
|
ctx.lineTo(x, y);
|
|
ctx.stroke();
|
|
currentPath.push({x: x, y: y});
|
|
}
|
|
}
|
|
},
|
|
mouseup(e) {
|
|
if (isEraser && drawing) {
|
|
// 1. 현재 캔버스 이미지를 저장
|
|
const erasedImage = canvas.toDataURL('image/png');
|
|
// 2. drawingData를 이미지로만 덮어쓰기
|
|
drawingData = [{
|
|
type: 'image',
|
|
data: erasedImage
|
|
}];
|
|
// 3. 미러링 업데이트
|
|
setTimeout(() => {
|
|
updatePreviewMirror();
|
|
}, 10);
|
|
}
|
|
if (drawMode === 'line' && drawing) {
|
|
const rect = canvas.getBoundingClientRect();
|
|
const endX = e.clientX - rect.left, endY = e.clientY - rect.top;
|
|
ctx.beginPath();
|
|
ctx.moveTo(startX, startY);
|
|
ctx.lineTo(endX, endY);
|
|
ctx.strokeStyle = drawColor;
|
|
ctx.stroke();
|
|
|
|
saveDrawingData('line', { startX, startY, endX, endY });
|
|
}
|
|
else if (drawMode === 'free' && drawing && currentPath.length > 1) {
|
|
saveDrawingData('free', { path: [...currentPath] });
|
|
}
|
|
drawing = false;
|
|
currentPath = [];
|
|
|
|
// 그리기 완료 후 미러링 업데이트
|
|
setTimeout(() => {
|
|
updatePreviewMirror();
|
|
}, 10);
|
|
},
|
|
mouseout(e) {
|
|
if (drawing) drawing = false;
|
|
|
|
if (drawMode === 'polyline' && previewLine) {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
redrawCanvas();
|
|
previewLine = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
// 점연결 폴리라인 클릭 핸들러
|
|
onPolyClick = function(e) {
|
|
if (drawMode !== 'polyline') return;
|
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
const rawX = e.clientX - rect.left;
|
|
const rawY = e.clientY - rect.top;
|
|
|
|
let endX = rawX, endY = rawY;
|
|
const straight = document.getElementById('straightToggle') ? document.getElementById('straightToggle').checked : false;
|
|
if (straight && polyPrev) {
|
|
const dx = rawX - polyPrev.x, dy = rawY - polyPrev.y;
|
|
if (Math.abs(dx) > Math.abs(dy)) {
|
|
endY = polyPrev.y;
|
|
} else {
|
|
endX = polyPrev.x;
|
|
}
|
|
}
|
|
|
|
if (previewLine) {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
redrawCanvas();
|
|
previewLine = null;
|
|
}
|
|
|
|
if (!polyPrev) {
|
|
polyPrev = { x: endX, y: endY };
|
|
return;
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(polyPrev.x, polyPrev.y);
|
|
ctx.lineTo(endX, endY);
|
|
ctx.strokeStyle = drawColor;
|
|
ctx.lineWidth = 2;
|
|
ctx.setLineDash([]);
|
|
ctx.stroke();
|
|
|
|
saveDrawingData('polyline', {
|
|
startX: polyPrev.x,
|
|
startY: polyPrev.y,
|
|
endX: endX,
|
|
endY: endY
|
|
});
|
|
polyPrev = { x: endX, y: endY };
|
|
|
|
// 폴리라인 그리기 완료 후 미러링 업데이트
|
|
setTimeout(() => {
|
|
updatePreviewMirror();
|
|
}, 10);
|
|
};
|
|
|
|
// ESC 누르면 폴리라인 종료
|
|
onPolyKeydown = function(e) {
|
|
if (drawMode === 'polyline' && e.key === 'Escape') {
|
|
polyPrev = null;
|
|
if (previewLine) {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
redrawCanvas();
|
|
previewLine = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
// 캔버스 내용 다시 그리기
|
|
function redrawCanvas() {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
const imageItem = drawingData.find(item => item.type === 'image');
|
|
if (imageItem) {
|
|
// 이미지 객체가 없거나 src가 다르면 새로 생성
|
|
if (!erasedImageObj || erasedImageObj.src !== imageItem.data) {
|
|
erasedImageObj = new window.Image();
|
|
erasedImageObj.onload = function() {
|
|
ctx.drawImage(erasedImageObj, 0, 0, canvas.width, canvas.height);
|
|
// 이후 나머지 drawingData 그리기
|
|
drawingData.forEach(item => {
|
|
if (item.type !== 'image') {
|
|
ctx.save();
|
|
ctx.strokeStyle = item.color;
|
|
ctx.lineWidth = item.lineWidth || 2;
|
|
ctx.setLineDash([]);
|
|
if (item.type === 'line') {
|
|
ctx.beginPath();
|
|
ctx.moveTo(item.data.startX, item.data.startY);
|
|
ctx.lineTo(item.data.endX, item.data.endY);
|
|
ctx.stroke();
|
|
} else if (item.type === 'polyline') {
|
|
ctx.beginPath();
|
|
ctx.moveTo(item.data.startX, item.data.startY);
|
|
ctx.lineTo(item.data.endX, item.data.endY);
|
|
ctx.stroke();
|
|
} else if (item.type === 'free') {
|
|
if (item.data.path) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(item.data.path[0].x, item.data.path[0].y);
|
|
for (let i = 1; i < item.data.path.length; i++) {
|
|
ctx.lineTo(item.data.path[i].x, item.data.path[i].y);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
} else if (item.type === 'text') {
|
|
ctx.save();
|
|
ctx.fillStyle = item.color;
|
|
ctx.font = item.font;
|
|
ctx.textBaseline = 'top';
|
|
ctx.fillText(item.data.text, item.data.x, item.data.y);
|
|
ctx.restore();
|
|
}
|
|
ctx.restore();
|
|
}
|
|
});
|
|
};
|
|
erasedImageObj.src = imageItem.data;
|
|
return; // 이미지가 로드될 때까지 대기
|
|
} else {
|
|
// 이미지가 이미 로드되어 있으면 바로 그림
|
|
ctx.drawImage(erasedImageObj, 0, 0, canvas.width, canvas.height);
|
|
drawingData.forEach(item => {
|
|
if (item.type !== 'image') {
|
|
ctx.save();
|
|
ctx.strokeStyle = item.color;
|
|
ctx.lineWidth = item.lineWidth || 2;
|
|
ctx.setLineDash([]);
|
|
if (item.type === 'line') {
|
|
ctx.beginPath();
|
|
ctx.moveTo(item.data.startX, item.data.startY);
|
|
ctx.lineTo(item.data.endX, item.data.endY);
|
|
ctx.stroke();
|
|
} else if (item.type === 'polyline') {
|
|
ctx.beginPath();
|
|
ctx.moveTo(item.data.startX, item.data.startY);
|
|
ctx.lineTo(item.data.endX, item.data.endY);
|
|
ctx.stroke();
|
|
} else if (item.type === 'free') {
|
|
if (item.data.path) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(item.data.path[0].x, item.data.path[0].y);
|
|
for (let i = 1; i < item.data.path.length; i++) {
|
|
ctx.lineTo(item.data.path[i].x, item.data.path[i].y);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
} else if (item.type === 'text') {
|
|
ctx.save();
|
|
ctx.fillStyle = item.color;
|
|
ctx.font = item.font;
|
|
ctx.textBaseline = 'top';
|
|
ctx.fillText(item.data.text, item.data.x, item.data.y);
|
|
ctx.restore();
|
|
}
|
|
ctx.restore();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
// image 타입이 없으면 기존 방식대로
|
|
if (originalImage) {
|
|
ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
|
|
} else {
|
|
ctx.fillStyle = '#ffffff';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
}
|
|
drawingData.forEach(item => {
|
|
ctx.save();
|
|
ctx.strokeStyle = item.color;
|
|
ctx.lineWidth = item.lineWidth || 2;
|
|
ctx.setLineDash([]);
|
|
if (item.type === 'line') {
|
|
ctx.beginPath();
|
|
ctx.moveTo(item.data.startX, item.data.startY);
|
|
ctx.lineTo(item.data.endX, item.data.endY);
|
|
ctx.stroke();
|
|
} else if (item.type === 'polyline') {
|
|
ctx.beginPath();
|
|
ctx.moveTo(item.data.startX, item.data.startY);
|
|
ctx.lineTo(item.data.endX, item.data.endY);
|
|
ctx.stroke();
|
|
} else if (item.type === 'free') {
|
|
if (item.data.path) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(item.data.path[0].x, item.data.path[0].y);
|
|
for (let i = 1; i < item.data.path.length; i++) {
|
|
ctx.lineTo(item.data.path[i].x, item.data.path[i].y);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
} else if (item.type === 'text') {
|
|
ctx.save();
|
|
ctx.fillStyle = item.color;
|
|
ctx.font = item.font;
|
|
ctx.textBaseline = 'top';
|
|
ctx.fillText(item.data.text, item.data.x, item.data.y);
|
|
ctx.restore();
|
|
}
|
|
ctx.restore();
|
|
});
|
|
}
|
|
|
|
// 미리보기 선 그리기 함수
|
|
function drawPreviewLine(x, y) {
|
|
if (!polyPrev || drawMode !== 'polyline') return;
|
|
|
|
const straight = document.getElementById('straightToggle') ? document.getElementById('straightToggle').checked : false;
|
|
let endX = x, endY = y;
|
|
if (straight) {
|
|
const dx = endX - polyPrev.x, dy = endY - polyPrev.y;
|
|
if (Math.abs(dx) > Math.abs(dy)) {
|
|
endY = polyPrev.y;
|
|
} else {
|
|
endX = polyPrev.x;
|
|
}
|
|
}
|
|
|
|
if (previewLine) {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
redrawCanvas();
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(polyPrev.x, polyPrev.y);
|
|
ctx.lineTo(endX, endY);
|
|
ctx.strokeStyle = drawColor;
|
|
ctx.lineWidth = 2;
|
|
ctx.setLineDash([5, 5]);
|
|
ctx.stroke();
|
|
|
|
previewLine = { startX: polyPrev.x, startY: polyPrev.y, endX, endY };
|
|
}
|
|
|
|
// 그리기 모드 활성화 시 canvas 생성 및 이벤트 등록
|
|
function createAndShowCanvas() {
|
|
console.log('createAndShowCanvas 함수 시작 - 그리기 모드 활성화');
|
|
|
|
// 현재 버튼 상태 확인
|
|
const drawBtn = document.getElementById('<?= $drawBtnId ?>');
|
|
if (!drawBtn || !drawBtn.classList.contains('active')) {
|
|
console.log('그리기 버튼이 활성화되지 않음');
|
|
return;
|
|
}
|
|
|
|
// 기본 모드 설정
|
|
drawMode = 'polyline';
|
|
const modeSelect = document.getElementById('drawMode');
|
|
const lineBtn = document.getElementById('lineBtn');
|
|
const textBtn = document.getElementById('textBtn');
|
|
const eraserBtn = document.getElementById('eraserBtn');
|
|
|
|
if (modeSelect) modeSelect.value = 'polyline';
|
|
if (lineBtn) lineBtn.classList.add('active');
|
|
if (textBtn) textBtn.classList.remove('active');
|
|
if (eraserBtn) eraserBtn.classList.remove('active');
|
|
|
|
console.log('canvas 존재 여부:', !!canvas);
|
|
|
|
// 기존 canvas 완전 제거
|
|
if (canvas) {
|
|
console.log('기존 canvas 완전 제거');
|
|
try {
|
|
['mousedown','mousemove','mouseup','mouseout'].forEach(evt =>
|
|
canvas.removeEventListener(evt, handlers[evt])
|
|
);
|
|
canvas.removeEventListener('click', onPolyClick);
|
|
document.removeEventListener('keydown', onPolyKeydown);
|
|
canvas.remove();
|
|
} catch (e) {
|
|
console.log('canvas 제거 중 오류:', e);
|
|
}
|
|
canvas = null;
|
|
ctx = null;
|
|
}
|
|
|
|
// 기존 drawingCanvas도 제거
|
|
const existingCanvas = document.getElementById('drawingCanvas');
|
|
if (existingCanvas) {
|
|
console.log('기존 drawingCanvas 제거');
|
|
existingCanvas.remove();
|
|
}
|
|
|
|
console.log('createCanvasOverlay 함수 호출');
|
|
canvas = createCanvasOverlay();
|
|
console.log('생성된 canvas:', canvas);
|
|
|
|
// 그리기 영역에 캔버스 추가
|
|
const drawingCanvasArea = document.getElementById('drawingCanvasArea');
|
|
if (drawingCanvasArea) {
|
|
drawingCanvasArea.appendChild(canvas);
|
|
}
|
|
ctx = canvas.getContext('2d');
|
|
ctx.lineWidth = 2;
|
|
|
|
drawingData = [];
|
|
currentPath = [];
|
|
|
|
if (originalImage) {
|
|
console.log('originalImage가 존재합니다. 캔버스에 그립니다.');
|
|
ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
|
|
} else {
|
|
const preview = document.getElementById('<?= $previewContainerId ?>');
|
|
const img = preview ? preview.querySelector('img') : null;
|
|
console.log('preview 내부의 img 요소:', img);
|
|
if (img) ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
}
|
|
|
|
console.log('캔버스 이벤트 리스너 등록');
|
|
canvas.addEventListener('mousedown', handlers.mousedown);
|
|
canvas.addEventListener('mousemove', handlers.mousemove);
|
|
canvas.addEventListener('mouseup', handlers.mouseup);
|
|
canvas.addEventListener('mouseout', handlers.mouseout);
|
|
canvas.addEventListener('click', onPolyClick);
|
|
document.addEventListener('keydown', onPolyKeydown);
|
|
|
|
// 지우개 가상 커서를 위한 마우스 움직임 이벤트
|
|
canvas.addEventListener('mousemove', function(e) {
|
|
if (isEraser) {
|
|
updateEraserCursorPosition(e);
|
|
}
|
|
});
|
|
|
|
canvas.addEventListener('mouseout', function() {
|
|
const cursor = document.getElementById('eraserCursor');
|
|
if (cursor) {
|
|
cursor.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
console.log('attachTextHandler 함수 호출');
|
|
attachTextHandler();
|
|
|
|
console.log('createAndShowCanvas 함수 완료 - 그리기 모드 준비 완료');
|
|
}
|
|
|
|
// 문자 클릭 찍어서 그리기
|
|
function attachTextHandler() {
|
|
if (!canvas) return;
|
|
canvas.addEventListener('click', e => {
|
|
if (!textMode) return;
|
|
|
|
// 캔버스 기준 좌표 계산
|
|
const rect = canvas.getBoundingClientRect();
|
|
const xCanvas = e.clientX - rect.left;
|
|
const yCanvas = e.clientY - rect.top;
|
|
|
|
// 캔버스 내부 좌표인지 확인
|
|
if (xCanvas < 0 || xCanvas > canvas.width || yCanvas < 0 || yCanvas > canvas.height) {
|
|
return;
|
|
}
|
|
|
|
// 미리보기 컨테이너 기준으로 위치 계산
|
|
const preview = document.getElementById('<?= $previewContainerId ?>');
|
|
const previewRect = preview.getBoundingClientRect();
|
|
|
|
// 캔버스와 미리보기 컨테이너의 비율 계산
|
|
const canvasRatioX = canvas.width / previewRect.width;
|
|
const canvasRatioY = canvas.height / previewRect.height;
|
|
|
|
// 미리보기 컨테이너 기준 좌표로 변환
|
|
const xInput = xCanvas / canvasRatioX;
|
|
const yInput = yCanvas / canvasRatioY;
|
|
|
|
const inp = document.createElement('input');
|
|
inp.type = 'text';
|
|
inp.className = 'noborder-input';
|
|
inp.style.position = 'absolute';
|
|
inp.style.left = `${xInput}px`;
|
|
inp.style.top = `${yInput}px`;
|
|
inp.style.width = '80px';
|
|
inp.style.height = '20px';
|
|
inp.style.zIndex = 1000;
|
|
inp.style.border = 'none';
|
|
inp.style.outline = 'none';
|
|
inp.style.background = 'transparent';
|
|
inp.style.fontSize = '16px';
|
|
inp.style.fontFamily = 'Arial';
|
|
inp.style.color = drawColor;
|
|
inp.style.padding = '0';
|
|
|
|
preview.appendChild(inp);
|
|
inp.focus();
|
|
|
|
inp.addEventListener('blur', () => {
|
|
try {
|
|
if (inp && inp.parentNode === preview) {
|
|
preview.removeChild(inp);
|
|
}
|
|
} catch (err) {
|
|
console.log('Input element already removed');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// drawing-row 동적 생성 함수 추가
|
|
function createDrawingRow() {
|
|
// 기존 그리기 row가 있다면 제거
|
|
const existingDrawingRow = document.querySelector('.drawing-row');
|
|
if (existingDrawingRow) {
|
|
existingDrawingRow.remove();
|
|
}
|
|
// 새로운 그리기 row 생성
|
|
const drawingRow = document.createElement('div');
|
|
drawingRow.className = 'row drawing-row';
|
|
drawingRow.innerHTML = `
|
|
<div class="col-sm-12">
|
|
<div class="drawing-container">
|
|
<div class="drawing-canvas-area" id="drawingCanvasArea">
|
|
<!-- 캔버스가 여기에 생성됩니다 -->
|
|
</div>
|
|
<div class="drawing-controls">
|
|
<h6 class="mb-3">그리기 도구</h6>
|
|
<div class="mb-3">
|
|
<label class="form-label">그리기 모드:</label>
|
|
<select id="drawMode" class="form-select form-select-sm">
|
|
<option value="polyline">점연결</option>
|
|
<option value="line">직선</option>
|
|
<option value="free">자유선</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">색상:</label>
|
|
<input type="color" id="drawColor" class="form-control form-control-sm" value="#000000">
|
|
</div>
|
|
<div class="mb-3">
|
|
<button type="button" id="lineBtn" class="btn btn-outline-primary btn-sm me-2">선</button>
|
|
<button type="button" id="textBtn" class="btn btn-outline-primary btn-sm me-2">텍스트</button>
|
|
<button type="button" id="eraserBtn" class="btn btn-outline-warning btn-sm">지우개</button>
|
|
</div>
|
|
<div class="mb-3" id="eraserSizeContainer" style="display: none;">
|
|
<label class="form-label">지우개 크기:</label>
|
|
<input type="range" id="eraserSize" class="form-range" min="5" max="50" value="15">
|
|
<small class="text-muted" id="eraserSizeLabel">15px</small>
|
|
</div>
|
|
<div class="mb-3" id="straightModeContainer" style="display: block;">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="straightToggle">
|
|
<label class="form-check-label" for="straightToggle" id="straightToggleLabel">
|
|
직각모드
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<button type="button" id="saveDrawingBtn" class="btn btn-success btn-sm me-2">저장</button>
|
|
<button type="button" id="clearDrawingBtn" class="btn btn-danger btn-sm">초기화</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
// previewContainerId 밑에 추가
|
|
const preview = document.getElementById('<?= $previewContainerId ?>');
|
|
if (preview) {
|
|
preview.appendChild(drawingRow);
|
|
setTimeout(() => {
|
|
attachDrawingControlEvents();
|
|
}, 50);
|
|
}
|
|
}
|
|
|
|
// 전역 함수로 노출
|
|
window.initializeDrawingFeatures = initializeDrawingFeatures;
|
|
window.drawBtnClickHandler = drawBtnClickHandler;
|
|
window.updatePreviewMirror = updatePreviewMirror;
|
|
window.showNoImageMessage = showNoImageMessage;
|
|
window.hideNoImageMessage = hideNoImageMessage;
|
|
|
|
// DOM이 로드되면 초기화
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initializeDrawingFeatures);
|
|
} else {
|
|
initializeDrawingFeatures();
|
|
}
|
|
|
|
})();
|
|
</script>
|