1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
|
"use client";
import React, {
useState,
useEffect,
useRef,
SetStateAction,
Dispatch,
} from "react";
import { WebViewerInstance } from "@pdftron/webviewer";
import { Loader2 } from "lucide-react";
import { toast } from "sonner";
interface BasicContractTemplateViewerProps {
templateId?: number;
filePath?: string;
instance: WebViewerInstance | null;
setInstance: Dispatch<SetStateAction<WebViewerInstance | null>>;
}
export function BasicContractTemplateViewer({
templateId,
filePath,
instance,
setInstance,
}: BasicContractTemplateViewerProps) {
const [fileLoading, setFileLoading] = useState<boolean>(true);
const viewer = useRef<HTMLDivElement>(null);
const initialized = useRef(false);
const isCancelled = useRef(false);
// WebViewer 초기화 (기존 SignViewer와 완전히 동일)
useEffect(() => {
if (!initialized.current && viewer.current) {
initialized.current = true;
isCancelled.current = false;
requestAnimationFrame(() => {
if (viewer.current) {
import("@pdftron/webviewer").then(({ default: WebViewer }) => {
if (isCancelled.current) {
console.log("📛 WebViewer 초기화 취소됨");
return;
}
// viewerElement이 확실히 존재함을 확인
const viewerElement = viewer.current;
if (!viewerElement) return;
WebViewer(
{
path: "/pdftronWeb",
licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY,
fullAPI: true,
// 한글 입력 지원을 위한 설정
enableOfficeEditing: true, // Office 편집 모드에서 IME 지원 필요
l: "ko", // 한국어 로케일 설정
},
viewerElement
).then((instance: WebViewerInstance) => {
setInstance(instance);
setFileLoading(false);
try {
const { disableElements, enableElements, setToolbarGroup } = instance.UI;
// 편집에 필요한 요소들 활성화
enableElements([
"toolbarGroup-Edit",
"toolbarGroup-Insert",
"textSelectButton",
"panToolButton"
]);
// 불필요한 요소들만 비활성화
disableElements([
"toolbarGroup-Annotate", // 주석 도구
"toolbarGroup-Shapes", // 도형 도구
"toolbarGroup-Forms", // 폼 도구
"signatureToolButton", // 서명 도구
"stampToolButton", // 스탬프 도구
"rubberStampToolButton", // 러버스탬프 도구
"freeHandToolButton", // 자유 그리기
"stickyToolButton", // 스티키 노트
"calloutToolButton", // 콜아웃
]);
// 편집 툴바 설정
setToolbarGroup("toolbarGroup-Edit");
// 한글 입력 지원을 위한 추가 설정
const iframeWindow = instance.UI.iframeWindow;
if (iframeWindow && iframeWindow.document) {
// IME 지원 활성화
const documentBody = iframeWindow.document.body;
if (documentBody) {
documentBody.style.imeMode = 'active';
documentBody.setAttribute('lang', 'ko-KR');
}
// 키보드 이벤트 리스너 추가 (한글 입력 감지)
iframeWindow.document.addEventListener('compositionstart', (e) => {
console.log('🇰🇷 한글 입력 시작');
});
iframeWindow.document.addEventListener('compositionend', (e) => {
console.log('🇰🇷 한글 입력 완료:', e.data);
});
}
console.log("📝 WebViewer 한글 지원 초기화 완료");
} catch (uiError) {
console.warn("⚠️ UI 설정 중 오류 (무시됨):", uiError);
}
}).catch((error) => {
console.error("❌ WebViewer 초기화 실패:", error);
setFileLoading(false);
toast.error("뷰어 초기화에 실패했습니다.");
});
});
}
});
}
return () => {
if (instance) {
instance.UI.dispose();
}
isCancelled.current = true;
setTimeout(() => cleanupHtmlStyle(), 500);
};
}, []);
// 문서 로드 (기존 SignViewer와 동일)
useEffect(() => {
if (!instance || !filePath) return;
loadDocument(instance, filePath);
}, [instance, filePath]);
// 한글 지원 Office 문서 로드
const loadDocument = async (instance: WebViewerInstance, documentPath: string) => {
setFileLoading(true);
try {
// 절대 URL로 변환
const fullPath = documentPath.startsWith('http')
? documentPath
: `${window.location.origin}${documentPath}`;
// 파일명 추출
const fileName = documentPath.split('/').pop() || 'document.docx';
console.log("📄 한글 지원 Office 문서 로드 시작:", fullPath);
console.log("📎 파일명:", fileName);
// PDFTron 공식 방법: instance.UI.loadDocument() + 한글 지원 옵션
await instance.UI.loadDocument(fullPath, {
filename: fileName,
enableOfficeEditing: true,
// 한글 입력 지원을 위한 추가 옵션
officeOptions: {
locale: 'ko-KR',
enableIME: true,
}
});
// 문서 로드 후 한글 입력 환경 설정
setTimeout(() => {
try {
const iframeWindow = instance.UI.iframeWindow;
if (iframeWindow && iframeWindow.document) {
// Office 편집기 컨테이너 찾기
const officeContainer = iframeWindow.document.querySelector('[data-office-editor]') ||
iframeWindow.document.querySelector('.office-editor') ||
iframeWindow.document.body;
if (officeContainer) {
// 한글 입력 최적화 설정
officeContainer.style.imeMode = 'active';
officeContainer.setAttribute('lang', 'ko-KR');
officeContainer.setAttribute('inputmode', 'text');
console.log("🇰🇷 한글 입력 환경 설정 완료");
}
}
} catch (setupError) {
console.warn("⚠️ 한글 입력 환경 설정 실패:", setupError);
}
}, 1000);
console.log("✅ 한글 지원 Office 편집 모드로 문서 로드 완료");
toast.success("Office 편집 모드가 활성화되었습니다.", {
description: "한글 입력이 안 될 경우 외부에서 작성 후 복사-붙여넣기를 사용하세요."
});
} catch (err) {
console.error("❌ Office 문서 로딩 중 오류:", err);
toast.error(`Office 문서 로드 실패: ${err instanceof Error ? err.message : '알 수 없는 오류'}`);
} finally {
setFileLoading(false);
}
};
// 기존 SignViewer와 동일한 렌더링 (확대 문제 해결)
return (
<div className="relative w-full h-full overflow-hidden">
<div
ref={viewer}
className="w-full h-full"
style={{
position: 'relative',
overflow: 'hidden',
contain: 'layout style paint', // CSS containment로 격리
}}
>
{fileLoading && (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-white bg-opacity-90 z-10">
<Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
<p className="text-sm text-muted-foreground">문서 로딩 중...</p>
</div>
)}
</div>
</div>
);
}
// WebViewer 정리 함수 (기존과 동일)
const cleanupHtmlStyle = () => {
// iframe 스타일 정리 (WebViewer가 추가한 스타일)
const elements = document.querySelectorAll('.Document_container');
elements.forEach((elem) => {
elem.remove();
});
};
|