- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경 - DB 연결 하드코딩 → .env 기반으로 변경 - MySQL strict mode DATE 오류 수정
238 lines
8.9 KiB
HTML
238 lines
8.9 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>이미지 에디터 통합 예제</title>
|
||
<!-- Bootstrap 5.3 CSS + Icons -->
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
|
||
<style>
|
||
body { padding: 1rem; }
|
||
dialog {
|
||
width: 800px; height: 600px;
|
||
border: none; border-radius: .5rem;
|
||
padding: 0; overflow: hidden;
|
||
}
|
||
dialog::backdrop { background: rgba(0,0,0,.5); }
|
||
#editorHeader {
|
||
background: #f8f9fa; padding: .5rem 1rem;
|
||
border-bottom: 1px solid #dee2e6;
|
||
display: flex; align-items: center;
|
||
}
|
||
#editorHeader h5 { margin: 0; flex-grow: 1; }
|
||
#editorHeader .btn-close { margin-left: .5rem; }
|
||
#editorToolbar, #editorToolbar2 {
|
||
background: #fff;
|
||
padding: .5rem; gap: .25rem;
|
||
}
|
||
#editorToolbar { display: flex; align-items: center; flex-wrap: wrap; }
|
||
#editorToolbar2 { display: flex; align-items: center; }
|
||
.toolbar-btn { width: 40px; height: 40px; padding: 0; }
|
||
.toolbar-btn i { font-size: 1.2rem; }
|
||
.toolbar-btn.active { background-color: #0d6efd; color: #fff; }
|
||
.color-btn { width: 24px; height: 24px; padding: 0; border: 2px solid #fff; cursor: pointer; }
|
||
.color-btn.active { border-color: #000; }
|
||
#editorBody { position: relative; width: 100%; height: calc(100% - 128px); }
|
||
#editorBody canvas { width: 100%; height: 100%; cursor: crosshair; }
|
||
.canvas-text-input {
|
||
position: absolute;
|
||
border: 1px dashed #666;
|
||
background: transparent;
|
||
resize: none;
|
||
outline: none;
|
||
padding: 2px;
|
||
font-family: sans-serif;
|
||
}
|
||
|
||
/* 이미지 컨테이너 스타일 */
|
||
.image-container {
|
||
position: relative;
|
||
display: inline-block;
|
||
}
|
||
|
||
.paste-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 123, 255, 0.8);
|
||
color: white;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 0.375rem;
|
||
font-size: 0.875rem;
|
||
opacity: 0;
|
||
transition: opacity 0.2s;
|
||
}
|
||
|
||
.paste-overlay.show {
|
||
opacity: 1;
|
||
}
|
||
|
||
.paste-overlay i {
|
||
font-size: 2rem;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- 편집 대상 이미지 -->
|
||
<div class="image-container mb-3">
|
||
<img id="targetImage"
|
||
src="/guiderail/images/2025_07_16_15_53_07_drawing.png"
|
||
class="img-thumbnail"
|
||
alt="편집할 이미지"
|
||
style="max-width:400px; cursor: pointer;">
|
||
<div class="paste-overlay" style="display: none;">
|
||
<i class="bi bi-clipboard-plus"></i>
|
||
<span>Ctrl+V로 이미지 붙여넣기</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 에디터 열기 버튼 -->
|
||
<button id="openEditorBtn" class="btn btn-primary mb-3">
|
||
<i class="bi bi-pencil-square"></i> 그리기 시작
|
||
</button>
|
||
|
||
<!-- 다이얼로그(편집기) 마크업: imageEditor.js 호출 시 참조 -->
|
||
<dialog id="editorDialog">
|
||
<div id="editorHeader">
|
||
<h5>이미지 편집기</h5>
|
||
<button id="applyBtn" class="btn btn-success btn-sm">적용하기</button>
|
||
<button type="button" class="btn-close" aria-label="닫기" id="closeEditorBtn"></button>
|
||
</div>
|
||
|
||
<!-- 툴바 -->
|
||
<div id="editorToolbar">
|
||
<button id="polyBtn" class="btn btn-outline-primary toolbar-btn" title="폴리라인"><i class="bi bi-vector-pen"></i></button>
|
||
<button id="freeBtn" class="btn btn-outline-primary toolbar-btn" title="자유선"><i class="bi bi-brush"></i></button>
|
||
<button id="lineBtn" class="btn btn-outline-primary toolbar-btn" title="직선 (L키)"><i class="bi bi-slash-lg"></i></button>
|
||
<button id="textBtn" class="btn btn-outline-primary toolbar-btn" title="문자입력"><i class="bi bi-type"></i></button>
|
||
<button id="eraserBtn" class="btn btn-outline-warning toolbar-btn" title="지우개"><i class="bi bi-eraser-fill"></i></button>
|
||
<button id="selectBtn" class="btn btn-outline-secondary toolbar-btn" title="객체선택"><i class="bi bi-cursor-text"></i></button>
|
||
<div class="form-check form-switch ms-3">
|
||
<input class="form-check-input" type="checkbox" id="rightAngle" checked>
|
||
<label class="form-check-label" for="rightAngle">직각 고정</label>
|
||
</div>
|
||
<button id="clearBtn" class="btn btn-outline-danger ms-auto">전체 지우기</button>
|
||
</div>
|
||
|
||
<!-- 지우개 크기 & 색상 -->
|
||
<div id="editorToolbar2" class="d-flex align-items-center px-3 pb-2">
|
||
<div class="btn-group" role="group" id="colorPicker">
|
||
<button type="button" class="btn color-btn active" data-color="#000000" style="background:#000;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#ff0000" style="background:#f00;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#0000ff" style="background:#00f;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#00aa00" style="background:#0a0;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#ff8800" style="background:#f80;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#800080" style="background:#808;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#888888" style="background:#888;"></button>
|
||
</div>
|
||
<label class="mb-0 me-2">지우개 크기</label>
|
||
<input type="range" class="form-range me-2" id="eraserRange" min="5" max="100" step="1" value="20">
|
||
<span id="eraserSizeLabel" class="fw-bold">20</span>px
|
||
</div>
|
||
|
||
<!-- 캔버스 영역 -->
|
||
<div id="editorBody">
|
||
<canvas id="c" width="800" height="600"></canvas>
|
||
</div>
|
||
</dialog>
|
||
|
||
<!-- Dependencies -->
|
||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.0/fabric.min.js"></script>
|
||
|
||
<!-- 모듈 스크립트 -->
|
||
<script type="module">
|
||
import { openImageEditor } from '/js/imageEditor.js';
|
||
|
||
const openBtn = document.getElementById('openEditorBtn');
|
||
const targetImg = document.getElementById('targetImage');
|
||
const imageContainer = document.querySelector('.image-container');
|
||
const pasteOverlay = document.querySelector('.paste-overlay');
|
||
|
||
// 붙여넣기 이벤트 처리 함수
|
||
function handlePaste(event) {
|
||
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
|
||
|
||
for (let item of items) {
|
||
if (item.type.indexOf('image') !== -1) {
|
||
const blob = item.getAsFile();
|
||
const reader = new FileReader();
|
||
|
||
reader.onload = function(e) {
|
||
targetImg.src = e.target.result;
|
||
console.log('이미지가 붙여넣기되었습니다:', e.target.result);
|
||
|
||
// 붙여넣기 성공 피드백
|
||
showPasteFeedback(true);
|
||
};
|
||
|
||
reader.readAsDataURL(blob);
|
||
event.preventDefault();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 붙여넣기 피드백 표시
|
||
function showPasteFeedback(success = false) {
|
||
if (success) {
|
||
pasteOverlay.innerHTML = '<i class="bi bi-check-circle"></i><span>이미지가 붙여넣기되었습니다!</span>';
|
||
pasteOverlay.style.background = 'rgba(40, 167, 69, 0.8)';
|
||
} else {
|
||
pasteOverlay.innerHTML = '<i class="bi bi-clipboard-plus"></i><span>Ctrl+V로 이미지 붙여넣기</span>';
|
||
pasteOverlay.style.background = 'rgba(0, 123, 255, 0.8)';
|
||
}
|
||
|
||
pasteOverlay.classList.add('show');
|
||
setTimeout(() => {
|
||
pasteOverlay.classList.remove('show');
|
||
}, 2000);
|
||
}
|
||
|
||
// 붙여넣기 이벤트 리스너 추가
|
||
document.addEventListener('paste', handlePaste);
|
||
|
||
// 이미지 컨테이너 클릭 시 포커스
|
||
imageContainer.addEventListener('click', () => {
|
||
imageContainer.focus();
|
||
});
|
||
|
||
// 이미지 컨테이너에 포커스 가능하도록 설정
|
||
imageContainer.tabIndex = 0;
|
||
imageContainer.title = '클릭 후 Ctrl+V로 이미지 붙여넣기';
|
||
|
||
// 포커스 시 오버레이 표시
|
||
imageContainer.addEventListener('focus', () => {
|
||
showPasteFeedback();
|
||
});
|
||
|
||
// 포커스 해제 시 오버레이 숨김
|
||
imageContainer.addEventListener('blur', () => {
|
||
pasteOverlay.classList.remove('show');
|
||
});
|
||
|
||
openBtn.addEventListener('click', () => {
|
||
const src = targetImg.src;
|
||
openImageEditor(src)
|
||
.then(newUrl => {
|
||
// 편집된 이미지를 화면에 반영
|
||
targetImg.src = newUrl;
|
||
})
|
||
.catch(err => {
|
||
// 사용자가 취소했을 때
|
||
console.log('편집 취소:', err.message);
|
||
});
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|